diff --git a/src/DocNet/Docnet.csproj b/src/DocNet/Docnet.csproj
index f083b6f..66d9900 100644
--- a/src/DocNet/Docnet.csproj
+++ b/src/DocNet/Docnet.csproj
@@ -33,6 +33,10 @@
4
+
+ ..\..\packages\Handlebars.Net.1.6.4\lib\Handlebars.dll
+ True
+
..\..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll
True
@@ -80,4 +84,4 @@
-->
-
+
\ No newline at end of file
diff --git a/src/DocNet/SimpleNavigationElement.cs b/src/DocNet/SimpleNavigationElement.cs
index 53ba3fa..0a24ff0 100644
--- a/src/DocNet/SimpleNavigationElement.cs
+++ b/src/DocNet/SimpleNavigationElement.cs
@@ -67,7 +67,7 @@ public override void GenerateOutput(Config activeConfig, NavigatedPath activePat
{
this.MarkdownFromFile = File.ReadAllText(sourceFile);
// Check if the content contains @@include tag
- content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder));
+ content = Utils.PartialProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder));
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, _relativeH2LinksOnPage);
}
else
@@ -212,7 +212,7 @@ public override string TargetURL
_targetURLForHTML = (this.Value ?? string.Empty);
if(_targetURLForHTML.ToLowerInvariant().EndsWith(".md"))
{
- _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length-3) + ".htm";
+ _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length-3) + ".html";
}
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/");
}
diff --git a/src/DocNet/Utils.cs b/src/DocNet/Utils.cs
index 23f9a94..1326ce4 100644
--- a/src/DocNet/Utils.cs
+++ b/src/DocNet/Utils.cs
@@ -20,6 +20,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////////////////////
+using HandlebarsDotNet;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -30,209 +32,272 @@
namespace Docnet
{
- public static class Utils
- {
- #region Statics
- ///
- /// 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 created anchor collector, for ToC sublinks for H2 headers.
- ///
- public static string ConvertMarkdownToHtml(string toConvert, string documentPath, string siteRoot, List> createdAnchorCollector)
- {
- var parser = new MarkdownDeep.Markdown
- {
- ExtraMode = true,
- GitHubCodeBlocks = true,
- AutoHeadingIDs = true,
- NewWindowForExternalLinks = true,
- DocNetMode = true,
- DocumentLocation = documentPath,
- DocumentRoot = siteRoot,
- HtmlClassTitledImages = "figure",
- };
-
- var toReturn = parser.Transform(toConvert);
- createdAnchorCollector.AddRange(parser.CreatedH2IdCollector);
- return toReturn;
- }
-
-
- ///
- /// Copies directories and files, eventually recursively. From MSDN.
- ///
- /// Name of the source dir.
- /// Name of the dest dir.
- /// if set to true it will recursively copy files/folders.
- public static void DirectoryCopy(string sourceFolderName, string destinationFolderName, bool copySubFolders)
- {
- // Get the subdirectories for the specified directory.
- DirectoryInfo sourceFolder = new DirectoryInfo(sourceFolderName);
- 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))
- {
- Directory.CreateDirectory(destinationFolderName);
- }
-
- // Get the files in the directory and copy them to the new location.
- foreach(FileInfo file in sourceFolder.GetFiles())
- {
- file.CopyTo(Path.Combine(destinationFolderName, file.Name), true);
- }
- if(copySubFolders)
- {
- foreach(DirectoryInfo subFolder in sourceFoldersToCopy)
- {
- Utils.DirectoryCopy(subFolder.FullName, Path.Combine(destinationFolderName, subFolder.Name), copySubFolders);
- }
- }
- }
-
-
-
- ///
- /// Makes toMakeAbsolute an absolute path, if it's not already a rooted path. If it's not a rooted path it's assumed it's relative to rootPath and is combined with that.
- ///
- /// The root path.
- /// To make absolute.
- ///
- public static string MakeAbsolutePath(string rootPath, string toMakeAbsolute)
- {
- if(string.IsNullOrWhiteSpace(toMakeAbsolute))
- {
- return rootPath;
- }
- if(Path.IsPathRooted(toMakeAbsolute))
- {
- return toMakeAbsolute;
- }
- var rawToReturn = Path.Combine(rootPath, toMakeAbsolute);
- return Path.GetFullPath(rawToReturn).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
- }
-
-
- ///
- /// Creates the folders in the path specified if they don't exist, recursively
- ///
- /// The full path.
- public static void CreateFoldersIfRequired(string fullPath)
- {
- string folderToCheck = Path.GetDirectoryName(fullPath);
- if(string.IsNullOrWhiteSpace(folderToCheck))
- {
- // nothing to do, no folder to emit
- return;
- }
- if(!Directory.Exists(folderToCheck))
- {
- Directory.CreateDirectory(folderToCheck);
- }
- }
-
-
- ///
- /// Creates a relative path to get from fromPath to toPath. If one of them is empty, the emptystring is returned. If there's no common path, toPath is returned.
- ///
- /// From path.
- /// To path.
- ///
- /// Only works with file paths, which is ok, as it's used to create the {{Path}} macro.
- public static string MakeRelativePath(string fromPath, string toPath)
- {
- var fromPathToUse = fromPath;
- if(string.IsNullOrEmpty(fromPathToUse))
- {
- return string.Empty;
- }
- var toPathToUse = toPath;
- if(string.IsNullOrEmpty(toPathToUse))
- {
- return string.Empty;
- }
- if(fromPathToUse.Last() != Path.DirectorySeparatorChar)
- {
- fromPathToUse += Path.DirectorySeparatorChar;
- }
- if(toPathToUse.Last() != Path.DirectorySeparatorChar)
- {
- toPathToUse += Path.DirectorySeparatorChar;
- }
-
- var fromUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(fromPathToUse)));
- var toUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(toPathToUse)));
-
- if(fromUri.Scheme != toUri.Scheme)
- {
- // path can't be made relative.
- return toPathToUse;
- }
-
- var relativeUri = fromUri.MakeRelativeUri(toUri);
- string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
-
- if(toUri.Scheme.ToUpperInvariant() == "FILE")
- {
- relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
- }
-
- return relativePath;
- }
-
-
- ///
- /// As but it also converts '\' to '/'.
- ///
- /// From path.
- /// To path.
- ///
- /// Only works with file paths, which is ok, as it's used to create the {{Path}} macro.
- public static string MakeRelativePathForUri(string fromPath, string toPath)
- {
- return Utils.MakeRelativePath(fromPath, toPath).Replace(@"\", @"/");
- }
-
-
- ///
- /// Process the input for @@include tags and embeds the included content
- /// into the output.
- ///
- /// content to be scanned for include tags
- /// Directory containing the include files (absolute folder)
- /// String with @@include replaced with the actual content from the partial.
- public static string IncludeProcessor(String content, string includeFolder)
- {
- Match m = includeRegex.Match(content);
- while (m.Success)
- {
- if (m.Groups.Count > 1)
- {
- string tagToReplace = m.Groups[0].Value;
- string fileName = m.Groups[1].Value;
- fileName = fileName.Replace("\"", "");
- string filePath = Path.Combine(includeFolder, fileName);
- if (File.Exists(filePath))
- {
- content = content.Replace(tagToReplace, File.ReadAllText(filePath));
- }
- }
- m = m.NextMatch();
- }
- return content;
- }
+ public static class Utils
+ {
+ #region Statics
+ ///
+ /// Regex expression used to parse @@include(filename.html) tag.
+ ///
+ private static Regex includeRegex = new Regex(@"@@([^\(]+)\(([^ \)]+)\)|(```[\s\S]+?```)", 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 created anchor collector, for ToC sublinks for H2 headers.
+ ///
+ public static string ConvertMarkdownToHtml(string toConvert, string documentPath, string siteRoot, List> createdAnchorCollector)
+ {
+ var parser = new MarkdownDeep.Markdown
+ {
+ ExtraMode = true,
+ GitHubCodeBlocks = true,
+ AutoHeadingIDs = true,
+ NewWindowForExternalLinks = true,
+ DocNetMode = true,
+ DocumentLocation = documentPath,
+ DocumentRoot = siteRoot,
+ HtmlClassTitledImages = "figure",
+ };
+
+ var toReturn = parser.Transform(toConvert);
+ createdAnchorCollector.AddRange(parser.CreatedH2IdCollector);
+ return toReturn;
+ }
+
+
+ ///
+ /// Copies directories and files, eventually recursively. From MSDN.
+ ///
+ /// Name of the source dir.
+ /// Name of the dest dir.
+ /// if set to true it will recursively copy files/folders.
+ public static void DirectoryCopy(string sourceFolderName, string destinationFolderName, bool copySubFolders)
+ {
+ // Get the subdirectories for the specified directory.
+ DirectoryInfo sourceFolder = new DirectoryInfo(sourceFolderName);
+ 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))
+ {
+ Directory.CreateDirectory(destinationFolderName);
+ }
+
+ // Get the files in the directory and copy them to the new location.
+ foreach (FileInfo file in sourceFolder.GetFiles())
+ {
+ file.CopyTo(Path.Combine(destinationFolderName, file.Name), true);
+ }
+ if (copySubFolders)
+ {
+ foreach (DirectoryInfo subFolder in sourceFoldersToCopy)
+ {
+ Utils.DirectoryCopy(subFolder.FullName, Path.Combine(destinationFolderName, subFolder.Name), copySubFolders);
+ }
+ }
+ }
+
+
+
+ ///
+ /// Makes toMakeAbsolute an absolute path, if it's not already a rooted path. If it's not a rooted path it's assumed it's relative to rootPath and is combined with that.
+ ///
+ /// The root path.
+ /// To make absolute.
+ ///
+ public static string MakeAbsolutePath(string rootPath, string toMakeAbsolute)
+ {
+ if (string.IsNullOrWhiteSpace(toMakeAbsolute))
+ {
+ return rootPath;
+ }
+ if (Path.IsPathRooted(toMakeAbsolute))
+ {
+ return toMakeAbsolute;
+ }
+ var rawToReturn = Path.Combine(rootPath, toMakeAbsolute);
+ return Path.GetFullPath(rawToReturn).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+ }
+
+
+ ///
+ /// Creates the folders in the path specified if they don't exist, recursively
+ ///
+ /// The full path.
+ public static void CreateFoldersIfRequired(string fullPath)
+ {
+ string folderToCheck = Path.GetDirectoryName(fullPath);
+ if (string.IsNullOrWhiteSpace(folderToCheck))
+ {
+ // nothing to do, no folder to emit
+ return;
+ }
+ if (!Directory.Exists(folderToCheck))
+ {
+ Directory.CreateDirectory(folderToCheck);
+ }
+ }
+
+
+ ///
+ /// Creates a relative path to get from fromPath to toPath. If one of them is empty, the emptystring is returned. If there's no common path, toPath is returned.
+ ///
+ /// From path.
+ /// To path.
+ ///
+ /// Only works with file paths, which is ok, as it's used to create the {{Path}} macro.
+ public static string MakeRelativePath(string fromPath, string toPath)
+ {
+ var fromPathToUse = fromPath;
+ if (string.IsNullOrEmpty(fromPathToUse))
+ {
+ return string.Empty;
+ }
+ var toPathToUse = toPath;
+ if (string.IsNullOrEmpty(toPathToUse))
+ {
+ return string.Empty;
+ }
+ if (fromPathToUse.Last() != Path.DirectorySeparatorChar)
+ {
+ fromPathToUse += Path.DirectorySeparatorChar;
+ }
+ if (toPathToUse.Last() != Path.DirectorySeparatorChar)
+ {
+ toPathToUse += Path.DirectorySeparatorChar;
+ }
+
+ var fromUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(fromPathToUse)));
+ var toUri = new Uri(Uri.UnescapeDataString(Path.GetFullPath(toPathToUse)));
+
+ if (fromUri.Scheme != toUri.Scheme)
+ {
+ // path can't be made relative.
+ return toPathToUse;
+ }
+
+ var relativeUri = fromUri.MakeRelativeUri(toUri);
+ string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
+
+ if (toUri.Scheme.ToUpperInvariant() == "FILE")
+ {
+ relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+ }
+
+ return relativePath;
+ }
+
+
+ ///
+ /// As but it also converts '\' to '/'.
+ ///
+ /// From path.
+ /// To path.
+ ///
+ /// Only works with file paths, which is ok, as it's used to create the {{Path}} macro.
+ public static string MakeRelativePathForUri(string fromPath, string toPath)
+ {
+ return Utils.MakeRelativePath(fromPath, toPath).Replace(@"\", @"/");
+ }
+
+ ///
+ /// Process the function the begins with @@ tag.
+ ///
+ /// The string that contains the @@ tag
+ /// The folder containing the partials and actual data files
+ /// String with @@ tag replaced with the actual content from the function.
+ public static string PartialProcessor(string content, string includeFolder)
+ {
+ return includeRegex.Replace(content,
+ new MatchEvaluator(match => PartialSelector(includeFolder, match)),
+ content.Split(' ').Length);
+ }
+
+ ///
+ /// Selects which function to execute (e.g. include, render, etc.). This is the string right after @@
+ ///
+ /// The folder containing the partials and actual data files
+ /// The Regex that matched the @@ tag
+ /// String with @@ tag replaced with the actual content from the function.
+ public static string PartialSelector(string includeFolder, Match match)
+ {
+ if (match.Groups.Count >= 2 && match.Groups[0].Value.StartsWith("@@"))
+ {
+ var partialName = match.Groups[1].Value.ToLowerInvariant();
+ var functionParams = match.Groups[2].Value;
+
+ switch (partialName)
+ {
+ case "include": return IncludeProcessor(functionParams, includeFolder);
+ case "render": return RenderProcessor(functionParams, includeFolder);
+ default: return "Error: Couldn't find function named " + partialName;
+ }
+ }
+
+ // Return the original string if we don't have an include match
+ return match.Groups[0].Value;
+ }
+
+ ///
+ /// Gets the contents of the file in the specified folder
+ ///
+ /// Name of the file
+ /// Folder to search in
+ /// File contents as a string
+ public static string GetFileContents(string fileName, string includesFolder)
+ {
+ string filePath = Path.Combine(includesFolder, fileName.Replace("\"", ""));
+ if (File.Exists(filePath))
+ return File.ReadAllText(filePath);
+ else
+ return "Error: File not found - " + filePath;
+ }
+
+ ///
+ /// Process the input for @@include tags and embeds the included content
+ /// into the output.
+ ///
+ /// content to be scanned for include tags
+ /// Directory containing the include files (absolute folder)
+ /// String with @@include replaced with the actual content from the partial.
+ public static string IncludeProcessor(String content, string includeFolder)
+ {
+ return GetFileContents(content, includeFolder);
+ }
+
+ ///
+ /// Process the input for @@render tags and executes the specified partial (parameter number 1)
+ /// using Handlebars against the given data/json file (parameter number 2)
+ ///
+ /// Comma separated parameters of the @@render function
+ /// Directory containing the include files (absolute folder)
+ /// String with @@render replaced with the actual content from the partial.
+ public static string RenderProcessor(string content, string includeFolder)
+ {
+ var parameters = content.Trim().Split(',');
+ if (parameters.Length < 2) return "Error: expected at least 2 parameters";
+
+ var partialContents = GetFileContents(parameters[0], includeFolder);
+ var dataContents = GetFileContents(parameters[1], includeFolder);
+
+ try
+ {
+ var dataJson = JsonConvert.DeserializeObject(dataContents);
+ var hbTemplate = Handlebars.Compile(partialContents);
+ return hbTemplate(dataJson);
+ }
+ catch (Exception e)
+ {
+ return $"Error: Couldn't deserialize content of file {parameters[1]}: {e.Message}";
+ }
+ }
}
}
diff --git a/src/DocNet/packages.config b/src/DocNet/packages.config
index d18d9b1..dfb35cd 100644
--- a/src/DocNet/packages.config
+++ b/src/DocNet/packages.config
@@ -1,4 +1,5 @@
+
\ No newline at end of file