/* 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: 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: 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>"); }