public bool Convert(DitaElement bodyElement, List <DitaPageSectionJson> sections, string fileName, out string body) { StringBuilder bodyStringBuilder = new StringBuilder(); Sections = sections; FileName = fileName; if (bodyElement != null) { if (bodyElement.IsContainer) { foreach (DitaElement childElement in bodyElement?.Children) { bodyStringBuilder.Append(Convert(childElement)); } } else { bodyStringBuilder.Append(bodyElement.InnerText); } } body = bodyStringBuilder.ToString(); return(true); }
// Parse the title from the book map private void ParseBookMapTitle() { DitaElement titleElement = RootMap.RootElement.FindOnlyChild("booktitle"); AddChildDitaElementTextToDictionary(titleElement, "mainbooktitle", BookTitle); AddChildDitaElementTextToDictionary(titleElement, "booktitlealt", BookTitle); }
// Build a dictionary of elements in the document, by id private void ParseElementIds(DitaElement parentElement) { if (parentElement != null) { // Does this element have an id? string id = parentElement.AttributeValueOrDefault("id", string.Empty); if (!string.IsNullOrEmpty(id)) { if (!ElementsById.ContainsKey(id)) { ElementsById.Add(id, parentElement); } else { Trace.TraceWarning($"Duplicate element id {id} found in {FileName}."); } } // Parse the children if (parentElement.Children != null) { foreach (DitaElement childElement in parentElement.Children) { ParseElementIds(childElement); } } } }
protected List <DitaElement> FindChildren(string[] types, DitaElement parentElement) { List <DitaElement> result = null; if (IsContainer) { // Are any of our direct children of this type? result = parentElement?.Children?.Where(e => types.Contains(e.Type)).ToList(); if (result?.Count == 0) { // Try finding children of children foreach (DitaElement childElement in parentElement.Children) { result = FindChildren(types, childElement); if (result?.Count != 0) { break; } } } } return(result); }
private void ResolveKeywords(DitaElement parentElement, DitaCollection collection) { if (parentElement != null) { if (parentElement.Type == "keyword") { string keyref = parentElement.AttributeValueOrDefault("keyref", String.Empty); if (!string.IsNullOrWhiteSpace(keyref)) { // Try to find the referred to file DitaKeyDef keyDef = collection.GetKeyDefByKey(keyref); if (keyDef != null) { if (!string.IsNullOrWhiteSpace(keyDef.Keywords)) { parentElement.SetInnerText(keyDef.Keywords); } } } } else { // Check the child elements if (parentElement.Children != null) { foreach (DitaElement childElement in parentElement.Children) { ResolveKeywords(childElement, collection); } } } } }
// Parse the metadata from the given element? private void ParseMetaCategories(DitaElement parentElement) { try { List <DitaElement> categoriesData = parentElement?.FindChildren("category"); if (categoriesData != null) { foreach (DitaElement categoryData in categoriesData) { foreach (DitaElement data in categoryData.Children) { if (data?.Attributes.ContainsKey("name") ?? false) { if (BookMeta.ContainsKey(data?.Attributes["name"])) { Trace.TraceWarning($"BookMeta already contains a value for {data?.Attributes["name"]}"); } else { BookMeta.Add(data?.Attributes["name"], data?.Attributes["value"]); } } } } } } catch (Exception ex) { Trace.TraceError(ex); } }
// Replaces the values in this element with those copied from another element public void Copy(DitaElement sourceElement) { Type = sourceElement.Type; Attributes = sourceElement.Attributes; IsContainer = sourceElement.IsContainer; Children = sourceElement.Children; InnerText = sourceElement.InnerText; }
// Parse the book meta data from the book map private void ParseBookMapBookMeta() { DitaElement bookMetaElement = RootMap.RootElement.FindOnlyChild("bookmeta"); ParseBookMapVersion(bookMetaElement); ParseBookMapReleaseDate(bookMetaElement); // Everything in category ParseMetaCategories(bookMetaElement); }
// Construct from a single topic public DitaPageJson(DitaFile file, DitaCollection collection) { Collection = collection; // Get the title of the page Title = DitaFile.FixSpecialCharacters(file.Title); // Create the file name FileName = file.NewFileName ?? file.FileName; FileName = Path.ChangeExtension(FileName, ".json"); OriginalFileName = file.FileName; // Find the body element string bodyElementName = null; if (DitaFile.DitaFileBodyElement.ContainsKey(file.GetType())) { bodyElementName = DitaFile.DitaFileBodyElement[file.GetType()](); } if (!string.IsNullOrEmpty(bodyElementName)) { DitaElement bodyElement = file.RootElement.FindOnlyChild(bodyElementName); if (bodyElement != null) { Sections = new List <DitaPageSectionJson>(); // Convert the body to html DitaElementToHtmlConverter htmlConverter = new DitaElementToHtmlConverter(collection); htmlConverter.Convert(bodyElement, Sections, file.FileName, out string bodyHtml); BodyHtml = bodyHtml; // Convert the body to text DitaElementToTextConverter textConverter = new DitaElementToTextConverter(); textConverter.Convert(bodyElement, out string bodyText); BodyText = bodyText; } else { Trace.TraceWarning($"Body element not found in {file.FileName}."); } } else { Trace.TraceWarning($"No body element identified in {file.FileName}."); } IsEmpty = string.IsNullOrEmpty(BodyText) || string.IsNullOrEmpty(Title); }
// Wraps a dita element in another dita element, if needed to output correct html // For instance, wrap tables, sections, figures, etc in an <a name="..."> for instance private DitaElement PrependDitaElementIfNeeded(DitaElement inputElement) { string id = inputElement.AttributeValueOrDefault("id", null); // If this object has an id, wrap it in an a, name if (!string.IsNullOrEmpty(id)) { DitaElement outputElement = new DitaElement("a", false, null, inputElement.Parent, inputElement.PreviousSibling); outputElement.Attributes.Add("name", id); return(outputElement); } return(null); }
// Default constructor public DitaElement(string type, bool isContainer = false, string innerText = null, DitaElement parent = null, DitaElement previousSibling = null) { Type = type; IsContainer = isContainer; Parent = parent; PreviousSibling = previousSibling; if (IsContainer) { Children = new List <DitaElement>(); } else { InnerText = innerText; } }
private string Convert(DitaElement element) { if (element.IsContainer) { StringBuilder elementStringBuilder = new StringBuilder(); foreach (DitaElement childElement in element.Children) { elementStringBuilder.AppendLine(Convert(childElement)); } return(elementStringBuilder.ToString()); } else { return($"{element}"); } }
// Tries to add the text of the given element to the dictionary private bool AddChildDitaElementTextToDictionary(DitaElement parentElement, string type, Dictionary <string, string> dictionary) { // Try to find the child elements that match the type List <DitaElement> childElements = parentElement.FindChildren(type); if (childElements?.Count > 0) { foreach (DitaElement childElement in childElements) { dictionary.Add(type, childElement.InnerText); } return(true); } return(false); }
// Does a given image element refer to SVG? private bool IsImageElementSvg(DitaElement imageElement) { if (imageElement != null) { if (imageElement.Type == "image") { string extension = Path.GetExtension(imageElement.AttributeValueOrDefault("href", "")); if ((extension?.Equals(".svg", StringComparison.OrdinalIgnoreCase) ?? false) || (extension?.Equals(".image", StringComparison.OrdinalIgnoreCase) ?? false)) { return(true); } } } return(false); }
// Ingest an XmlNode and recursively parse it to create a group of DitaElements public DitaElement Convert(XmlNode inputNode, DitaElement parentElement = null, DitaElement previousSiblingElement = null) { // What type of element are we creating string type = inputNode?.Name; // Does this node/element have children bool isContainer = !IsNodeOnlyText(inputNode, out string innerText); if (!isContainer) { innerText = CleanInnerText(innerText); } // Create the new DITA element DitaElement outputElement = new DitaElement(type, isContainer, innerText, parentElement, previousSiblingElement); // Add the attributes if (inputNode?.Attributes != null) { foreach (XmlAttribute attribute in inputNode?.Attributes) { outputElement.Attributes.Add(attribute.Name, attribute.InnerText); } } // Add the children of this node/element, if any // ReSharper disable once InvertIf if (isContainer && inputNode?.ChildNodes != null) { DitaElement previousElement = null; foreach (XmlNode childNode in inputNode.ChildNodes) { DitaElement childElement = Convert(childNode, outputElement, previousElement); outputElement.Children.Add(childElement); previousElement = childElement; } } return(outputElement); }
// Parse the version of the document private void ParseBookMapVersion(DitaElement bookMetaElement) { // Try checking the publisher information section DitaElement publisherInformationElement = bookMetaElement?.FindOnlyChild("publisherinformation"); DitaElement publishedElement = publisherInformationElement?.FindChildren("published")?.Last(); DitaElement revisionIdElement = publishedElement?.FindOnlyChild("revisionid"); string version = revisionIdElement?.ToString(); // Try checking the prodinfo section if (string.IsNullOrWhiteSpace(version) || version == "ProductVersionNumber") { DitaElement prodinfoElement = bookMetaElement?.FindOnlyChild("prodinfo"); DitaElement vrmlistElement = prodinfoElement?.FindOnlyChild("vrmlist"); DitaElement vrmElement = vrmlistElement?.FindChildren("vrm")?[0]; version = vrmElement?.Attributes?["version"]; } if (!string.IsNullOrWhiteSpace(version)) { BookMeta.Add("version", version); } }
public bool Convert(DitaElement bodyElement, out string body) { StringBuilder bodyStringBuilder = new StringBuilder(); if (bodyElement != null) { if (bodyElement.IsContainer) { foreach (DitaElement childElement in bodyElement?.Children) { bodyStringBuilder.Append(Convert(childElement)); } } else { bodyStringBuilder.Append(bodyElement); } } body = bodyStringBuilder.ToString(); return(true); }
// Parse the date of the document private void ParseBookMapReleaseDate(DitaElement bookMetaElement) { DitaElement publisherInformationElement = bookMetaElement?.FindOnlyChild("publisherinformation"); DitaElement publishedElement = publisherInformationElement?.FindChildren("published")?.Last(); DitaElement completedElement = publishedElement?.FindOnlyChild("completed"); string year = completedElement?.FindOnlyChild("year")?.ToString(); string month = completedElement?.FindOnlyChild("month")?.ToString(); string day = completedElement?.FindOnlyChild("day")?.ToString(); if (!string.IsNullOrWhiteSpace(year) && !string.IsNullOrWhiteSpace(month) && !string.IsNullOrWhiteSpace(day)) { try { // Is this a valid date? DateTime publishDate = new DateTime(int.Parse(year), int.Parse(month), int.Parse(day)); BookMeta.Add("published date", $"{publishDate.Day}/{publishDate.Month}/{publishDate.Year} 00:00:00"); } catch { // } } }
private string Convert(DitaElement element) { StringBuilder elementStringBuilder = new StringBuilder(); // Does this element need to be wrapped by another element? DitaElement prependElement = PrependDitaElementIfNeeded(element); if (prependElement != null) { elementStringBuilder.Append(Convert(prependElement)); } // Determine the new html tag and attributes string htmlTag = ConvertDitaTagToHtmlTag(element); Dictionary <string, string> htmlAttributes = ConvertDitaTagAttributesToHtmlTagAttributes(element); AddHtmlTagAttributes(htmlAttributes, element); // If this is a parent, then add the children if (element.IsContainer) { elementStringBuilder.Append(HtmlOpeningTag(htmlTag, htmlAttributes)); foreach (DitaElement childElement in element.Children) { elementStringBuilder.Append(Convert(childElement)); } elementStringBuilder.Append(HtmlClosingTag(htmlTag)); } else { elementStringBuilder.Append($"{HtmlOpeningTag(htmlTag, htmlAttributes)}{element.InnerText}{HtmlClosingTag(htmlTag)}"); } return(elementStringBuilder.ToString()); }
// Parse the meta date from the map private void ParseMapMeta() { DitaElement bookMetaElement = RootMap.RootElement.FindOnlyChild("topicmeta"); ParseMetaCategories(bookMetaElement); }
// Takes a DITA "tag" and returns the corresponding HTML tag private string ConvertDitaTagToHtmlTag(DitaElement element) { switch (element.Type) { case "b": return("strong"); case "colspec": TableColumnIndex++; FixUpTableColumnSpecs(); _tableColumnSpecs[TableColumnIndex] = new DitaTableColumnSpec { Number = (TableColumnIndex + 1) }; return(""); case "entry": TableRowColumnIndex++; if (element.Parent?.Parent?.Type == "thead" || element.AttributeValueOrDefault("class", "") == "th") { return("th"); } return("td"); case "fig": return("figure"); case "image": // Is this referring to an SVG or other type of image? if (IsImageElementSvg(element)) { return("object"); } return("img"); case "keyword": return(""); case "row": TableRowColumnIndex = -1; return("tr"); case "steps": return("ol"); case "step": return("li"); case "table": TableColumnIndex = -1; _tableColumnSpecs = null; break; case "tgroup": TableColumnIndex = -1; _tableColumnSpecs = null; return(""); case "title": if (element.Parent?.Type == "section") { // Create a reference to this section, if this is the title of the section if (CurrentSection != null) { if (string.IsNullOrEmpty(CurrentSection.Title) && !string.IsNullOrEmpty(CurrentSection.Anchor)) { CurrentSection.Title = element.ToString(); Sections.Add(CurrentSection); CurrentSection = null; } } return("h3"); } return("h4"); case "xref": return("a"); case "#text": return(""); } return(element.Type); }
// Converts DITA tag attributes to html tag attributes private Dictionary <string, string> ConvertDitaTagAttributesToHtmlTagAttributes(DitaElement element) { Dictionary <string, string> htmlAttributes = new Dictionary <string, string>(); foreach (string ditaAttributeKey in element.Attributes.Keys) { (string newKey, string newValue) = ConvertDitaTagAttributeToHtmlTagAttribute(ditaAttributeKey, element.Attributes[ditaAttributeKey], element); if (!string.IsNullOrWhiteSpace(newKey)) { htmlAttributes.Add(newKey, newValue); } } return(htmlAttributes); }
// Resolves conrefs in this file private void ResolveConRefs(DitaElement parentElement, DitaCollection collection) { if (parentElement != null) { bool updated = false; // Does this element have a conref? string conref = parentElement.AttributeValueOrDefault("conref", String.Empty); if (!string.IsNullOrEmpty(conref)) { // We expect the conref to be in the form of filename.xml#fileid/elementid Regex conRefRegex = new Regex("^(.*)#(.*)/(.*)$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); MatchCollection conRefMatchCollection = conRefRegex.Matches(conref); if (conRefMatchCollection?.Count > 0 && (conRefMatchCollection[0].Groups.Count == 4)) { // Extract the components of the conref string refFileName = conRefMatchCollection[0].Groups[1].Value; string refFileId = conRefMatchCollection[0].Groups[2].Value; string refElementId = conRefMatchCollection[0].Groups[3].Value; if (Path.GetFileNameWithoutExtension(refFileName) != refFileId) { Trace.TraceWarning($"conref file name '{refFileName}' is not equal to file id '{refFileId}'."); } // Try to find the file that this conref refers to DitaFile refFile = collection.GetFileByName(refFileName); if (refFile != null) { // Does the references element exist in this file if (refFile.ElementsById.ContainsKey(refElementId)) { DitaElement refElement = refFile.ElementsById[refElementId]; // Copy the refernce element parentElement.Copy(refElement); updated = true; } else { Trace.TraceWarning($"Element '{refElementId}' not found in file '{refFileName}'."); } } else { Trace.TraceWarning($"Can't find file '{refFileName}' referenced in file '{FileName}'."); } } else { Trace.TraceWarning($"conref {conref} not in expected format."); } } // Update child references if (!updated && parentElement.Children != null) { foreach (DitaElement childElement in parentElement.Children) { ResolveConRefs(childElement, collection); } } } }
// Converts a single dita tag attribute to an html attribute private (string newKey, string newValue) ConvertDitaTagAttributeToHtmlTagAttribute(string key, string value, DitaElement element) { switch (element.Type) { case "a": return(key, value); case "colspec": if (key == "colname") { FixUpTableColumnSpecs(); _tableColumnSpecs[TableColumnIndex].Name = value; } if (key == "colwidth") { FixUpTableColumnSpecs(); _tableColumnSpecs[TableColumnIndex].Width = value; } if (key == "colnum") { if (int.TryParse(value, out int colnum)) { FixUpTableColumnSpecs(); _tableColumnSpecs[TableColumnIndex].Number = colnum; } } break; case "entry": if (key == "morerows") { if (int.TryParse(value, out int rowspan)) { return("rowspan", $"{rowspan + 1}"); } } if (key == "valign") { return(key, value); } break; case "image": if (key == "href") { if (IsImageElementSvg(element)) { return("data", ImageUrlFromHref(value)); } return("src", ImageUrlFromHref(value)); } break; case "section": if (key == "id") { CurrentSection = new DitaPageSectionJson { Anchor = value }; return(key, value); } break; case "tgroup": if (key == "cols") { if (int.TryParse(value, out int columns)) { _tableColumnSpecs = new DitaTableColumnSpec[columns]; } } break; case "xref": if (key == "href") { string url = UrlFromXref(element, out string title); // If we encounter an empty xref, we want to try to generate link text, based on what it links too if (string.IsNullOrWhiteSpace(element.ToString())) { element.SetInnerText(title); } return(key, url); } break; } return("", ""); }
// Add additional attributes to specific html tags private void AddHtmlTagAttributes(Dictionary <string, string> htmlAttributes, DitaElement element) { switch (element.Type) { case "image": if (IsImageElementSvg(element)) { // Add the type of embedded svg if (!htmlAttributes.ContainsKey("type")) { htmlAttributes.Add("type", "image/svg+xml"); } } break; case "table": // Add the generic "table" class to all tables if (!htmlAttributes.ContainsKey("class")) { htmlAttributes.Add("class", "table"); } break; case "entry": // If there is a width defined, add it to the entry string colname = element.Attributes.ContainsKey("colname") ? element.Attributes["colname"] : null; if (!htmlAttributes.ContainsKey("width")) { string widthAsPercent = ColumnWidthAsPercent(colname, TableRowColumnIndex); if (!string.IsNullOrEmpty(widthAsPercent)) { htmlAttributes.Add("width", widthAsPercent); } } // If there is a colspan defined, add it to the entry if (element.Attributes.ContainsKey("namest") && element.Attributes.ContainsKey("nameend")) { // Build the colspan int startColumn = _tableColumnSpecs?.FirstOrDefault(o => o.Name == element.Attributes["namest"])?.Number ?? -1; int endColumn = _tableColumnSpecs?.FirstOrDefault(o => o.Name == element.Attributes["nameend"])?.Number ?? -1; if (startColumn >= 0 && endColumn >= 0) { if (!htmlAttributes.ContainsKey("colspan")) { htmlAttributes.Add("colspan", $"{endColumn - startColumn + 1}"); } } } break; } }
// Returns the relative or absolute url from a Dita XREF for use in an html A tag private string UrlFromXref(DitaElement xrefElement, out string title) { // What is the scope string scope = xrefElement.AttributeValueOrDefault("scope", null); string format = xrefElement.AttributeValueOrDefault("format", null); string href = xrefElement.AttributeValueOrDefault("href", null); title = null; if (scope == "external") { return(href); } if (!string.IsNullOrEmpty(href)) { string result = null; if (href[0] == '#') { // Link to the same page if (href.Contains('/')) { string[] anchorSplit = href.Split('/'); if (anchorSplit.Length > 1) { result = $"#{anchorSplit[1]}"; } } else { result = href.Substring(1); } } else if (href.ToLowerInvariant().StartsWith("http")) { result = href; } else { // Split by hash, if any string[] hashSplit = href.Split('#'); // Try to find the topic it is linking to DitaFile referenceFile = Collection?.GetFileByName(hashSplit[0]); if (referenceFile != null) { result = $"%DOCUMENT_ROOT%/{Path.GetFileNameWithoutExtension(referenceFile.NewFileName)}"; if (hashSplit.Length > 1) { result += $"#{hashSplit[1]}"; } title = referenceFile.Title; } else { Trace.TraceError($"Xref refers to unknown local file {hashSplit[0]} in {FileName}"); } } if (!string.IsNullOrEmpty(result)) { return(result); } else { return("#"); } } Trace.TraceWarning($"Unknown xref scope={scope}, format={format}, href={href} in {FileName}"); return("#"); }