diff --git a/src/DocNet/Config.cs b/src/DocNet/Config.cs
index f67046f..8fcbcee 100644
--- a/src/DocNet/Config.cs
+++ b/src/DocNet/Config.cs
@@ -180,7 +180,10 @@ namespace Docnet
searchSimpleElement.ExtraScriptProducerFunc = (e,c,n) => @"
";
- searchSimpleElement.GenerateOutput(this, activePath, navigationContext);
+
+ // Force custom navigation context because this should end up in the root
+ searchSimpleElement.GenerateOutput(this, activePath, new NavigationContext(PathSpecification.Full, UrlFormatting.None, 0, false));
+
activePath.Pop();
}
diff --git a/src/DocNet/INavigationElementExtensions.cs b/src/DocNet/INavigationElementExtensions.cs
index 09579aa..d10f976 100644
--- a/src/DocNet/INavigationElementExtensions.cs
+++ b/src/DocNet/INavigationElementExtensions.cs
@@ -16,12 +16,23 @@ namespace Docnet
public static string GetFinalTargetUrl(this INavigationElement navigationElement, NavigationContext navigationContext)
{
var targetUrl = navigationElement.GetTargetURL(navigationContext);
+ return GetFinalTargetUrl(targetUrl, navigationContext);
+ }
+
+ ///
+ /// Gets the final URL by encoding the path and by removing the filename if it equals index.htm.
+ ///
+ /// The target URL.
+ /// The navigation context.
+ ///
+ public static string GetFinalTargetUrl(this string targetUrl, NavigationContext navigationContext)
+ {
var link = HttpUtility.UrlPathEncode(targetUrl);
if (navigationContext.StripIndexHtm)
{
if (link.Length > IndexHtmFileName.Length &&
- link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase))
+ link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase))
{
link = link.Substring(0, link.Length - IndexHtmFileName.Length);
}
diff --git a/src/DocNet/NotFoundNavigationElement.cs b/src/DocNet/NotFoundNavigationElement.cs
index 4545ebd..9d6f1b1 100644
--- a/src/DocNet/NotFoundNavigationElement.cs
+++ b/src/DocNet/NotFoundNavigationElement.cs
@@ -42,7 +42,8 @@ namespace Docnet
var destinationFile = Utils.MakeAbsolutePath(config.Destination, this.GetTargetURL(navigationContext));
var htmlContent = Utils.ConvertMarkdownToHtml(markdownContent, Path.GetDirectoryName(destinationFile), config.Destination,
- string.Empty, new List(), config.ConvertLocalLinks);
+ string.Empty, new List(), config.ConvertLocalLinks,
+ new NavigationContext(config.PathSpecification, config.UrlFormatting, config.MaxLevelInToC, config.StripIndexHtm));
return htmlContent;
}
diff --git a/src/DocNet/SimpleNavigationElement.cs b/src/DocNet/SimpleNavigationElement.cs
index e8722db..d80f2ed 100644
--- a/src/DocNet/SimpleNavigationElement.cs
+++ b/src/DocNet/SimpleNavigationElement.cs
@@ -72,7 +72,7 @@ namespace Docnet
this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8);
// Check if the content contains @@include tag
content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder));
- content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeLinksOnPage, activeConfig.ConvertLocalLinks);
+ content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeLinksOnPage, activeConfig.ConvertLocalLinks, navigationContext);
}
else
{
@@ -95,7 +95,7 @@ namespace Docnet
sibling.GetFinalTargetUrl(navigationContext), Environment.NewLine);
}
defaultMarkdown.Append(Environment.NewLine);
- content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks);
+ content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks, navigationContext);
}
else
{
@@ -112,7 +112,7 @@ namespace Docnet
sb.Replace("{{Footer}}", activeConfig.Footer);
sb.Replace("{{TopicTitle}}", this.Name);
sb.Replace("{{Path}}", relativePathToRoot);
- sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/'));
+ sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Source, sourceFile).TrimEnd('/'));
sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/'));
sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, navigationContext));
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, navigationContext));
@@ -226,29 +226,7 @@ namespace Docnet
{
if (_targetURLForHTML == null)
{
- var toReplace = "mdext";
- var replacement = ".htm";
-
- var value = (this.Value ?? string.Empty);
-
- // Replace with custom extension because url formatting might optimize the extension
- value = value.Replace(".md", toReplace);
- _targetURLForHTML = value.ApplyUrlFormatting(navigationContext.UrlFormatting);
-
- if (navigationContext.PathSpecification == PathSpecification.RelativeAsFolder)
- {
- if (!IsIndexElement && !_targetURLForHTML.EndsWith($"index{toReplace}", StringComparison.InvariantCultureIgnoreCase))
- {
- replacement = "/index.htm";
- }
- }
-
- if (_targetURLForHTML.EndsWith(toReplace, StringComparison.InvariantCultureIgnoreCase))
- {
- _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - toReplace.Length) + replacement;
- }
-
- _targetURLForHTML = _targetURLForHTML.Replace("\\", "/");
+ _targetURLForHTML = Utils.ResolveTargetURL(this.Value ?? string.Empty, IsIndexElement, navigationContext);
}
return _targetURLForHTML;
diff --git a/src/DocNet/StringExtensions.cs b/src/DocNet/StringExtensions.cs
index 502f768..98d8122 100644
--- a/src/DocNet/StringExtensions.cs
+++ b/src/DocNet/StringExtensions.cs
@@ -37,6 +37,11 @@ namespace Docnet
for(var i = 0; i < splitted.Length; i++)
{
var splittedValue = splitted[i];
+ if (string.Equals(splittedValue, ".") || string.Equals(splittedValue, ".."))
+ {
+ continue;
+ }
+
splittedValue = regEx.Replace(splittedValue, replacementValue).Replace(" ", replacementValue);
if (!string.IsNullOrEmpty(replacementValue))
diff --git a/src/DocNet/UrlFormatting.cs b/src/DocNet/UrlFormatting.cs
index 0953938..b4c7597 100644
--- a/src/DocNet/UrlFormatting.cs
+++ b/src/DocNet/UrlFormatting.cs
@@ -1,9 +1,9 @@
namespace Docnet
{
- public enum UrlFormatting
- {
+ public enum UrlFormatting
+ {
None,
- Strip,
- Dashes
- }
+ Strip,
+ Dashes
+ }
}
\ No newline at end of file
diff --git a/src/DocNet/Utils.cs b/src/DocNet/Utils.cs
index 1aefc4e..5befa1b 100644
--- a/src/DocNet/Utils.cs
+++ b/src/DocNet/Utils.cs
@@ -38,34 +38,70 @@ namespace Docnet
/// Regex expression used to parse @@include(filename.html) tag.
///
private static Regex includeRegex = new Regex(@"@@include\((.*)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
- #endregion
-
- ///
- /// Converts the markdown to HTML.
- ///
- /// The markdown string to convert.
- /// The document path (without the document filename).
- /// The site root.
- /// the filename of the source markdown file
- /// The created anchor collector, for ToC sublinks for H2 headers.
- /// if set to true, convert local links to md files to target files.
- ///
- public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename,
- List createdAnchorCollector, bool convertLocalLinks)
+ #endregion
+
+ ///
+ /// Converts the markdown to HTML.
+ ///
+ /// The markdown string to convert.
+ /// The document path (without the document filename).
+ /// The site root.
+ /// the filename of the source markdown file
+ /// The created anchor collector, for ToC sublinks for H2 headers.
+ /// if set to true, convert local links to md files to target files.
+ /// The navigation context.
+ ///
+ public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename,
+ List createdAnchorCollector, bool convertLocalLinks, NavigationContext navigationContext)
{
+ var localLinkProcessor = new Func(s =>
+ {
+ var result = s;
+
+ if (!string.IsNullOrWhiteSpace(result))
+ {
+ switch (navigationContext.PathSpecification)
+ {
+ case PathSpecification.Full:
+ break;
+
+ case PathSpecification.Relative:
+ break;
+
+ case PathSpecification.RelativeAsFolder:
+ // Step 1: we need to move up 1 additional folder (get out of current subfolder)
+ var relativeAsFolderIndex = result.StartsWith("./") ? 2 : 0;
+ result = result.Insert(relativeAsFolderIndex, "../");
+
+ // Step 2: we need an additional layer to go into (filename is now a folder)
+ result = ResolveTargetURL(result, false, navigationContext);
+
+ // Step 3: get the final url
+ result = result.GetFinalTargetUrl(navigationContext);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(navigationContext.PathSpecification), navigationContext.PathSpecification, null);
+ }
+ }
+
+ return result;
+ });
+
var parser = new MarkdownDeep.Markdown
- {
- ExtraMode = true,
- GitHubCodeBlocks = true,
- AutoHeadingIDs = true,
- NewWindowForExternalLinks = true,
- DocNetMode = true,
- ConvertLocalLinks = convertLocalLinks,
- DestinationDocumentLocation = destinationDocumentPath,
- DocumentRoot = siteRoot,
- SourceDocumentFilename = sourceDocumentFilename,
- HtmlClassTitledImages = "figure",
- };
+ {
+ ExtraMode = true,
+ GitHubCodeBlocks = true,
+ AutoHeadingIDs = true,
+ NewWindowForExternalLinks = true,
+ DocNetMode = true,
+ ConvertLocalLinks = convertLocalLinks,
+ LocalLinkProcessor = localLinkProcessor,
+ DestinationDocumentLocation = destinationDocumentPath,
+ DocumentRoot = siteRoot,
+ SourceDocumentFilename = sourceDocumentFilename,
+ HtmlClassTitledImages = "figure",
+ };
var toReturn = parser.Transform(toConvert);
@@ -74,6 +110,33 @@ namespace Docnet
return toReturn;
}
+ public static string ResolveTargetURL(string sourceFileName, bool isIndexElement, NavigationContext navigationContext)
+ {
+ var toReplace = "mdext";
+ var replacement = ".htm";
+
+ var value = sourceFileName;
+
+ // Replace with custom extension because url formatting might optimize the extension
+ value = value.Replace(".md", toReplace);
+ var targetUrl = value.ApplyUrlFormatting(navigationContext.UrlFormatting);
+
+ if (navigationContext.PathSpecification == PathSpecification.RelativeAsFolder)
+ {
+ if (!isIndexElement && !targetUrl.EndsWith($"index{toReplace}", StringComparison.InvariantCultureIgnoreCase))
+ {
+ replacement = "/index.htm";
+ }
+ }
+
+ if (targetUrl.EndsWith(toReplace, StringComparison.InvariantCultureIgnoreCase))
+ {
+ targetUrl = targetUrl.Substring(0, targetUrl.Length - toReplace.Length) + replacement;
+ }
+
+ targetUrl = targetUrl.Replace("\\", "/");
+ return targetUrl;
+ }
///
/// Copies directories and files, eventually recursively. From MSDN.
@@ -85,26 +148,26 @@ namespace Docnet
{
// Get the subdirectories for the specified directory.
DirectoryInfo sourceFolder = new DirectoryInfo(sourceFolderName);
- if(!sourceFolder.Exists)
+ if (!sourceFolder.Exists)
{
throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceFolderName);
}
DirectoryInfo[] sourceFoldersToCopy = sourceFolder.GetDirectories();
// If the destination directory doesn't exist, create it.
- if(!Directory.Exists(destinationFolderName))
+ if (!Directory.Exists(destinationFolderName))
{
Directory.CreateDirectory(destinationFolderName);
}
// Get the files in the directory and copy them to the new location.
- foreach(FileInfo file in sourceFolder.GetFiles())
+ foreach (FileInfo file in sourceFolder.GetFiles())
{
file.CopyTo(Path.Combine(destinationFolderName, file.Name), true);
}
- if(copySubFolders)
+ if (copySubFolders)
{
- foreach(DirectoryInfo subFolder in sourceFoldersToCopy)
+ foreach (DirectoryInfo subFolder in sourceFoldersToCopy)
{
Utils.DirectoryCopy(subFolder.FullName, Path.Combine(destinationFolderName, subFolder.Name), copySubFolders);
}
@@ -121,11 +184,11 @@ namespace Docnet
///
public static string MakeAbsolutePath(string rootPath, string toMakeAbsolute)
{
- if(string.IsNullOrWhiteSpace(toMakeAbsolute))
+ if (string.IsNullOrWhiteSpace(toMakeAbsolute))
{
return rootPath;
}
- if(Path.IsPathRooted(toMakeAbsolute))
+ if (Path.IsPathRooted(toMakeAbsolute))
{
return toMakeAbsolute;
}
@@ -141,12 +204,12 @@ namespace Docnet
public static void CreateFoldersIfRequired(string fullPath)
{
string folderToCheck = Path.GetDirectoryName(fullPath);
- if(string.IsNullOrWhiteSpace(folderToCheck))
+ if (string.IsNullOrWhiteSpace(folderToCheck))
{
// nothing to do, no folder to emit
return;
}
- if(!Directory.Exists(folderToCheck))
+ if (!Directory.Exists(folderToCheck))
{
Directory.CreateDirectory(folderToCheck);
}
@@ -163,20 +226,20 @@ namespace Docnet
public static string MakeRelativePath(string fromPath, string toPath)
{
var fromPathToUse = fromPath;
- if(string.IsNullOrEmpty(fromPathToUse))
+ if (string.IsNullOrEmpty(fromPathToUse))
{
return string.Empty;
}
var toPathToUse = toPath;
- if(string.IsNullOrEmpty(toPathToUse))
+ if (string.IsNullOrEmpty(toPathToUse))
{
return string.Empty;
}
- if(fromPathToUse.Last() != Path.DirectorySeparatorChar)
+ if (fromPathToUse.Last() != Path.DirectorySeparatorChar)
{
fromPathToUse += Path.DirectorySeparatorChar;
}
- if(toPathToUse.Last() != Path.DirectorySeparatorChar)
+ if (toPathToUse.Last() != Path.DirectorySeparatorChar)
{
toPathToUse += Path.DirectorySeparatorChar;
}
@@ -184,7 +247,7 @@ namespace Docnet
var fromUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(fromPathToUse)));
var toUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(toPathToUse)));
- if(fromUri.Scheme != toUri.Scheme)
+ if (fromUri.Scheme != toUri.Scheme)
{
// path can't be made relative.
return toPathToUse;
@@ -193,7 +256,7 @@ namespace Docnet
var relativeUri = fromUri.MakeRelativeUri(toUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
- if(toUri.Scheme.ToUpperInvariant() == "FILE")
+ if (toUri.Scheme.ToUpperInvariant() == "FILE")
{
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
@@ -242,5 +305,5 @@ namespace Docnet
}
return content;
}
- }
+ }
}
diff --git a/src/MarkdownDeep/LinkDefinition.cs b/src/MarkdownDeep/LinkDefinition.cs
index e5f74a5..198764a 100644
--- a/src/MarkdownDeep/LinkDefinition.cs
+++ b/src/MarkdownDeep/LinkDefinition.cs
@@ -58,24 +58,32 @@ namespace MarkdownDeep
{
HtmlTag tag = new HtmlTag("a");
- var url = this.Url;
-
- if (m.DocNetMode && m.ConvertLocalLinks)
- {
- // A few requirements before we can convert local links:
- // 1. Link contains .md
- // 2. Link is relative
- // 3. Link is included in the index
- var index = url.LastIndexOf(".md", StringComparison.OrdinalIgnoreCase);
- if (index >= 0)
- {
- Uri uri;
- if(Uri.TryCreate(url, UriKind.Relative, out uri))
+ var url = this.Url;
+
+ if (m.DocNetMode && m.ConvertLocalLinks)
+ {
+ // A few requirements before we can convert local links:
+ // 1. Link contains .md
+ // 2. Link is relative
+ // 3. Link is included in the index
+ var index = url.LastIndexOf(".md", StringComparison.OrdinalIgnoreCase);
+ if (index >= 0)
+ {
+ var linkProcessor = m.LocalLinkProcessor;
+ if (linkProcessor != null)
+ {
+ url = linkProcessor(url);
+ }
+ else
{
- url = String.Concat(url.Substring(0, index), ".htm", url.Substring(index + ".md".Length));
+ Uri uri;
+ if (Uri.TryCreate(url, UriKind.Relative, out uri))
+ {
+ url = String.Concat(url.Substring(0, index), ".htm", url.Substring(index + ".md".Length));
+ }
}
- }
- }
+ }
+ }
// encode url
StringBuilder sb = m.GetStringBuilder();
@@ -83,14 +91,14 @@ namespace MarkdownDeep
tag.attributes["href"] = sb.ToString();
// encode title
- if (!String.IsNullOrEmpty(this.Title ))
+ if (!String.IsNullOrEmpty(this.Title))
{
sb.Length = 0;
Utils.SmartHtmlEncodeAmpsAndAngles(sb, this.Title);
tag.attributes["title"] = sb.ToString();
}
- if(specialAttributes.Any())
+ if (specialAttributes.Any())
{
LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag);
}
@@ -101,7 +109,7 @@ namespace MarkdownDeep
// Render the opening tag
tag.RenderOpening(b);
- b.Append(link_text); // Link text already escaped by SpanFormatter
+ b.Append(link_text); // Link text already escaped by SpanFormatter
b.Append("");
}
}
@@ -131,7 +139,7 @@ namespace MarkdownDeep
Utils.SmartHtmlEncodeAmpsAndAngles(sb, Title);
tag.attributes["title"] = sb.ToString();
}
- if(specialAttributes.Any())
+ if (specialAttributes.Any())
{
LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag);
}
@@ -153,9 +161,9 @@ namespace MarkdownDeep
// Parse a link definition
internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode)
{
- int savepos=p.Position;
+ int savepos = p.Position;
var l = ParseLinkDefinitionInternal(p, ExtraMode);
- if (l==null)
+ if (l == null)
p.Position = savepos;
return l;
@@ -181,7 +189,7 @@ namespace MarkdownDeep
return null;
// Parse the url and title
- var link=ParseLinkTarget(p, id, ExtraMode);
+ var link = ParseLinkTarget(p, id, ExtraMode);
// and trailing whitespace
p.SkipLinespace();
@@ -236,7 +244,7 @@ namespace MarkdownDeep
int paren_depth = 1;
while (!p.Eol)
{
- char ch=p.Current;
+ char ch = p.Current;
if (char.IsWhiteSpace(ch))
break;
if (id == null)
@@ -246,7 +254,7 @@ namespace MarkdownDeep
else if (ch == ')')
{
paren_depth--;
- if (paren_depth==0)
+ if (paren_depth == 0)
break;
}
}
@@ -275,7 +283,7 @@ namespace MarkdownDeep
char delim;
switch (p.Current)
{
- case '\'':
+ case '\'':
case '\"':
delim = p.Current;
break;
@@ -349,27 +357,27 @@ namespace MarkdownDeep
private static void HandleSpecialAttributes(List specialAttributes, StringBuilder sb, HtmlTag tag)
{
string id = specialAttributes.FirstOrDefault(s => s.StartsWith("#"));
- if(id != null && id.Length > 1)
+ if (id != null && id.Length > 1)
{
sb.Length = 0;
Utils.SmartHtmlEncodeAmpsAndAngles(sb, id.Substring(1));
tag.attributes["id"] = sb.ToString();
}
var cssClasses = new List();
- foreach(var cssClass in specialAttributes.Where(s => s.StartsWith(".") && s.Length > 1))
+ foreach (var cssClass in specialAttributes.Where(s => s.StartsWith(".") && s.Length > 1))
{
sb.Length = 0;
Utils.SmartHtmlEncodeAmpsAndAngles(sb, cssClass.Substring(1));
cssClasses.Add(sb.ToString());
}
- if(cssClasses.Any())
+ if (cssClasses.Any())
{
tag.attributes["class"] = string.Join(" ", cssClasses.ToArray());
}
- foreach(var nameValuePair in specialAttributes.Where(s => s.Contains("=") && s.Length > 2 && !s.StartsWith(".") && !s.StartsWith("#")))
+ foreach (var nameValuePair in specialAttributes.Where(s => s.Contains("=") && s.Length > 2 && !s.StartsWith(".") && !s.StartsWith("#")))
{
var pair = nameValuePair.Split('=');
- if(pair.Length == 2)
+ if (pair.Length == 2)
{
sb.Length = 0;
Utils.SmartHtmlEncodeAmpsAndAngles(sb, pair[0]);
diff --git a/src/MarkdownDeep/MarkdownDeep.cs b/src/MarkdownDeep/MarkdownDeep.cs
index d851caf..8f83ff9 100644
--- a/src/MarkdownDeep/MarkdownDeep.cs
+++ b/src/MarkdownDeep/MarkdownDeep.cs
@@ -880,6 +880,14 @@ namespace MarkdownDeep
///
public bool ConvertLocalLinks { get; set; }
+ ///
+ /// Gets or sets the local link processor allowing customization of the local links before being transformed.
+ ///
+ ///
+ /// The local link processor.
+ ///
+ public Func LocalLinkProcessor { get; set; }
+
// When set, all html block level elements automatically support
// markdown syntax within them.
// (Similar to Pandoc's handling of markdown in html)