| @@ -216,6 +216,14 @@ namespace Docnet | |||
| } | |||
| } | |||
| public int MaxLevelInToC | |||
| { | |||
| get | |||
| { | |||
| return _configData.MaxLevelInToC ?? 2; | |||
| } | |||
| } | |||
| public string ThemeName | |||
| { | |||
| get | |||
| @@ -43,8 +43,9 @@ namespace Docnet | |||
| /// </summary> | |||
| /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | |||
| /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <returns></returns> | |||
| string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); | |||
| string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel); | |||
| /// <summary> | |||
| /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | |||
| /// </summary> | |||
| @@ -73,11 +73,12 @@ namespace Docnet | |||
| /// <summary> | |||
| /// Creates the ToC HTML for the element reached by the elements in this path. All containers in this path are expanded, all elements inside these containers which | |||
| /// aren't, are not expanded. | |||
| /// aren't, are not expanded. | |||
| /// </summary> | |||
| /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <returns></returns> | |||
| public string CreateToCHTML(string relativePathToRoot) | |||
| public string CreateToCHTML(string relativePathToRoot, int maxLevel) | |||
| { | |||
| // the root container is the bottom element of this path. We use that container to build the root and navigate any node open along the navigated path. | |||
| var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel; | |||
| @@ -86,7 +87,7 @@ namespace Docnet | |||
| // no root container, no TOC | |||
| return string.Empty; | |||
| } | |||
| return rootContainer.GenerateToCFragment(this, relativePathToRoot); | |||
| return rootContainer.GenerateToCFragment(this, relativePathToRoot, maxLevel); | |||
| } | |||
| } | |||
| } | |||
| @@ -43,8 +43,9 @@ namespace Docnet | |||
| /// </summary> | |||
| /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | |||
| /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <returns></returns> | |||
| public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); | |||
| public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel); | |||
| /// <summary> | |||
| /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. | |||
| /// </summary> | |||
| @@ -106,8 +106,9 @@ namespace Docnet | |||
| /// </summary> | |||
| /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | |||
| /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <returns></returns> | |||
| public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
| public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||
| { | |||
| var fragments = new List<string>(); | |||
| if(!this.IsRoot) | |||
| @@ -138,7 +139,7 @@ namespace Docnet | |||
| { | |||
| if(this.IsRoot) | |||
| { | |||
| fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot)); | |||
| fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel)); | |||
| } | |||
| else | |||
| { | |||
| @@ -149,7 +150,7 @@ namespace Docnet | |||
| // then the elements in the container. Index elements are skipped here. | |||
| foreach(var element in this.Value) | |||
| { | |||
| fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot)); | |||
| fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel)); | |||
| } | |||
| fragments.Add("</ul>"); | |||
| } | |||
| @@ -27,6 +27,7 @@ using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Web; | |||
| using MarkdownDeep; | |||
| namespace Docnet | |||
| { | |||
| @@ -34,13 +35,14 @@ namespace Docnet | |||
| { | |||
| #region Members | |||
| private string _targetURLForHTML; | |||
| private List<Tuple<string, string>> _relativeH2LinksOnPage; // first element in Tuple is anchor name, second is name for ToC. | |||
| private readonly List<Heading> _relativeLinksOnPage; // first element in Tuple is anchor name, second is name for ToC. | |||
| #endregion | |||
| public SimpleNavigationElement() | |||
| { | |||
| _relativeH2LinksOnPage = new List<Tuple<string, string>>(); | |||
| _relativeLinksOnPage = new List<Heading>(); | |||
| } | |||
| @@ -52,50 +54,50 @@ namespace Docnet | |||
| public override void GenerateOutput(Config activeConfig, NavigatedPath activePath) | |||
| { | |||
| // if we're the __index element, we're not pushing ourselves on the path, as we're representing the container we're in, which is already on the path. | |||
| if(!this.IsIndexElement) | |||
| if (!this.IsIndexElement) | |||
| { | |||
| activePath.Push(this); | |||
| } | |||
| _relativeH2LinksOnPage.Clear(); | |||
| _relativeLinksOnPage.Clear(); | |||
| var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); | |||
| var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.TargetURL); | |||
| var sb = new StringBuilder(activeConfig.PageTemplateContents.Length + 2048); | |||
| var content = string.Empty; | |||
| this.MarkdownFromFile = string.Empty; | |||
| var relativePathToRoot = Utils.MakeRelativePathForUri(Path.GetDirectoryName(destinationFile), activeConfig.Destination); | |||
| if(File.Exists(sourceFile)) | |||
| if (File.Exists(sourceFile)) | |||
| { | |||
| 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, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); | |||
| content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||
| } | |||
| else | |||
| { | |||
| // if we're not the index element, the file is missing and potentially it's an error in the config page. | |||
| // Otherwise we can simply assume we are a missing index page and we'll generate default markdown so the user has something to look at. | |||
| if(this.IsIndexElement) | |||
| if (this.IsIndexElement) | |||
| { | |||
| // replace with default markdown snippet. This is the name of our container and links to the elements in that container as we are the index page that's not | |||
| // specified / existend. | |||
| var defaultMarkdown = new StringBuilder(); | |||
| defaultMarkdown.AppendFormat("# {0}{1}{1}", this.ParentContainer.Name, Environment.NewLine); | |||
| defaultMarkdown.AppendFormat("Please select one of the topics in this section:{0}{0}", Environment.NewLine); | |||
| foreach(var sibling in this.ParentContainer.Value) | |||
| foreach (var sibling in this.ParentContainer.Value) | |||
| { | |||
| if(sibling == this) | |||
| if (sibling == this) | |||
| { | |||
| continue; | |||
| } | |||
| defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, HttpUtility.UrlPathEncode(sibling.TargetURL), Environment.NewLine); | |||
| } | |||
| defaultMarkdown.Append(Environment.NewLine); | |||
| content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); | |||
| content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks); | |||
| } | |||
| else | |||
| { | |||
| // target not found. See if there's a content producer func to produce html for us. If not, we can only conclude an error in the config file. | |||
| if(this.ContentProducerFunc == null) | |||
| if (this.ContentProducerFunc == null) | |||
| { | |||
| throw new FileNotFoundException(string.Format("The specified markdown file '{0}' couldn't be found. Aborting", sourceFile)); | |||
| } | |||
| @@ -107,17 +109,17 @@ namespace Docnet | |||
| sb.Replace("{{Footer}}", activeConfig.Footer); | |||
| sb.Replace("{{TopicTitle}}", this.Name); | |||
| sb.Replace("{{Path}}", relativePathToRoot); | |||
| sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/')); | |||
| sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); | |||
| sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot)); | |||
| sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot)); | |||
| sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/')); | |||
| sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); | |||
| sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot)); | |||
| sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, activeConfig.MaxLevelInToC)); | |||
| sb.Replace("{{ExtraScript}}", (this.ExtraScriptProducerFunc == null) ? string.Empty : this.ExtraScriptProducerFunc(this)); | |||
| // the last action has to be replacing the content marker, so markers in the content which we have in the template as well aren't replaced | |||
| sb.Replace("{{Content}}", content); | |||
| Utils.CreateFoldersIfRequired(destinationFile); | |||
| File.WriteAllText(destinationFile, sb.ToString()); | |||
| if(!this.IsIndexElement) | |||
| if (!this.IsIndexElement) | |||
| { | |||
| activePath.Pop(); | |||
| } | |||
| @@ -133,7 +135,7 @@ namespace Docnet | |||
| { | |||
| activePath.Push(this); | |||
| // simply convert ourselves into an entry if we're not an index | |||
| if(!this.IsIndexElement) | |||
| if (!this.IsIndexElement) | |||
| { | |||
| var toAdd = new SearchIndexEntry(); | |||
| toAdd.Fill(this.MarkdownFromFile, this.TargetURL, this.Name, activePath); | |||
| @@ -148,16 +150,17 @@ namespace Docnet | |||
| /// </summary> | |||
| /// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param> | |||
| /// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <returns></returns> | |||
| public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
| public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||
| { | |||
| // index elements are rendered in the parent container. | |||
| if(this.IsIndexElement) | |||
| if (this.IsIndexElement) | |||
| { | |||
| return string.Empty; | |||
| } | |||
| return PerformGenerateToCFragment(navigatedPath, relativePathToRoot); | |||
| return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, null); | |||
| } | |||
| @@ -167,15 +170,17 @@ namespace Docnet | |||
| /// </summary> | |||
| /// <param name="navigatedPath">The navigated path.</param> | |||
| /// <param name="relativePathToRoot">The relative path to root.</param> | |||
| /// <param name="maxLevel">The maximum level.</param> | |||
| /// <param name="parentHeading">The parent heading.</param> | |||
| /// <returns></returns> | |||
| public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) | |||
| public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel) | |||
| { | |||
| // we can't navigate deeper from here. If we are the element being navigated to, we are the current and will have to emit any additional relative URLs too. | |||
| bool isCurrent = navigatedPath.Contains(this); | |||
| var fragments = new List<string>(); | |||
| var liClass = "tocentry"; | |||
| var aClass = string.Empty; | |||
| if(isCurrent) | |||
| if (isCurrent) | |||
| { | |||
| liClass = "tocentry current"; | |||
| aClass = "current"; | |||
| @@ -186,20 +191,46 @@ namespace Docnet | |||
| relativePathToRoot, | |||
| HttpUtility.UrlPathEncode(this.TargetURL), | |||
| this.Name)); | |||
| if(isCurrent && _relativeH2LinksOnPage.Any()) | |||
| if (isCurrent) | |||
| { | |||
| // generate relative links | |||
| fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | |||
| foreach(var p in _relativeH2LinksOnPage) | |||
| var content = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, null); | |||
| if (!string.IsNullOrWhiteSpace(content)) | |||
| { | |||
| fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", p.Item1, p.Item2)); | |||
| fragments.Add(content); | |||
| } | |||
| fragments.Add("</ul>"); | |||
| } | |||
| else | |||
| fragments.Add("</li>"); | |||
| return string.Join(Environment.NewLine, fragments.ToArray()); | |||
| } | |||
| private string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel, Heading parentHeading) | |||
| { | |||
| var fragments = new List<string>(); | |||
| var headings = (parentHeading != null) ? parentHeading.Children : _relativeLinksOnPage; | |||
| var includedHeadings = headings.Where(x => x.Level > 1 && x.Level <= maxLevel).ToList(); | |||
| if (includedHeadings.Count > 0) | |||
| { | |||
| fragments.Add("</li>"); | |||
| fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative")); | |||
| // generate relative links | |||
| foreach (var heading in includedHeadings) | |||
| { | |||
| fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", heading.Id, heading.Name)); | |||
| var headingContent = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, heading); | |||
| if (!string.IsNullOrWhiteSpace(headingContent)) | |||
| { | |||
| fragments.Add(headingContent); | |||
| } | |||
| } | |||
| fragments.Add("</ul>"); | |||
| } | |||
| return string.Join(Environment.NewLine, fragments.ToArray()); | |||
| } | |||
| @@ -209,12 +240,12 @@ namespace Docnet | |||
| { | |||
| get | |||
| { | |||
| if(_targetURLForHTML==null) | |||
| if (_targetURLForHTML == null) | |||
| { | |||
| _targetURLForHTML = (this.Value ?? string.Empty); | |||
| if(_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) | |||
| if (_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) | |||
| { | |||
| _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length-3) + ".htm"; | |||
| _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - 3) + ".htm"; | |||
| } | |||
| _targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); | |||
| } | |||
| @@ -27,6 +27,7 @@ using System.Linq; | |||
| using System.Text; | |||
| using System.Text.RegularExpressions; | |||
| using System.Threading.Tasks; | |||
| using MarkdownDeep; | |||
| namespace Docnet | |||
| { | |||
| @@ -50,7 +51,7 @@ namespace Docnet | |||
| /// <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<Tuple<string, string>> createdAnchorCollector, bool convertLocalLinks) | |||
| List<Heading> createdAnchorCollector, bool convertLocalLinks) | |||
| { | |||
| var parser = new MarkdownDeep.Markdown | |||
| { | |||
| @@ -67,7 +68,9 @@ namespace Docnet | |||
| }; | |||
| var toReturn = parser.Transform(toConvert); | |||
| createdAnchorCollector.AddRange(parser.CreatedH2IdCollector); | |||
| createdAnchorCollector.AddRange(parser.Headings.ConvertToHierarchy()); | |||
| return toReturn; | |||
| } | |||
| @@ -193,14 +193,19 @@ namespace MarkdownDeep | |||
| { | |||
| b.Append("<" + BlockType.ToString() + ">"); | |||
| } | |||
| if(m.DocNetMode && BlockType == BlockType.h2 && !string.IsNullOrWhiteSpace(id)) | |||
| if(m.DocNetMode && !string.IsNullOrWhiteSpace(id)) | |||
| { | |||
| // collect h2 id + text in collector | |||
| var h2ContentSb = new StringBuilder(); | |||
| m.SpanFormatter.Format(h2ContentSb, Buf, ContentStart, ContentLen); | |||
| var h2ContentAsString = h2ContentSb.ToString(); | |||
| b.Append(h2ContentAsString); | |||
| m.CreatedH2IdCollector.Add(new Tuple<string, string>(id, h2ContentAsString)); | |||
| // collect id + text in collector | |||
| var headerContentStringBuilder = new StringBuilder(); | |||
| m.SpanFormatter.Format(headerContentStringBuilder, Buf, ContentStart, ContentLen); | |||
| var headerContentAsString = headerContentStringBuilder.ToString(); | |||
| b.Append(headerContentAsString); | |||
| m.Headings.Add(new Heading | |||
| { | |||
| Level = (int)BlockType, | |||
| Id = id, | |||
| Name = headerContentAsString | |||
| }); | |||
| } | |||
| else | |||
| { | |||
| @@ -0,0 +1,59 @@ | |||
| using System.Collections.Generic; | |||
| namespace MarkdownDeep | |||
| { | |||
| public static class Extensions | |||
| { | |||
| public static List<Heading> ConvertToHierarchy(this List<Heading> headings) | |||
| { | |||
| var hierarchy = new List<Heading>(); | |||
| for (var i = 0; i < headings.Count; i++) | |||
| { | |||
| if (i > 0) | |||
| { | |||
| var previousHeading = headings[i - 1]; | |||
| var currentHeading = headings[i]; | |||
| SetParentForHeading(previousHeading, currentHeading); | |||
| var parent = currentHeading.Parent; | |||
| if (parent == null) | |||
| { | |||
| hierarchy.Add(currentHeading); | |||
| } | |||
| else | |||
| { | |||
| parent.Children.Add(currentHeading); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| hierarchy.Add(headings[i]); | |||
| } | |||
| } | |||
| return hierarchy; | |||
| } | |||
| private static void SetParentForHeading(Heading previousHeading, Heading headingToAdd) | |||
| { | |||
| if (previousHeading.Level == headingToAdd.Level) | |||
| { | |||
| headingToAdd.Parent = previousHeading.Parent; | |||
| } | |||
| else if (previousHeading.Level < headingToAdd.Level) | |||
| { | |||
| headingToAdd.Parent = previousHeading; | |||
| } | |||
| else if (previousHeading.Level > headingToAdd.Level) | |||
| { | |||
| var previousHeadingParent = previousHeading.Parent; | |||
| if (previousHeadingParent != null) | |||
| { | |||
| SetParentForHeading(previousHeadingParent, headingToAdd); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| namespace MarkdownDeep | |||
| { | |||
| public class Heading | |||
| { | |||
| public Heading() | |||
| { | |||
| Children = new List<Heading>(); | |||
| } | |||
| public Heading Parent { get; set; } | |||
| public List<Heading> Children { get; private set; } | |||
| public int Level { get; set; } | |||
| public string Id { get; set; } | |||
| public string Name { get; set; } | |||
| public override string ToString() | |||
| { | |||
| var stringBuilder = new StringBuilder(); | |||
| for (var i = 0; i < Level; i++) | |||
| { | |||
| stringBuilder.Append("#"); | |||
| } | |||
| stringBuilder.AppendLine($"{Id} - {Name}"); | |||
| foreach (var child in Children) | |||
| { | |||
| stringBuilder.AppendLine(child.ToString()); | |||
| } | |||
| var value = stringBuilder.ToString(); | |||
| return value; | |||
| } | |||
| } | |||
| } | |||
| @@ -57,7 +57,9 @@ namespace MarkdownDeep | |||
| m_Footnotes = new Dictionary<string, Block>(); | |||
| m_UsedFootnotes = new List<Block>(); | |||
| m_UsedHeaderIDs = new Dictionary<string, bool>(); | |||
| this.CreatedH2IdCollector = new List<Tuple<string, string>>(); | |||
| this.Headings = new List<Heading>(); | |||
| _tabIdCounter = 0; | |||
| } | |||
| @@ -951,13 +953,11 @@ namespace MarkdownDeep | |||
| set; | |||
| } | |||
| /// <summary> | |||
| /// Collector for the created id's for H2 headers. First element in Tuple is id name, second is name for ToC (the text for H2). Id's are generated | |||
| /// Collector for the created id's for headers. First element in Tuple is id name, second is name for ToC (the text for header). Id's are generated | |||
| /// by the parser and use pandoc algorithm, as AutoHeadingId's is switched on. Only in use if DocNetMode is set to true | |||
| /// </summary> | |||
| public List<Tuple<string, string>> CreatedH2IdCollector { get; private set; } | |||
| public List<Heading> Headings { get; private set; } | |||
| // Set the html class for the footnotes div | |||
| // (defaults to "footnotes") | |||
| @@ -77,7 +77,9 @@ | |||
| <ItemGroup> | |||
| <Compile Include="Abbreviation.cs" /> | |||
| <Compile Include="BlockProcessor.cs" /> | |||
| <Compile Include="Extensions.cs" /> | |||
| <Compile Include="FootnoteReference.cs" /> | |||
| <Compile Include="Heading.cs" /> | |||
| <Compile Include="HtmlTag.cs" /> | |||
| <Compile Include="LinkDefinition.cs" /> | |||
| <Compile Include="LinkInfo.cs" /> | |||
| @@ -0,0 +1,65 @@ | |||
| using System.Collections.Generic; | |||
| using MarkdownDeep; | |||
| using NUnit.Framework; | |||
| namespace MarkdownDeepTests | |||
| { | |||
| [TestFixture] | |||
| public class ExtensionsTests | |||
| { | |||
| [TestCase] | |||
| public void ConvertsHeadingsHierarchy() | |||
| { | |||
| var headings = new List<Heading>(); | |||
| headings.Add(new Heading { Level = 1, Name = "1" }); | |||
| headings.Add(new Heading { Level = 2, Name = "1.1" }); | |||
| headings.Add(new Heading { Level = 3, Name = "1.1.1" }); | |||
| headings.Add(new Heading { Level = 2, Name = "1.2" }); | |||
| headings.Add(new Heading { Level = 4, Name = "1.2.1.1" }); | |||
| headings.Add(new Heading { Level = 2, Name = "1.3" }); | |||
| headings.Add(new Heading { Level = 1, Name = "2" }); | |||
| headings.Add(new Heading { Level = 3, Name = "2.1.1" }); | |||
| headings.Add(new Heading { Level = 2, Name = "2.2" }); | |||
| var hierarchy = headings.ConvertToHierarchy(); | |||
| Assert.AreEqual(2, hierarchy.Count); | |||
| var heading1 = hierarchy[0]; | |||
| Assert.AreEqual("1", heading1.Name); | |||
| Assert.AreEqual(3, heading1.Children.Count); | |||
| var heading1_1 = heading1.Children[0]; | |||
| Assert.AreEqual("1.1", heading1_1.Name); | |||
| Assert.AreEqual(1, heading1_1.Children.Count); | |||
| var heading1_1_1 = heading1_1.Children[0]; | |||
| Assert.AreEqual("1.1.1", heading1_1_1.Name); | |||
| Assert.AreEqual(0, heading1_1_1.Children.Count); | |||
| var heading1_2 = heading1.Children[1]; | |||
| Assert.AreEqual("1.2", heading1_2.Name); | |||
| Assert.AreEqual(1, heading1_2.Children.Count); | |||
| var heading1_2_1_1 = heading1_2.Children[0]; | |||
| Assert.AreEqual("1.2.1.1", heading1_2_1_1.Name); | |||
| Assert.AreEqual(0, heading1_2_1_1.Children.Count); | |||
| var heading1_3 = heading1.Children[2]; | |||
| Assert.AreEqual("1.3", heading1_3.Name); | |||
| Assert.AreEqual(0, heading1_3.Children.Count); | |||
| var heading2 = hierarchy[1]; | |||
| Assert.AreEqual("2", heading2.Name); | |||
| Assert.AreEqual(2, heading2.Children.Count); | |||
| var heading2_1_1 = heading2.Children[0]; | |||
| Assert.AreEqual("2.1.1", heading2_1_1.Name); | |||
| Assert.AreEqual(0, heading2_1_1.Children.Count); | |||
| var heading2_2 = heading2.Children[1]; | |||
| Assert.AreEqual("2.2", heading2_2.Name); | |||
| Assert.AreEqual(0, heading2_2.Children.Count); | |||
| } | |||
| } | |||
| } | |||
| @@ -75,6 +75,7 @@ | |||
| <Compile Include="AutoLinkTests.cs" /> | |||
| <Compile Include="AutoHeaderIDTests.cs" /> | |||
| <Compile Include="DocNetMode.cs" /> | |||
| <Compile Include="ExtensionsTests.cs" /> | |||
| <Compile Include="GithubMode.cs" /> | |||
| <Compile Include="LocalLinkTests.cs" /> | |||
| <Compile Include="TableSpecTests.cs" /> | |||