| @@ -16,12 +16,23 @@ namespace Docnet | |||
| public static string GetFinalTargetUrl(this INavigationElement navigationElement, NavigationContext navigationContext) | |||
| { | |||
| var targetUrl = navigationElement.GetTargetURL(navigationContext); | |||
| return GetFinalTargetUrl(targetUrl, navigationContext); | |||
| } | |||
| /// <summary> | |||
| /// Gets the final URL by encoding the path and by removing the filename if it equals <c>index.htm</c>. | |||
| /// </summary> | |||
| /// <param name="targetUrl">The target URL.</param> | |||
| /// <param name="navigationContext">The navigation context.</param> | |||
| /// <returns></returns> | |||
| 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); | |||
| } | |||
| @@ -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<Heading>(), config.ConvertLocalLinks); | |||
| string.Empty, new List<Heading>(), config.ConvertLocalLinks, | |||
| new NavigationContext(config.PathSpecification, config.UrlFormatting, config.MaxLevelInToC, config.StripIndexHtm)); | |||
| return htmlContent; | |||
| } | |||
| @@ -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 | |||
| { | |||
| @@ -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; | |||
| @@ -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)) | |||
| @@ -1,9 +1,9 @@ | |||
| namespace Docnet | |||
| { | |||
| public enum UrlFormatting | |||
| { | |||
| public enum UrlFormatting | |||
| { | |||
| None, | |||
| Strip, | |||
| Dashes | |||
| } | |||
| Strip, | |||
| Dashes | |||
| } | |||
| } | |||
| @@ -38,34 +38,70 @@ namespace Docnet | |||
| /// Regex expression used to parse @@include(filename.html) tag. | |||
| /// </summary> | |||
| private static Regex includeRegex = new Regex(@"@@include\((.*)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); | |||
| #endregion | |||
| /// <summary> | |||
| /// Converts the markdown to HTML. | |||
| /// </summary> | |||
| /// <param name="toConvert">The markdown string to convert.</param> | |||
| /// <param name="destinationDocumentPath">The document path (without the document filename).</param> | |||
| /// <param name="siteRoot">The site root.</param> | |||
| /// <param name="sourceDocumentFilename">the filename of the source markdown file</param> | |||
| /// <param name="createdAnchorCollector">The created anchor collector, for ToC sublinks for H2 headers.</param> | |||
| /// <param name="convertLocalLinks">if set to <c>true</c>, convert local links to md files to target files.</param> | |||
| /// <returns></returns> | |||
| public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename, | |||
| List<Heading> createdAnchorCollector, bool convertLocalLinks) | |||
| #endregion | |||
| /// <summary> | |||
| /// Converts the markdown to HTML. | |||
| /// </summary> | |||
| /// <param name="toConvert">The markdown string to convert.</param> | |||
| /// <param name="destinationDocumentPath">The document path (without the document filename).</param> | |||
| /// <param name="siteRoot">The site root.</param> | |||
| /// <param name="sourceDocumentFilename">the filename of the source markdown file</param> | |||
| /// <param name="createdAnchorCollector">The created anchor collector, for ToC sublinks for H2 headers.</param> | |||
| /// <param name="convertLocalLinks">if set to <c>true</c>, convert local links to md files to target files.</param> | |||
| /// <param name="navigationContext">The navigation context.</param> | |||
| /// <returns></returns> | |||
| public static string ConvertMarkdownToHtml(string toConvert, string destinationDocumentPath, string siteRoot, string sourceDocumentFilename, | |||
| List<Heading> createdAnchorCollector, bool convertLocalLinks, NavigationContext navigationContext) | |||
| { | |||
| var localLinkProcessor = new Func<string, string>(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; | |||
| } | |||
| /// <summary> | |||
| /// 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 | |||
| /// <returns></returns> | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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("</a>"); | |||
| } | |||
| } | |||
| @@ -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<string> 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<string>(); | |||
| 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]); | |||
| @@ -880,6 +880,14 @@ namespace MarkdownDeep | |||
| /// </summary> | |||
| public bool ConvertLocalLinks { get; set; } | |||
| /// <summary> | |||
| /// Gets or sets the local link processor allowing customization of the local links before being transformed. | |||
| /// </summary> | |||
| /// <value> | |||
| /// The local link processor. | |||
| /// </value> | |||
| public Func<string, string> 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) | |||