/* Function: GetBodyLinks * Goes through the body of the passed <Topic> and adds any Natural Docs and image links it finds in the <NDMarkup> * to <LinkSet> and <ImageLinkSet>. */ protected void GetBodyLinks(Topic topic, ref LinkSet linkSet, ref ImageLinkSet imageLinkSet) { if (topic.Body == null) { return; } NDMarkup.Iterator iterator = new NDMarkup.Iterator(topic.Body); // Doing two passes of GoToFirstTag is probably faster than iterating through each element if (iterator.GoToFirstTag("<link type=\"naturaldocs\"")) { do { Link link = new Link(); // ignore LinkID link.Type = LinkType.NaturalDocs; link.Text = iterator.Property("originaltext"); link.Context = topic.BodyContext; // ignore contextID link.FileID = topic.FileID; link.ClassString = topic.ClassString; // ignore classID link.LanguageID = topic.LanguageID; // ignore EndingSymbol // ignore TargetTopicID // ignore TargetScore linkSet.Add(link); }while (iterator.GoToNextTag("<link type=\"naturaldocs\"")); } iterator = new NDMarkup.Iterator(topic.Body); if (iterator.GoToFirstTag("<image")) { do { ImageLink imageLink = new ImageLink(); // ignore ImageLinkID imageLink.OriginalText = iterator.Property("originaltext"); imageLink.Path = new Path(iterator.Property("target")); // ignore FileName, generated from Path imageLink.FileID = topic.FileID; imageLink.ClassString = topic.ClassString; // ignore classID // ignore TargetFileID // ignore TargetScore imageLinkSet.Add(imageLink); }while (iterator.GoToNextTag("<image")); } }
/* Function: AppendSummary */ protected void AppendSummary(StringBuilder output) { output.Append("<div class=\"TTSummary\">"); string summary = context.Topic.Summary; NDMarkup.Iterator iterator = new NDMarkup.Iterator(summary); while (iterator.IsInBounds) { switch (iterator.Type) { case NDMarkup.Iterator.ElementType.Text: // Preserve multiple whitespace chars, but skip the extra processing if there aren't any if (summary.IndexOf(" ", iterator.RawTextIndex, iterator.Length) != -1) { output.Append(iterator.String.ConvertMultipleWhitespaceChars()); } else { iterator.AppendTo(output); } break; case NDMarkup.Iterator.ElementType.BoldTag: case NDMarkup.Iterator.ElementType.ItalicsTag: case NDMarkup.Iterator.ElementType.UnderlineTag: case NDMarkup.Iterator.ElementType.LTEntityChar: case NDMarkup.Iterator.ElementType.GTEntityChar: case NDMarkup.Iterator.ElementType.AmpEntityChar: case NDMarkup.Iterator.ElementType.QuoteEntityChar: // These the NDMarkup directly matches the HTML tags iterator.AppendTo(output); break; case NDMarkup.Iterator.ElementType.LinkTag: string linkType = iterator.Property("type"); if (linkType == "email") { AppendEMailLink(iterator, output); } else if (linkType == "url") { AppendURLLink(iterator, output); } else // type == "naturaldocs" { AppendNaturalDocsLink(iterator, output); } break; } iterator.Next(); } output.Append("</div>"); }
/* Function: BuildSummary */ protected void BuildSummary() { htmlOutput.Append("<div class=\"TTSummary\">"); NDMarkup.Iterator iterator = new NDMarkup.Iterator(topic.Summary); while (iterator.IsInBounds) { switch (iterator.Type) { case NDMarkup.Iterator.ElementType.Text: if (topic.Body.IndexOf(" ", iterator.RawTextIndex, iterator.Length) == -1) { iterator.AppendTo(htmlOutput); } else { htmlOutput.Append(iterator.String.ConvertMultipleWhitespaceChars()); } break; case NDMarkup.Iterator.ElementType.BoldTag: case NDMarkup.Iterator.ElementType.ItalicsTag: case NDMarkup.Iterator.ElementType.UnderlineTag: case NDMarkup.Iterator.ElementType.LTEntityChar: case NDMarkup.Iterator.ElementType.GTEntityChar: case NDMarkup.Iterator.ElementType.AmpEntityChar: case NDMarkup.Iterator.ElementType.QuoteEntityChar: iterator.AppendTo(htmlOutput); break; case NDMarkup.Iterator.ElementType.LinkTag: string linkType = iterator.Property("type"); if (linkType == "email") { BuildEMailLink(iterator); } else if (linkType == "url") { BuildURLLink(iterator); } else // type == "naturaldocs" { BuildNaturalDocsLink(iterator); } break; } iterator.Next(); } htmlOutput.Append("</div>"); }
/* Function: AppendInlineImageLink */ protected void AppendInlineImageLink(NDMarkup.Iterator iterator, StringBuilder output) { // Create a link object with the identifying properties needed to look it up in the list of links. ImageLink imageLinkStub = new ImageLink(); imageLinkStub.OriginalText = iterator.Property("originaltext"); imageLinkStub.FileID = context.Topic.FileID; imageLinkStub.ClassString = context.Topic.ClassString; imageLinkStub.ClassID = context.Topic.ClassID; // Find the actual link so we know if it resolved to anything. ImageLink fullImageLink = null; foreach (var imageLink in imageLinks) { if (imageLink.SameIdentifyingPropertiesAs(imageLinkStub)) { fullImageLink = imageLink; break; } } #if DEBUG if (fullImageLink == null) { throw new Exception("All image links in a topic must be in the list passed to HTMLTooltip."); } #endif if (fullImageLink.IsResolved) { output.EntityEncodeAndAppend(iterator.Property("linktext")); } else { output.EntityEncodeAndAppend(iterator.Property("originaltext")); } }
/* Function: BuildEMailLink */ protected void BuildEMailLink(NDMarkup.Iterator iterator) { string address = iterator.Property("target"); int atIndex = address.IndexOf('@'); int cutPoint1 = atIndex / 2; int cutPoint2 = (atIndex + 1) + ((address.Length - (atIndex + 1)) / 2); if (!isToolTip) { htmlOutput.Append("<a href=\"#\" onclick=\"javascript:location.href='ma\\u0069'+'lto\\u003a'+'"); htmlOutput.Append(EMailSegmentForJavaScriptString(address.Substring(0, cutPoint1))); htmlOutput.Append("'+'"); htmlOutput.Append(EMailSegmentForJavaScriptString(address.Substring(cutPoint1, atIndex - cutPoint1))); htmlOutput.Append("'+'\\u0040'+'"); htmlOutput.Append(EMailSegmentForJavaScriptString(address.Substring(atIndex + 1, cutPoint2 - (atIndex + 1)))); htmlOutput.Append("'+'"); htmlOutput.Append(EMailSegmentForJavaScriptString(address.Substring(cutPoint2, address.Length - cutPoint2))); htmlOutput.Append("';return false;\">"); } string text = iterator.Property("text"); if (text != null) { htmlOutput.EntityEncodeAndAppend(text); } else { htmlOutput.Append(EMailSegmentForHTML(address.Substring(0, cutPoint1))); htmlOutput.Append("<span style=\"display: none\">[xxx]</span>"); htmlOutput.Append(EMailSegmentForHTML(address.Substring(cutPoint1, atIndex - cutPoint1))); htmlOutput.Append("<span>@</span>"); htmlOutput.Append(EMailSegmentForHTML(address.Substring(atIndex + 1, cutPoint2 - (atIndex + 1)))); htmlOutput.Append("<span style=\"display: none\">[xxx]</span>"); htmlOutput.Append(EMailSegmentForHTML(address.Substring(cutPoint2, address.Length - cutPoint2))); } if (!isToolTip) { htmlOutput.Append("</a>"); } }
/* Function: AppendEMailLink */ protected void AppendEMailLink(NDMarkup.Iterator iterator, StringBuilder output) { string text = iterator.Property("text"); if (text != null) { output.EntityEncodeAndAppend(text); } else { string address = iterator.Property("target"); int atIndex = address.IndexOf('@'); int cutPoint1 = atIndex / 2; int cutPoint2 = (atIndex + 1) + ((address.Length - (atIndex + 1)) / 2); output.Append(EMailSegmentForHTML(address.Substring(0, cutPoint1))); output.Append("<span style=\"display: none\">[xxx]</span>"); output.Append(EMailSegmentForHTML(address.Substring(cutPoint1, atIndex - cutPoint1))); output.Append("<span>@</span>"); output.Append(EMailSegmentForHTML(address.Substring(atIndex + 1, cutPoint2 - (atIndex + 1)))); output.Append("<span style=\"display: none\">[xxx]</span>"); output.Append(EMailSegmentForHTML(address.Substring(cutPoint2, address.Length - cutPoint2))); } }
/* Function: AppendNaturalDocsLink */ protected void AppendNaturalDocsLink(NDMarkup.Iterator iterator, StringBuilder output) { // Create a link object with the identifying properties needed to look it up in the list of links. Link linkStub = new Link(); linkStub.Type = LinkType.NaturalDocs; linkStub.Text = iterator.Property("originaltext"); linkStub.Context = context.Topic.BodyContext; linkStub.ContextID = context.Topic.BodyContextID; linkStub.FileID = context.Topic.FileID; linkStub.ClassString = context.Topic.ClassString; linkStub.ClassID = context.Topic.ClassID; linkStub.LanguageID = context.Topic.LanguageID; // Find the actual link so we know if it resolved to anything. Link fullLink = null; foreach (Link link in links) { if (link.SameIdentifyingPropertiesAs(linkStub)) { fullLink = link; break; } } #if DEBUG if (fullLink == null) { throw new Exception("All links in a topic must be in the list passed to Tooltip."); } #endif // If it didn't resolve, we just output the original text and we're done. if (!fullLink.IsResolved) { output.EntityEncodeAndAppend(iterator.Property("originaltext")); return; } // If it did resolve, find the interpretation that was used. If it was a named link it would affect the link text. LinkInterpretation linkInterpretation = null; string ignore; List <LinkInterpretation> linkInterpretations = EngineInstance.Comments.NaturalDocsParser.LinkInterpretations(fullLink.Text, Comments.Parsers.NaturalDocs.LinkInterpretationFlags.AllowNamedLinks | Comments.Parsers.NaturalDocs.LinkInterpretationFlags.AllowPluralsAndPossessives | Comments.Parsers.NaturalDocs.LinkInterpretationFlags.FromOriginalText, out ignore); linkInterpretation = linkInterpretations[fullLink.TargetInterpretationIndex]; // Since it's a tooltip, that's all we need. We don't need to find the Topic because we're not creating an actual link; // you can't click on tooltips. We just needed to know what the text should be. output.EntityEncodeAndAppend(linkInterpretation.Text); }
/* Function: AppendURLLink */ protected void AppendURLLink(NDMarkup.Iterator iterator, StringBuilder output) { string text = iterator.Property("text"); if (text != null) { output.EntityEncodeAndAppend(text); } else { string target = iterator.Property("target"); int startIndex = 0; int breakIndex; // Skip the protocol and any following slashes since we don't want a break after every slash in http:// or // file:///. int endOfProtocolIndex = target.IndexOf(':'); if (endOfProtocolIndex != -1) { do { endOfProtocolIndex++; }while (endOfProtocolIndex < target.Length && target[endOfProtocolIndex] == '/'); output.EntityEncodeAndAppend(target.Substring(0, endOfProtocolIndex)); output.Append("​"); // Zero width space startIndex = endOfProtocolIndex; } for (;;) { breakIndex = target.IndexOfAny(BreakURLCharacters, startIndex); if (breakIndex == -1) { if (target.Length - startIndex > MaxUnbrokenURLCharacters) { breakIndex = startIndex + MaxUnbrokenURLCharacters; } else { break; } } else if (breakIndex - startIndex > MaxUnbrokenURLCharacters) { breakIndex = startIndex + MaxUnbrokenURLCharacters; } output.EntityEncodeAndAppend(target.Substring(startIndex, breakIndex - startIndex)); output.Append("​"); // Zero width space output.EntityEncodeAndAppend(target[breakIndex]); startIndex = breakIndex + 1; } output.EntityEncodeAndAppend(target.Substring(startIndex)); } }
/* Function: MakeSummaryFromBody * If the <Topic> has a body, attempts to extract a summary from it and set <Topic.Summary>. */ public bool MakeSummaryFromBody(Topic topic) { if (topic.Body == null) { return(false); } NDMarkup.Iterator iterator = new NDMarkup.Iterator(topic.Body); while (iterator.IsInBounds) { // Allow headings to come before the opening paragraph. // We can assume the NDMarkup is valid, so we can assume this is an opening tag and we'll hit a closing tag. if (iterator.Type == NDMarkup.Iterator.ElementType.HeadingTag) { do { iterator.Next(); }while (iterator.Type != NDMarkup.Iterator.ElementType.HeadingTag); iterator.Next(); } // Also allow prototypes to come before the opening paragraph. else if (iterator.Type == NDMarkup.Iterator.ElementType.PreTag && iterator.Property("type") == "prototype") { do { iterator.Next(); }while (iterator.Type != NDMarkup.Iterator.ElementType.PreTag); iterator.Next(); } // Extract the entire openng paragraph for the summary, unlike Natural Docs 1.x which only used the first sentence. else if (iterator.Type == NDMarkup.Iterator.ElementType.ParagraphTag) { // Don't include the opening <p> in the summary. iterator.Next(); int startingIndex = iterator.RawTextIndex; while (iterator.Type != NDMarkup.Iterator.ElementType.ParagraphTag) { iterator.Next(); } // Iterator is now on the closing </p>. topic.Summary = topic.Body.Substring(startingIndex, iterator.RawTextIndex - startingIndex); return(true); } // If we hit any other tag before a paragraph, there is no summary. else { break; } } return(false); }
/* Function: AppendInlineImageLink */ protected void AppendInlineImageLink(NDMarkup.Iterator iterator, StringBuilder linkOutput, StringBuilder imageOutput, int imageNumber) { // Create a link object with the identifying properties needed to look it up in the list of links. ImageLink imageLinkStub = new ImageLink(); imageLinkStub.OriginalText = iterator.Property("originaltext"); imageLinkStub.FileID = context.Topic.FileID; imageLinkStub.ClassString = context.Topic.ClassString; imageLinkStub.ClassID = context.Topic.ClassID; // Find the actual link so we know if it resolved to anything. ImageLink fullImageLink = null; foreach (var imageLink in imageLinks) { if (imageLink.SameIdentifyingPropertiesAs(imageLinkStub)) { fullImageLink = imageLink; break; } } #if DEBUG if (fullImageLink == null) { throw new Exception("All image links in a topic must be in the list passed to HTMLTopic."); } #endif // If it didn't resolve, we just output the original text and we're done. if (!fullImageLink.IsResolved) { linkOutput.EntityEncodeAndAppend(iterator.Property("originaltext")); return; } Files.ImageFile targetFile = (Files.ImageFile)EngineInstance.Files.FromID(fullImageLink.TargetFileID); string description = targetFile.FileName.NameWithoutPathOrExtension; string anchor = "Topic" + context.Topic.TopicID + "_Image" + imageNumber; var fileSource = EngineInstance.Files.FileSourceOf(targetFile); Path relativeTargetPath = fileSource.MakeRelative(targetFile.FileName); Path targetOutputPath = Paths.Image.OutputFile(context.Target.OutputFolder, fileSource.Number, fileSource.Type, relativeTargetPath); Path relativeTargetOutputPath = targetOutputPath.MakeRelativeTo(context.OutputFile.ParentFolder); linkOutput.Append( "<a href=\"#" + anchor + "\" class=\"SeeImageLink\">" + iterator.Property("linktext").EntityEncode() + "</a>"); imageOutput.Append( "<div class=\"CImage\">" + "<a name=\"" + anchor + "\"></a>" + "<a href=\"" + relativeTargetOutputPath.ToURL().EntityEncode() + "\" target=\"_blank\" class=\"ZoomLink\">" + "<img src=\"" + relativeTargetOutputPath.ToURL().EntityEncode() + "\" loading=\"lazy\" " + (targetFile.DimensionsKnown ? "class=\"KnownDimensions\" width=\"" + targetFile.Width + "\" height=\"" + targetFile.Height + "\" " + "style=\"max-width: " + targetFile.Width + "px\" " : "class=\"UnknownDimensions\" ") + "alt=\"" + description.EntityEncode() + "\" />" + "</a>" + "<div class=\"CICaption\">" + description.EntityEncode() + "</div>" + "</div>"); }
/* Function: AppendBody */ protected void AppendBody(StringBuilder output) { output.Append("<div class=\"CBody\">"); string body = context.Topic.Body; NDMarkup.Iterator iterator = new NDMarkup.Iterator(body); bool underParameterHeading = false; string parameterListSymbol = null; string altParameterListSymbol = null; StringBuilder inlineImageContent = null; int imageNumber = 1; while (iterator.IsInBounds) { switch (iterator.Type) { case NDMarkup.Iterator.ElementType.Text: // Preserve multiple whitespace chars, but skip the extra processing if there aren't any if (body.IndexOf(" ", iterator.RawTextIndex, iterator.Length) != -1) { output.Append(iterator.String.ConvertMultipleWhitespaceChars()); } else { iterator.AppendTo(output); } break; case NDMarkup.Iterator.ElementType.ParagraphTag: iterator.AppendTo(output); if (iterator.IsClosingTag && inlineImageContent != null && inlineImageContent.Length > 0) { output.Append(inlineImageContent.ToString()); inlineImageContent.Remove(0, inlineImageContent.Length); } break; case NDMarkup.Iterator.ElementType.BulletListTag: case NDMarkup.Iterator.ElementType.BulletListItemTag: case NDMarkup.Iterator.ElementType.BoldTag: case NDMarkup.Iterator.ElementType.ItalicsTag: case NDMarkup.Iterator.ElementType.UnderlineTag: case NDMarkup.Iterator.ElementType.LTEntityChar: case NDMarkup.Iterator.ElementType.GTEntityChar: case NDMarkup.Iterator.ElementType.AmpEntityChar: case NDMarkup.Iterator.ElementType.QuoteEntityChar: // These the NDMarkup directly matches the HTML tags iterator.AppendTo(output); break; case NDMarkup.Iterator.ElementType.HeadingTag: if (iterator.IsOpeningTag) { output.Append("<div class=\"CHeading\">"); underParameterHeading = (iterator.Property("type") == "parameters"); } else { output.Append("</div>"); } break; case NDMarkup.Iterator.ElementType.PreTag: string preType = iterator.Property("type"); string preLanguageName = iterator.Property("language"); iterator.Next(); NDMarkup.Iterator startOfCode = iterator; // Because we can assume the NDMarkup is valid, we can assume we were on an opening tag and that we will // run into a closing tag before the end of the text. We can also assume the next pre tag is a closing tag. while (iterator.Type != NDMarkup.Iterator.ElementType.PreTag) { iterator.Next(); } string ndMarkupCode = body.Substring(startOfCode.RawTextIndex, iterator.RawTextIndex - startOfCode.RawTextIndex); string textCode = NDMarkupCodeToText(ndMarkupCode); output.Append("<pre>"); if (preType == "code") { Languages.Language preLanguage = null; if (preLanguageName != null) { // This can return null if the language name is unrecognized. preLanguage = EngineInstance.Languages.FromName(preLanguageName); } if (preLanguage == null) { preLanguage = EngineInstance.Languages.FromID(context.Topic.LanguageID); } Tokenizer code = new Tokenizer(textCode, tabWidth: EngineInstance.Config.TabWidth); preLanguage.Parser.SyntaxHighlight(code); AppendSyntaxHighlightedText(code.FirstToken, code.LastToken, output); } else { string htmlCode = textCode.EntityEncode(); htmlCode = StringExtensions.LineBreakRegex.Replace(htmlCode, "<br />"); output.Append(htmlCode); } output.Append("</pre>"); break; case NDMarkup.Iterator.ElementType.DefinitionListTag: if (iterator.IsOpeningTag) { output.Append("<table class=\"CDefinitionList\">"); } else { output.Append("</table>"); } break; case NDMarkup.Iterator.ElementType.DefinitionListEntryTag: case NDMarkup.Iterator.ElementType.DefinitionListSymbolTag: if (iterator.IsOpeningTag) { output.Append("<tr><td class=\"CDLEntry\">"); parameterListSymbol = null; // Create anchors for symbols. We are assuming there are enough embedded topics for each <ds> // tag and that they follow their parent topic in order. if (iterator.Type == NDMarkup.Iterator.ElementType.DefinitionListSymbolTag) { #if DEBUG if (embeddedTopics == null || embeddedTopicIndex >= embeddedTopics.Count || embeddedTopics[embeddedTopicIndex].IsEmbedded == false) { throw new Exception("There are not enough embedded topics to build the definition list."); } #endif var embeddedTopic = embeddedTopics[embeddedTopicIndex]; Context embeddedTopicContext = context; embeddedTopicContext.Topic = embeddedTopic; string embeddedTopicHashPath = embeddedTopicContext.TopicOnlyHashPath; if (embeddedTopicHashPath != null) { output.Append("<a name=\"" + embeddedTopicHashPath.EntityEncode() + "\"></a>"); } output.Append("<a name=\"Topic" + embeddedTopic.TopicID + "\"></a>"); embeddedTopicIndex++; } // If we're using a Parameters: heading, store the entry symbol in parameterListSymbol if (underParameterHeading) { NDMarkup.Iterator temp = iterator; temp.Next(); StringBuilder symbol = new StringBuilder(); while (temp.IsInBounds && temp.Type != NDMarkup.Iterator.ElementType.DefinitionListEntryTag && temp.Type != NDMarkup.Iterator.ElementType.DefinitionListSymbolTag) { if (temp.Type == NDMarkup.Iterator.ElementType.Text) { temp.AppendTo(symbol); } temp.Next(); } // If the entry name starts with any combination of $, @, or % characters, strip them off. int firstNonSymbolIndex = 0; while (firstNonSymbolIndex < symbol.Length) { char charAtIndex = symbol[firstNonSymbolIndex]; if (charAtIndex != '$' && charAtIndex != '@' && charAtIndex != '%') { break; } firstNonSymbolIndex++; } if (symbol.Length > 0) { parameterListSymbol = symbol.ToString(); } else { parameterListSymbol = null; } if (firstNonSymbolIndex > 0) { symbol.Remove(0, firstNonSymbolIndex); altParameterListSymbol = symbol.ToString(); } else { altParameterListSymbol = null; } } } else // closing tag { // See if parameterListSymbol matches any of the prototype parameter names if ((parameterListSymbol != null || altParameterListSymbol != null) && context.Topic.Prototype != null) { var parsedPrototype = context.Topic.ParsedPrototype; TokenIterator start, end; int matchedParameter = -1; for (int i = 0; i < parsedPrototype.NumberOfParameters; i++) { parsedPrototype.GetParameterName(i, out start, out end); if ((parameterListSymbol != null && parsedPrototype.Tokenizer.EqualsTextBetween(parameterListSymbol, true, start, end)) || (altParameterListSymbol != null && parsedPrototype.Tokenizer.EqualsTextBetween(altParameterListSymbol, true, start, end))) { matchedParameter = i; break; } } // If so, include the type under the entry in the HTML if (matchedParameter != -1) { parsedPrototype.BuildFullParameterType(matchedParameter, out start, out end); if (start < end && // Don't include single symbol types !(end.RawTextIndex - start.RawTextIndex == 1 && (start.Character == '$' || start.Character == '@' || start.Character == '%'))) { output.Append("<div class=\"CDLParameterType\">"); AppendSyntaxHighlightedTextWithTypeLinks(start, end, output, links, linkTargets); output.Append("</div>"); } } } output.Append("</td>"); } break; case NDMarkup.Iterator.ElementType.DefinitionListDefinitionTag: if (iterator.IsOpeningTag) { output.Append("<td class=\"CDLDefinition\">"); } else { output.Append("</td></tr>"); } break; case NDMarkup.Iterator.ElementType.LinkTag: string linkType = iterator.Property("type"); if (linkType == "email") { AppendEMailLink(iterator, output); } else if (linkType == "url") { AppendURLLink(iterator, output); } else // type == "naturaldocs" { AppendNaturalDocsLink(iterator, output); } break; case NDMarkup.Iterator.ElementType.ImageTag: if (iterator.Property("type") == "standalone") { AppendStandaloneImageLink(iterator, output); } else if (iterator.Property("type") == "inline") { if (inlineImageContent == null) { inlineImageContent = new StringBuilder(); } AppendInlineImageLink(iterator, output, inlineImageContent, imageNumber); imageNumber++; } else { throw new NotImplementedException(); } break; } iterator.Next(); } output.Append("</div>"); }
/* Function: BuildBody */ protected void BuildBody() { htmlOutput.Append("<div class=\"CBody\">"); NDMarkup.Iterator iterator = new NDMarkup.Iterator(topic.Body); bool underParameterHeading = false; string parameterListSymbol = null; while (iterator.IsInBounds) { switch (iterator.Type) { case NDMarkup.Iterator.ElementType.Text: if (topic.Body.IndexOf(" ", iterator.RawTextIndex, iterator.Length) == -1) { iterator.AppendTo(htmlOutput); } else { htmlOutput.Append(iterator.String.ConvertMultipleWhitespaceChars()); } break; case NDMarkup.Iterator.ElementType.ParagraphTag: case NDMarkup.Iterator.ElementType.BulletListTag: case NDMarkup.Iterator.ElementType.BulletListItemTag: case NDMarkup.Iterator.ElementType.BoldTag: case NDMarkup.Iterator.ElementType.ItalicsTag: case NDMarkup.Iterator.ElementType.UnderlineTag: case NDMarkup.Iterator.ElementType.LTEntityChar: case NDMarkup.Iterator.ElementType.GTEntityChar: case NDMarkup.Iterator.ElementType.AmpEntityChar: case NDMarkup.Iterator.ElementType.QuoteEntityChar: iterator.AppendTo(htmlOutput); break; case NDMarkup.Iterator.ElementType.HeadingTag: if (iterator.IsOpeningTag) { htmlOutput.Append("<div class=\"CHeading\">"); underParameterHeading = (iterator.Property("type") == "parameters"); } else { htmlOutput.Append("</div>"); } break; case NDMarkup.Iterator.ElementType.PreTag: string preType = iterator.Property("type"); string preLanguageName = iterator.Property("language"); iterator.Next(); NDMarkup.Iterator startOfCode = iterator; // Because we can assume the NDMarkup is valid, we can assume we were on an opening tag and that we will // run into a closing tag before the end of the text. We can also assume the next pre tag is a closing tag. while (iterator.Type != NDMarkup.Iterator.ElementType.PreTag) { iterator.Next(); } string ndMarkupCode = topic.Body.Substring(startOfCode.RawTextIndex, iterator.RawTextIndex - startOfCode.RawTextIndex); string textCode = NDMarkupCodeToText(ndMarkupCode); htmlOutput.Append("<pre>"); if (preType == "code") { Languages.Language preLanguage = null; if (preLanguageName != null) { // This can return null if the language name is unrecognized. preLanguage = EngineInstance.Languages.FromName(preLanguageName); } if (preLanguage == null) { preLanguage = EngineInstance.Languages.FromID(topic.LanguageID); } Tokenizer code = new Tokenizer(textCode, tabWidth: EngineInstance.Config.TabWidth); preLanguage.SyntaxHighlight(code); BuildSyntaxHighlightedText(code.FirstToken, code.LastToken); } else { string htmlCode = textCode.EntityEncode(); htmlCode = StringExtensions.LineBreakRegex.Replace(htmlCode, "<br />"); htmlOutput.Append(htmlCode); } htmlOutput.Append("</pre>"); break; case NDMarkup.Iterator.ElementType.DefinitionListTag: if (iterator.IsOpeningTag) { htmlOutput.Append("<table class=\"CDefinitionList\">"); } else { htmlOutput.Append("</table>"); } break; case NDMarkup.Iterator.ElementType.DefinitionListEntryTag: case NDMarkup.Iterator.ElementType.DefinitionListSymbolTag: if (iterator.IsOpeningTag) { htmlOutput.Append("<tr><td class=\"CDLEntry\">"); parameterListSymbol = null; // Create anchors for symbols. We are assuming there are enough embedded topics for each <ds> // tag and that they follow their parent topic in order. if (iterator.Type == NDMarkup.Iterator.ElementType.DefinitionListSymbolTag) { #if DEBUG if (embeddedTopics == null || embeddedTopicIndex >= embeddedTopics.Count || embeddedTopics[embeddedTopicIndex].IsEmbedded == false) { throw new Exception("There are not enough embedded topics to build the definition list."); } #endif string topicHashPath = HTMLBuilder.Source_TopicHashPath(embeddedTopics[embeddedTopicIndex], topicPage.IncludeClassInTopicHashPaths); if (topicHashPath != null) { htmlOutput.Append("<a name=\"" + topicHashPath.EntityEncode() + "\"></a>"); } htmlOutput.Append("<a name=\"Topic" + embeddedTopics[embeddedTopicIndex].TopicID + "\"></a>"); embeddedTopicIndex++; } // If we're using a Parameters: heading, store the entry symbol in parameterListSymbol if (underParameterHeading) { NDMarkup.Iterator temp = iterator; temp.Next(); StringBuilder symbol = new StringBuilder(); while (temp.IsInBounds && temp.Type != NDMarkup.Iterator.ElementType.DefinitionListEntryTag && temp.Type != NDMarkup.Iterator.ElementType.DefinitionListSymbolTag) { if (temp.Type == NDMarkup.Iterator.ElementType.Text) { temp.AppendTo(symbol); } temp.Next(); } // If the entry name starts with any combination of $, @, or % characters, strip them off. int firstNonSymbolIndex = 0; while (firstNonSymbolIndex < symbol.Length) { char charAtIndex = symbol[firstNonSymbolIndex]; if (charAtIndex != '$' && charAtIndex != '@' && charAtIndex != '%') { break; } firstNonSymbolIndex++; } if (firstNonSymbolIndex > 0) { symbol.Remove(0, firstNonSymbolIndex); } if (symbol.Length > 0) { parameterListSymbol = symbol.ToString(); } } } else // closing tag { // See if parameterListSymbol matches any of the prototype parameter names if (parameterListSymbol != null && topic.Prototype != null) { TokenIterator start, end; int matchedParameter = -1; for (int i = 0; i < topic.ParsedPrototype.NumberOfParameters; i++) { topic.ParsedPrototype.GetParameterName(i, out start, out end); if (topic.ParsedPrototype.Tokenizer.EqualsTextBetween(parameterListSymbol, true, start, end)) { matchedParameter = i; break; } } // If so, include the type under the entry in the HTML if (matchedParameter != -1) { TokenIterator extraModifierStart, extraModifierEnd, prefixStart, prefixEnd, suffixStart, suffixEnd; topic.ParsedPrototype.GetFullParameterType(matchedParameter, out start, out end, out extraModifierStart, out extraModifierEnd, out prefixStart, out prefixEnd, out suffixStart, out suffixEnd); if (start < end && // Don't include single symbol types (end.RawTextIndex - start.RawTextIndex > 1 || (start.Character != '$' && start.Character != '@' && start.Character != '%'))) { htmlOutput.Append("<div class=\"CDLParameterType\">"); if (extraModifierStart < extraModifierEnd) { BuildTypeLinkedAndSyntaxHighlightedText(extraModifierStart, extraModifierEnd); htmlOutput.Append(' '); } BuildTypeLinkedAndSyntaxHighlightedText(start, end); BuildTypeLinkedAndSyntaxHighlightedText(prefixStart, prefixEnd); BuildTypeLinkedAndSyntaxHighlightedText(suffixStart, suffixEnd); htmlOutput.Append("</div>"); } } } htmlOutput.Append("</td>"); } break; case NDMarkup.Iterator.ElementType.DefinitionListDefinitionTag: if (iterator.IsOpeningTag) { htmlOutput.Append("<td class=\"CDLDefinition\">"); } else { htmlOutput.Append("</td></tr>"); } break; case NDMarkup.Iterator.ElementType.LinkTag: string linkType = iterator.Property("type"); if (linkType == "email") { BuildEMailLink(iterator); } else if (linkType == "url") { BuildURLLink(iterator); } else // type == "naturaldocs" { BuildNaturalDocsLink(iterator); } break; case NDMarkup.Iterator.ElementType.ImageTag: // xxx if (iterator.Property("type") == "standalone") { htmlOutput.Append("<p>"); } htmlOutput.Append(iterator.Property("originaltext").ToHTML()); if (iterator.Property("type") == "standalone") { htmlOutput.Append("</p>"); } break; } iterator.Next(); } htmlOutput.Append("</div>"); }