private OutlineNode GenerateOutlineTree(HtmlNode sectioningContentNode, OutlineNode parentOutlineNode) { OutlineNode outlineNode = null; foreach (HtmlNode childNode in sectioningContentNode.ChildNodes) { if (childNode.Name == "header") { HtmlNode headingElement = childNode.SelectSingleNode("*"); int level = headingElement.Name[1] - 48; // http://www.asciitable.com/ outlineNode = new OutlineNode { Content = headingElement.InnerText, Level = level, Href = "#" + sectioningContentNode.Id }; parentOutlineNode.Children.Add(outlineNode); } // Intentionally skips sub trees since they do not contribute to the document's outline. Sub trees are children of // sectioning content roots other than section and article. if (childNode.Name == "section" && outlineNode?.Level < 3) // Don't include h4s, h5s and h6s { GenerateOutlineTree(childNode, outlineNode); } } return(null); }
private void GenerateOutlineNodes(HtmlNode ulElement, OutlineNode outlineNode, HtmlDocument outlineHtmlDoc) { foreach (OutlineNode childOutlineNode in outlineNode.Children) { HtmlNode liElement = outlineHtmlDoc.CreateElement("li"); HtmlNode aElement = outlineHtmlDoc.CreateElement("a"); aElement.SetAttributeValue("href", childOutlineNode.Href); HtmlNode spanElement = outlineHtmlDoc.CreateElement("span"); spanElement.InnerHtml = childOutlineNode.Content; aElement.AppendChild(spanElement); liElement.AppendChild(aElement); ulElement.AppendChild(liElement); // Scrollabe track if (outlineNode.Level == 1) { HtmlNode indicatorTrackElement = outlineHtmlDoc.CreateElement("div"); indicatorTrackElement.SetAttributeValue("class", "outline-scrollable-track"); liElement.AppendChild(indicatorTrackElement); } if (childOutlineNode.Children.Count > 0 && childOutlineNode.Level < 3) // Don't include h4s, h5s and h6s { HtmlNode childULElement = outlineHtmlDoc.CreateElement("ul"); childULElement.SetAttributeValue("class", $"level-{childOutlineNode.Level + 1}"); liElement.AppendChild(childULElement); GenerateOutlineNodes(childULElement, childOutlineNode, outlineHtmlDoc); } } }
private OutlineNode GenerateOutlineTree(HtmlNode htmlNode, OutlineNode outlineNode) { HtmlNodeCollection children = htmlNode.ChildNodes; foreach (HtmlNode childHtmlNode in children) { // Intentionally skips sub trees since they do not contribute to the document's outline. Sub trees are children of // sectioning content roots, like blockquote. If child is a blockquote, we never iterate through its children, so sub trees are ignored. if (childHtmlNode.Name == "section") { // We don't know the heading tag, could be h1|h2|h3|h4|h5|h6. Xpath 1 sucks so bad. HtmlNode headingElement = childHtmlNode.SelectSingleNode("header/*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]"); int level = headingElement.Name[1] - 48; // http://www.asciitable.com/ OutlineNode childOutlineNode = new OutlineNode { Content = headingElement.InnerText, Level = level, Href = "#" + childHtmlNode.Id }; outlineNode.Children.Add(childOutlineNode); GenerateOutlineTree(childHtmlNode, childOutlineNode); } } return(null); }
private void GenerateOutlineNodes(HtmlNode ulElement, OutlineNode outlineNodes, HtmlDocument outlineHtmlDoc) { bool firstChild = true; foreach (OutlineNode childOutlineNode in outlineNodes.Children) { HtmlNode liElement = outlineHtmlDoc.CreateElement("li"); liElement.SetAttributeValue("class", $"outline__node outline__node_level_{childOutlineNode.Level}"); HtmlNode aElement = outlineHtmlDoc.CreateElement("a"); if (childOutlineNode.Level == 2 && firstChild) { aElement.SetAttributeValue("class", "outline__link outline__link_has-bar outline__link_indented"); } else if (childOutlineNode.Level == 1) { aElement.SetAttributeValue("class", "outline__link outline__link_title"); } else { aElement.SetAttributeValue("class", "outline__link outline__link_indented"); } aElement.SetAttributeValue("href", childOutlineNode.Href); HtmlNode spanElement = outlineHtmlDoc.CreateElement("span"); spanElement.SetAttributeValue("class", "outline__text"); spanElement.InnerHtml = childOutlineNode.Content; aElement.AppendChild(spanElement); liElement.AppendChild(aElement); ulElement.AppendChild(liElement); ulElement.SetAttributeValue("style", $"--level: {childOutlineNode.Level}"); if (childOutlineNode.Level == 1) { HtmlNode backToTopIconSvgNode = outlineHtmlDoc.CreateElement("svg"); backToTopIconSvgNode.SetAttributeValue("class", "outline__back-to-top-icon"); HtmlNode backToTopIconUseNode = outlineHtmlDoc.CreateElement("use"); backToTopIconUseNode.SetAttributeValue("xlink:href", "#material-design-arrow-upward"); backToTopIconSvgNode.AppendChild(backToTopIconUseNode); aElement.AppendChild(backToTopIconSvgNode); } if (childOutlineNode.Children.Count > 0) { HtmlNode childULElement = outlineHtmlDoc.CreateElement("ul"); childULElement.SetAttributeValue("class", "outline__list"); liElement.AppendChild(childULElement); GenerateOutlineNodes(childULElement, childOutlineNode, outlineHtmlDoc); } firstChild = false; } }
// If article menu is enabled, generates outline and inserts it public Manifest Process(Manifest manifest, string outputFolder) { if (outputFolder == null) { throw new ArgumentNullException("Base directory cannot be null"); } foreach (ManifestItem manifestItem in manifest.Files) { if (manifestItem.DocumentType != "Conceptual") { continue; } manifestItem.Metadata.TryGetValue("mimo_disableArticleMenu", out object disableArticleMenu); if (disableArticleMenu as bool? == true) { continue; } // Get document Node HtmlNode documentNode = manifestItem. GetHtmlOutputDoc(outputFolder). DocumentNode; // Get main article node HtmlNode mainArticleNode = documentNode.SelectSingleNode($"//article[@class='{UtilsConstants.MainArticleClasses}']"); // Generate outline tree for article OutlineNode rootOutlineNode = new OutlineNode(); GenerateOutlineTree(mainArticleNode, rootOutlineNode); // Render outline tree var outlineHtmlDoc = new HtmlDocument(); HtmlNode rootULElement = outlineHtmlDoc.CreateElement("ul"); rootULElement.SetAttributeValue("class", "outline__list outline__list_root"); GenerateOutlineNodes(rootULElement, rootOutlineNode, outlineHtmlDoc); // Insert title HtmlNode articleMenuContentElement = documentNode.SelectSingleNode("//*[@class='article-menu__content dropdown__body']"); // Scrollable indicators HtmlNode level2ULElement = rootULElement.SelectSingleNode("li/ul"); if (level2ULElement != null) // If there is no level2ULElement, there's no scrolling { // Wrap level 2 ul element in a div together with indicators, insert wrapper as child of level 2 ul element's parent level2ULElement.SetAttributeValue("class", "outline__list scrollable-indicators__scrollable outline__scrollable"); level2ULElement.Remove(); HtmlNode scrollableIndicatorsNode = outlineHtmlDoc.CreateElement("div"); scrollableIndicatorsNode.SetAttributeValue("class", "outline__scrollable-indicators scrollable-indicators scrollable-indicators_axis_vertical"); HtmlNode startIndicatorElement = outlineHtmlDoc.CreateElement("div"); startIndicatorElement.SetAttributeValue("class", "scrollable-indicators__indicator scrollable-indicators__indicator_start"); HtmlNode endIndicatorElement = outlineHtmlDoc.CreateElement("div"); endIndicatorElement.SetAttributeValue("class", "scrollable-indicators__indicator scrollable-indicators__indicator_end"); scrollableIndicatorsNode.AppendChild(level2ULElement); scrollableIndicatorsNode.AppendChild(startIndicatorElement); scrollableIndicatorsNode.AppendChild(endIndicatorElement); HtmlNode level1LIElement = rootULElement.SelectSingleNode("li"); level1LIElement.AppendChild(scrollableIndicatorsNode); } // Insert outline tree HtmlNode outlineNode = outlineHtmlDoc.CreateElement("div"); outlineNode.SetAttributeValue("class", "article-menu__outline outline"); outlineNode.AppendChild(rootULElement); articleMenuContentElement.AppendChild(outlineNode); // Save string relPath = manifestItem.GetHtmlOutputRelPath(); File.WriteAllText(Path.Combine(outputFolder, relPath), documentNode.OuterHtml); } return(manifest); }
// If article menu is enabled, generates outline and inserts it public Manifest Process(Manifest manifest, string outputFolder) { if (outputFolder == null) { throw new ArgumentNullException("Base directory cannot be null"); } foreach (ManifestItem manifestItem in manifest.Files) { if (manifestItem.DocumentType != "Conceptual") { continue; } manifestItem.Metadata.TryGetValue("mimo_disableArticleMenu", out object disableArticleMenu); if (disableArticleMenu as bool? == true) { continue; } // Get document Node HtmlNode documentNode = manifestItem. GetHtmlOutputDoc(outputFolder). DocumentNode; // Get article node HtmlNode articleNode = documentNode.SelectSingleNode("//article[@class='main-article']"); // Generate outline tree for article OutlineNode rootOutlineNode = new OutlineNode { Content = articleNode.SelectSingleNode("h1").InnerText, Level = 1, Href = "#" }; GenerateOutlineTree(articleNode.SelectSingleNode("div[@class='content']"), rootOutlineNode); // content div is the direct parent level 2 sections // Render outline tree var outlineHtmlDoc = new HtmlDocument(); HtmlNode rootULElement = outlineHtmlDoc.CreateElement("ul"); rootULElement.SetAttributeValue("class", "level-2"); GenerateOutlineNodes(rootULElement, rootOutlineNode, outlineHtmlDoc); // Insert title HtmlNode outlineElement = documentNode.SelectSingleNode("//*[@id='outline']"); HtmlNode titleSpanElement = outlineHtmlDoc.CreateElement("span"); titleSpanElement.InnerHtml = rootOutlineNode.Content; HtmlNode titleAElement = outlineHtmlDoc.CreateElement("a"); titleAElement.SetAttributeValue("href", "#"); titleAElement.SetAttributeValue("class", "level-1"); titleAElement.AppendChild(titleSpanElement); outlineElement.PrependChild(titleAElement); // Insert outline tree HtmlNode outlineScrollableElement = outlineElement.SelectSingleNode("*[@id='outline-scrollable']"); outlineScrollableElement.AppendChild(rootULElement); // Save string relPath = manifestItem.GetHtmlOutputRelPath(); File.WriteAllText(Path.Combine(outputFolder, relPath), documentNode.OuterHtml); } return(manifest); }