| @@ -180,7 +180,10 @@ namespace Docnet | |||||
| searchSimpleElement.ExtraScriptProducerFunc = (e,c,n) => @" | searchSimpleElement.ExtraScriptProducerFunc = (e,c,n) => @" | ||||
| <script>var base_url = '.';</script> | <script>var base_url = '.';</script> | ||||
| <script data-main=""js/search.js"" src=""js/require.js""></script>"; | <script data-main=""js/search.js"" src=""js/require.js""></script>"; | ||||
| 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(); | activePath.Pop(); | ||||
| } | } | ||||
| @@ -16,12 +16,23 @@ namespace Docnet | |||||
| public static string GetFinalTargetUrl(this INavigationElement navigationElement, NavigationContext navigationContext) | public static string GetFinalTargetUrl(this INavigationElement navigationElement, NavigationContext navigationContext) | ||||
| { | { | ||||
| var targetUrl = navigationElement.GetTargetURL(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); | var link = HttpUtility.UrlPathEncode(targetUrl); | ||||
| if (navigationContext.StripIndexHtm) | if (navigationContext.StripIndexHtm) | ||||
| { | { | ||||
| if (link.Length > IndexHtmFileName.Length && | if (link.Length > IndexHtmFileName.Length && | ||||
| link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase)) | |||||
| link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase)) | |||||
| { | { | ||||
| link = link.Substring(0, link.Length - IndexHtmFileName.Length); | link = link.Substring(0, link.Length - IndexHtmFileName.Length); | ||||
| } | } | ||||
| @@ -42,7 +42,8 @@ namespace Docnet | |||||
| var destinationFile = Utils.MakeAbsolutePath(config.Destination, this.GetTargetURL(navigationContext)); | var destinationFile = Utils.MakeAbsolutePath(config.Destination, this.GetTargetURL(navigationContext)); | ||||
| var htmlContent = Utils.ConvertMarkdownToHtml(markdownContent, Path.GetDirectoryName(destinationFile), config.Destination, | 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; | return htmlContent; | ||||
| } | } | ||||
| @@ -72,7 +72,7 @@ namespace Docnet | |||||
| this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8); | this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8); | ||||
| // Check if the content contains @@include tag | // Check if the content contains @@include tag | ||||
| content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder)); | 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 | else | ||||
| { | { | ||||
| @@ -95,7 +95,7 @@ namespace Docnet | |||||
| sibling.GetFinalTargetUrl(navigationContext), Environment.NewLine); | sibling.GetFinalTargetUrl(navigationContext), Environment.NewLine); | ||||
| } | } | ||||
| defaultMarkdown.Append(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 | else | ||||
| { | { | ||||
| @@ -112,7 +112,7 @@ namespace Docnet | |||||
| sb.Replace("{{Footer}}", activeConfig.Footer); | sb.Replace("{{Footer}}", activeConfig.Footer); | ||||
| sb.Replace("{{TopicTitle}}", this.Name); | sb.Replace("{{TopicTitle}}", this.Name); | ||||
| sb.Replace("{{Path}}", relativePathToRoot); | 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("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); | ||||
| sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, navigationContext)); | sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, navigationContext)); | ||||
| sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, navigationContext)); | sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, navigationContext)); | ||||
| @@ -226,29 +226,7 @@ namespace Docnet | |||||
| { | { | ||||
| if (_targetURLForHTML == null) | 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; | return _targetURLForHTML; | ||||
| @@ -37,6 +37,11 @@ namespace Docnet | |||||
| for(var i = 0; i < splitted.Length; i++) | for(var i = 0; i < splitted.Length; i++) | ||||
| { | { | ||||
| var splittedValue = splitted[i]; | var splittedValue = splitted[i]; | ||||
| if (string.Equals(splittedValue, ".") || string.Equals(splittedValue, "..")) | |||||
| { | |||||
| continue; | |||||
| } | |||||
| splittedValue = regEx.Replace(splittedValue, replacementValue).Replace(" ", replacementValue); | splittedValue = regEx.Replace(splittedValue, replacementValue).Replace(" ", replacementValue); | ||||
| if (!string.IsNullOrEmpty(replacementValue)) | if (!string.IsNullOrEmpty(replacementValue)) | ||||
| @@ -1,9 +1,9 @@ | |||||
| namespace Docnet | namespace Docnet | ||||
| { | { | ||||
| public enum UrlFormatting | |||||
| { | |||||
| public enum UrlFormatting | |||||
| { | |||||
| None, | None, | ||||
| Strip, | |||||
| Dashes | |||||
| } | |||||
| Strip, | |||||
| Dashes | |||||
| } | |||||
| } | } | ||||
| @@ -38,34 +38,70 @@ namespace Docnet | |||||
| /// Regex expression used to parse @@include(filename.html) tag. | /// Regex expression used to parse @@include(filename.html) tag. | ||||
| /// </summary> | /// </summary> | ||||
| private static Regex includeRegex = new Regex(@"@@include\((.*)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); | 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 | 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); | var toReturn = parser.Transform(toConvert); | ||||
| @@ -74,6 +110,33 @@ namespace Docnet | |||||
| return toReturn; | 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> | /// <summary> | ||||
| /// Copies directories and files, eventually recursively. From MSDN. | /// Copies directories and files, eventually recursively. From MSDN. | ||||
| @@ -85,26 +148,26 @@ namespace Docnet | |||||
| { | { | ||||
| // Get the subdirectories for the specified directory. | // Get the subdirectories for the specified directory. | ||||
| DirectoryInfo sourceFolder = new DirectoryInfo(sourceFolderName); | 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); | throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceFolderName); | ||||
| } | } | ||||
| DirectoryInfo[] sourceFoldersToCopy = sourceFolder.GetDirectories(); | DirectoryInfo[] sourceFoldersToCopy = sourceFolder.GetDirectories(); | ||||
| // If the destination directory doesn't exist, create it. | // If the destination directory doesn't exist, create it. | ||||
| if(!Directory.Exists(destinationFolderName)) | |||||
| if (!Directory.Exists(destinationFolderName)) | |||||
| { | { | ||||
| Directory.CreateDirectory(destinationFolderName); | Directory.CreateDirectory(destinationFolderName); | ||||
| } | } | ||||
| // Get the files in the directory and copy them to the new location. | // 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); | 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); | Utils.DirectoryCopy(subFolder.FullName, Path.Combine(destinationFolderName, subFolder.Name), copySubFolders); | ||||
| } | } | ||||
| @@ -121,11 +184,11 @@ namespace Docnet | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| public static string MakeAbsolutePath(string rootPath, string toMakeAbsolute) | public static string MakeAbsolutePath(string rootPath, string toMakeAbsolute) | ||||
| { | { | ||||
| if(string.IsNullOrWhiteSpace(toMakeAbsolute)) | |||||
| if (string.IsNullOrWhiteSpace(toMakeAbsolute)) | |||||
| { | { | ||||
| return rootPath; | return rootPath; | ||||
| } | } | ||||
| if(Path.IsPathRooted(toMakeAbsolute)) | |||||
| if (Path.IsPathRooted(toMakeAbsolute)) | |||||
| { | { | ||||
| return toMakeAbsolute; | return toMakeAbsolute; | ||||
| } | } | ||||
| @@ -141,12 +204,12 @@ namespace Docnet | |||||
| public static void CreateFoldersIfRequired(string fullPath) | public static void CreateFoldersIfRequired(string fullPath) | ||||
| { | { | ||||
| string folderToCheck = Path.GetDirectoryName(fullPath); | string folderToCheck = Path.GetDirectoryName(fullPath); | ||||
| if(string.IsNullOrWhiteSpace(folderToCheck)) | |||||
| if (string.IsNullOrWhiteSpace(folderToCheck)) | |||||
| { | { | ||||
| // nothing to do, no folder to emit | // nothing to do, no folder to emit | ||||
| return; | return; | ||||
| } | } | ||||
| if(!Directory.Exists(folderToCheck)) | |||||
| if (!Directory.Exists(folderToCheck)) | |||||
| { | { | ||||
| Directory.CreateDirectory(folderToCheck); | Directory.CreateDirectory(folderToCheck); | ||||
| } | } | ||||
| @@ -163,20 +226,20 @@ namespace Docnet | |||||
| public static string MakeRelativePath(string fromPath, string toPath) | public static string MakeRelativePath(string fromPath, string toPath) | ||||
| { | { | ||||
| var fromPathToUse = fromPath; | var fromPathToUse = fromPath; | ||||
| if(string.IsNullOrEmpty(fromPathToUse)) | |||||
| if (string.IsNullOrEmpty(fromPathToUse)) | |||||
| { | { | ||||
| return string.Empty; | return string.Empty; | ||||
| } | } | ||||
| var toPathToUse = toPath; | var toPathToUse = toPath; | ||||
| if(string.IsNullOrEmpty(toPathToUse)) | |||||
| if (string.IsNullOrEmpty(toPathToUse)) | |||||
| { | { | ||||
| return string.Empty; | return string.Empty; | ||||
| } | } | ||||
| if(fromPathToUse.Last() != Path.DirectorySeparatorChar) | |||||
| if (fromPathToUse.Last() != Path.DirectorySeparatorChar) | |||||
| { | { | ||||
| fromPathToUse += Path.DirectorySeparatorChar; | fromPathToUse += Path.DirectorySeparatorChar; | ||||
| } | } | ||||
| if(toPathToUse.Last() != Path.DirectorySeparatorChar) | |||||
| if (toPathToUse.Last() != Path.DirectorySeparatorChar) | |||||
| { | { | ||||
| toPathToUse += Path.DirectorySeparatorChar; | toPathToUse += Path.DirectorySeparatorChar; | ||||
| } | } | ||||
| @@ -184,7 +247,7 @@ namespace Docnet | |||||
| var fromUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(fromPathToUse))); | var fromUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(fromPathToUse))); | ||||
| var toUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(toPathToUse))); | 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. | // path can't be made relative. | ||||
| return toPathToUse; | return toPathToUse; | ||||
| @@ -193,7 +256,7 @@ namespace Docnet | |||||
| var relativeUri = fromUri.MakeRelativeUri(toUri); | var relativeUri = fromUri.MakeRelativeUri(toUri); | ||||
| string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); | string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); | ||||
| if(toUri.Scheme.ToUpperInvariant() == "FILE") | |||||
| if (toUri.Scheme.ToUpperInvariant() == "FILE") | |||||
| { | { | ||||
| relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); | relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); | ||||
| } | } | ||||
| @@ -242,5 +305,5 @@ namespace Docnet | |||||
| } | } | ||||
| return content; | return content; | ||||
| } | } | ||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -58,24 +58,32 @@ namespace MarkdownDeep | |||||
| { | { | ||||
| HtmlTag tag = new HtmlTag("a"); | 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 | // encode url | ||||
| StringBuilder sb = m.GetStringBuilder(); | StringBuilder sb = m.GetStringBuilder(); | ||||
| @@ -83,14 +91,14 @@ namespace MarkdownDeep | |||||
| tag.attributes["href"] = sb.ToString(); | tag.attributes["href"] = sb.ToString(); | ||||
| // encode title | // encode title | ||||
| if (!String.IsNullOrEmpty(this.Title )) | |||||
| if (!String.IsNullOrEmpty(this.Title)) | |||||
| { | { | ||||
| sb.Length = 0; | sb.Length = 0; | ||||
| Utils.SmartHtmlEncodeAmpsAndAngles(sb, this.Title); | Utils.SmartHtmlEncodeAmpsAndAngles(sb, this.Title); | ||||
| tag.attributes["title"] = sb.ToString(); | tag.attributes["title"] = sb.ToString(); | ||||
| } | } | ||||
| if(specialAttributes.Any()) | |||||
| if (specialAttributes.Any()) | |||||
| { | { | ||||
| LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag); | LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag); | ||||
| } | } | ||||
| @@ -101,7 +109,7 @@ namespace MarkdownDeep | |||||
| // Render the opening tag | // Render the opening tag | ||||
| tag.RenderOpening(b); | 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>"); | b.Append("</a>"); | ||||
| } | } | ||||
| } | } | ||||
| @@ -131,7 +139,7 @@ namespace MarkdownDeep | |||||
| Utils.SmartHtmlEncodeAmpsAndAngles(sb, Title); | Utils.SmartHtmlEncodeAmpsAndAngles(sb, Title); | ||||
| tag.attributes["title"] = sb.ToString(); | tag.attributes["title"] = sb.ToString(); | ||||
| } | } | ||||
| if(specialAttributes.Any()) | |||||
| if (specialAttributes.Any()) | |||||
| { | { | ||||
| LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag); | LinkDefinition.HandleSpecialAttributes(specialAttributes, sb, tag); | ||||
| } | } | ||||
| @@ -153,9 +161,9 @@ namespace MarkdownDeep | |||||
| // Parse a link definition | // Parse a link definition | ||||
| internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode) | internal static LinkDefinition ParseLinkDefinition(StringScanner p, bool ExtraMode) | ||||
| { | { | ||||
| int savepos=p.Position; | |||||
| int savepos = p.Position; | |||||
| var l = ParseLinkDefinitionInternal(p, ExtraMode); | var l = ParseLinkDefinitionInternal(p, ExtraMode); | ||||
| if (l==null) | |||||
| if (l == null) | |||||
| p.Position = savepos; | p.Position = savepos; | ||||
| return l; | return l; | ||||
| @@ -181,7 +189,7 @@ namespace MarkdownDeep | |||||
| return null; | return null; | ||||
| // Parse the url and title | // Parse the url and title | ||||
| var link=ParseLinkTarget(p, id, ExtraMode); | |||||
| var link = ParseLinkTarget(p, id, ExtraMode); | |||||
| // and trailing whitespace | // and trailing whitespace | ||||
| p.SkipLinespace(); | p.SkipLinespace(); | ||||
| @@ -236,7 +244,7 @@ namespace MarkdownDeep | |||||
| int paren_depth = 1; | int paren_depth = 1; | ||||
| while (!p.Eol) | while (!p.Eol) | ||||
| { | { | ||||
| char ch=p.Current; | |||||
| char ch = p.Current; | |||||
| if (char.IsWhiteSpace(ch)) | if (char.IsWhiteSpace(ch)) | ||||
| break; | break; | ||||
| if (id == null) | if (id == null) | ||||
| @@ -246,7 +254,7 @@ namespace MarkdownDeep | |||||
| else if (ch == ')') | else if (ch == ')') | ||||
| { | { | ||||
| paren_depth--; | paren_depth--; | ||||
| if (paren_depth==0) | |||||
| if (paren_depth == 0) | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -275,7 +283,7 @@ namespace MarkdownDeep | |||||
| char delim; | char delim; | ||||
| switch (p.Current) | switch (p.Current) | ||||
| { | { | ||||
| case '\'': | |||||
| case '\'': | |||||
| case '\"': | case '\"': | ||||
| delim = p.Current; | delim = p.Current; | ||||
| break; | break; | ||||
| @@ -349,27 +357,27 @@ namespace MarkdownDeep | |||||
| private static void HandleSpecialAttributes(List<string> specialAttributes, StringBuilder sb, HtmlTag tag) | private static void HandleSpecialAttributes(List<string> specialAttributes, StringBuilder sb, HtmlTag tag) | ||||
| { | { | ||||
| string id = specialAttributes.FirstOrDefault(s => s.StartsWith("#")); | string id = specialAttributes.FirstOrDefault(s => s.StartsWith("#")); | ||||
| if(id != null && id.Length > 1) | |||||
| if (id != null && id.Length > 1) | |||||
| { | { | ||||
| sb.Length = 0; | sb.Length = 0; | ||||
| Utils.SmartHtmlEncodeAmpsAndAngles(sb, id.Substring(1)); | Utils.SmartHtmlEncodeAmpsAndAngles(sb, id.Substring(1)); | ||||
| tag.attributes["id"] = sb.ToString(); | tag.attributes["id"] = sb.ToString(); | ||||
| } | } | ||||
| var cssClasses = new List<string>(); | 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; | sb.Length = 0; | ||||
| Utils.SmartHtmlEncodeAmpsAndAngles(sb, cssClass.Substring(1)); | Utils.SmartHtmlEncodeAmpsAndAngles(sb, cssClass.Substring(1)); | ||||
| cssClasses.Add(sb.ToString()); | cssClasses.Add(sb.ToString()); | ||||
| } | } | ||||
| if(cssClasses.Any()) | |||||
| if (cssClasses.Any()) | |||||
| { | { | ||||
| tag.attributes["class"] = string.Join(" ", cssClasses.ToArray()); | 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('='); | var pair = nameValuePair.Split('='); | ||||
| if(pair.Length == 2) | |||||
| if (pair.Length == 2) | |||||
| { | { | ||||
| sb.Length = 0; | sb.Length = 0; | ||||
| Utils.SmartHtmlEncodeAmpsAndAngles(sb, pair[0]); | Utils.SmartHtmlEncodeAmpsAndAngles(sb, pair[0]); | ||||
| @@ -880,6 +880,14 @@ namespace MarkdownDeep | |||||
| /// </summary> | /// </summary> | ||||
| public bool ConvertLocalLinks { get; set; } | 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 | // When set, all html block level elements automatically support | ||||
| // markdown syntax within them. | // markdown syntax within them. | ||||
| // (Similar to Pandoc's handling of markdown in html) | // (Similar to Pandoc's handling of markdown in html) | ||||