@@ -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) | ||||