kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/doc/html_renderer.cc (about) 1 /* 2 * Copyright 2016 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "kythe/cxx/doc/html_renderer.h" 18 19 #include <cstddef> 20 #include <map> 21 #include <stack> 22 #include <string> 23 #include <utility> 24 #include <vector> 25 26 #include "kythe/cxx/doc/markup_handler.h" 27 #include "kythe/proto/common.pb.h" 28 #include "kythe/proto/xref.pb.h" 29 30 namespace kythe { 31 namespace { 32 /// Don't recurse more than this many times when rendering MarkedSource. 33 constexpr size_t kMaxRenderDepth = 20; 34 35 /// \brief A RAII class to deal with styled div/span tags. 36 class CssTag { 37 public: 38 enum class Kind { Div, Span, Pre }; 39 /// \param kind the kind of tag to open. 40 /// \param style the CSS style to apply to the tag. Must be escaped. 41 /// \param buffer must outlive CssTag. 42 CssTag(Kind kind, const std::string& style, std::string* buffer) 43 : kind_(kind), buffer_(buffer) { 44 OpenTag(kind, style, buffer); 45 } 46 ~CssTag() { CloseTag(kind_, buffer_); } 47 static void OpenTag(Kind kind, const std::string& style, 48 std::string* buffer) { 49 buffer->append("<"); 50 buffer->append(label(kind)); 51 buffer->append(" class=\""); 52 buffer->append(style); 53 buffer->append("\">"); 54 } 55 static void CloseTag(Kind kind, std::string* buffer) { 56 buffer->append("</"); 57 buffer->append(label(kind)); 58 buffer->append(">"); 59 } 60 static const char* label(Kind kind) { 61 return kind == Kind::Div ? "div" : (kind == Kind::Span ? "span" : "pre"); 62 } 63 CssTag(const CssTag& o) = delete; 64 65 private: 66 Kind kind_; 67 std::string* buffer_; 68 }; 69 70 std::string RenderPrintable(const HtmlRendererOptions& options, 71 const std::vector<MarkupHandler>& handlers, 72 const proto::Printable& printable_proto) { 73 Printable printable(printable_proto); 74 auto markdoc = HandleMarkup(handlers, printable); 75 return RenderHtml(options, markdoc); 76 } 77 78 /// \brief Appends a representation of `c` to `buffer`, possibly using an HTML 79 /// entity instead of a literal character. 80 void AppendEscapedHtmlCharacter(std::string* buffer, char c) { 81 switch (c) { 82 case '<': 83 buffer->append("<"); 84 break; 85 case '>': 86 buffer->append(">"); 87 break; 88 case '&': 89 buffer->append("&"); 90 break; 91 default: 92 buffer->push_back(c); 93 } 94 } 95 96 /// \brief Content of a tagged block (e.g., a @param or a @returns). 97 struct TaggedText { 98 std::string buffer; 99 }; 100 101 /// \brief A map from tag block IDs and ordinals to their content. 102 using TagBlocks = 103 std::map<std::pair<PrintableSpan::TagBlockId, size_t>, TaggedText>; 104 105 /// \brief Renders the content of `tag_blocks` to the block `out`. 106 void RenderTagBlocks(const HtmlRendererOptions& options, 107 const TagBlocks& tag_blocks, TaggedText* out) { 108 bool first_block = true; 109 PrintableSpan::TagBlockId block_id; 110 for (const auto& block : tag_blocks) { 111 if (first_block || block_id != block.first.first) { 112 if (!first_block) { 113 out->buffer.append("</ul>"); 114 CssTag::CloseTag(CssTag::Kind::Div, &out->buffer); 115 } 116 block_id = block.first.first; 117 first_block = false; 118 { 119 CssTag title(CssTag::Kind::Div, options.tag_section_title_div, 120 &out->buffer); 121 switch (block.first.first) { 122 case PrintableSpan::TagBlockId::Author: 123 out->buffer.append("Author"); 124 break; 125 case PrintableSpan::TagBlockId::Returns: 126 out->buffer.append("Returns"); 127 break; 128 case PrintableSpan::TagBlockId::Since: 129 out->buffer.append("Since"); 130 break; 131 case PrintableSpan::TagBlockId::Version: 132 out->buffer.append("Version"); 133 break; 134 case PrintableSpan::TagBlockId::Throws: 135 out->buffer.append("Throws"); 136 break; 137 case PrintableSpan::TagBlockId::Param: 138 out->buffer.append("Parameter"); 139 break; 140 case PrintableSpan::TagBlockId::See: 141 out->buffer.append("See"); 142 break; 143 } 144 } 145 CssTag::OpenTag(CssTag::Kind::Div, options.tag_section_content_div, 146 &out->buffer); 147 out->buffer.append("<ul>"); 148 } 149 out->buffer.append("<li>"); 150 out->buffer.append(block.second.buffer); 151 out->buffer.append("</li>"); 152 } 153 if (!first_block) { 154 // We've opened a ul and a div that we need to close. 155 out->buffer.append("</ul>"); 156 CssTag::CloseTag(CssTag::Kind::Div, &out->buffer); 157 } 158 } 159 160 const char* TagNameForStyle(PrintableSpan::Style style) { 161 switch (style) { 162 case PrintableSpan::Style::Bold: 163 return "b"; 164 case PrintableSpan::Style::Italic: 165 return "i"; 166 case PrintableSpan::Style::H1: 167 return "h1"; 168 case PrintableSpan::Style::H2: 169 return "h2"; 170 case PrintableSpan::Style::H3: 171 return "h3"; 172 case PrintableSpan::Style::H4: 173 return "h4"; 174 case PrintableSpan::Style::H5: 175 return "h5"; 176 case PrintableSpan::Style::H6: 177 return "h6"; 178 case PrintableSpan::Style::Big: 179 return "big"; 180 case PrintableSpan::Style::Small: 181 return "small"; 182 case PrintableSpan::Style::Blockquote: 183 return "blockquote"; 184 case PrintableSpan::Style::Superscript: 185 return "sup"; 186 case PrintableSpan::Style::Subscript: 187 return "sub"; 188 case PrintableSpan::Style::Underline: 189 return "ul"; 190 } 191 } 192 193 template <typename SourceString> 194 void AppendEscapedHtmlString(const SourceString& source, std::string* dest) { 195 dest->reserve(dest->size() + source.size()); 196 for (char c : source) { 197 AppendEscapedHtmlCharacter(dest, c); 198 } 199 } 200 201 /// Target buffer for RenderSimpleIdentifier. 202 class RenderSimpleIdentifierTarget { 203 public: 204 /// \brief Escapes and appends `source` to the buffer. 205 template <typename SourceString> 206 void Append(const SourceString& source) { 207 if (!prepend_buffer_.empty() && !source.empty()) { 208 AppendEscapedHtmlString(prepend_buffer_, &buffer_); 209 prepend_buffer_.clear(); 210 } 211 AppendEscapedHtmlString(source, &buffer_); 212 } 213 /// \brief Escapes and adds `source` before the (non-empty) text that would 214 /// be added by the next call to `Append`. 215 template <typename SourceString> 216 void AppendFinalListToken(const SourceString& source) { 217 prepend_buffer_.append(std::string(source)); 218 } 219 const std::string buffer() const { return buffer_; } 220 void AppendRaw(const std::string& text) { buffer_.append(text); } 221 /// \brief Make sure that there's a space between the current content of the 222 /// buffer and whatever is appended to it later on. 223 void AppendHeuristicSpace() { 224 if (!buffer_.empty() && buffer_[buffer_.size() - 1] != ' ') { 225 prepend_buffer_.push_back(' '); 226 } 227 } 228 229 private: 230 /// The buffer used to hold escaped data. 231 std::string buffer_; 232 /// Unescaped text that should be escaped and appended before any other text 233 /// is appended to `buffer_`. 234 std::string prepend_buffer_; 235 }; 236 237 /// State while recursing through MarkedSource for RenderSimpleIdentifier. 238 struct RenderSimpleIdentifierState { 239 const HtmlRendererOptions* options = nullptr; 240 bool render_identifier = false; 241 bool render_context = false; 242 bool render_types = false; 243 bool render_parameters = false; 244 bool render_initializer = false; 245 bool render_modifier = false; 246 bool in_identifier = false; 247 bool in_context = false; 248 bool in_parameter = false; 249 bool in_type = false; 250 bool in_initializer = false; 251 bool in_modifier = false; 252 bool linkify = false; 253 std::string base_ticket; 254 std::string get_link(const proto::common::MarkedSource& sig) { 255 if (options == nullptr || !linkify) { 256 return ""; 257 } 258 std::string link; 259 auto try_link = [&](const std::string& ticket) { 260 if (const auto* node_info = options->node_info(ticket)) { 261 if (const auto* anchor = 262 options->anchor_for_ticket(node_info->definition())) { 263 link = options->make_semantic_link_uri(*anchor, ticket); 264 if (link.empty()) { 265 link = options->make_link_uri(*anchor); 266 } 267 } 268 } 269 }; 270 for (const auto& plink : sig.link()) { 271 for (const auto& ptick : plink.definition()) { 272 try_link(ptick); 273 } 274 } 275 if (link.empty() && should_infer_link()) { 276 try_link(base_ticket); 277 } 278 return link; 279 } 280 bool should_infer_link() const { 281 return (in_identifier && !base_ticket.empty() && !in_context && 282 !in_parameter && !in_type && !in_initializer && !in_modifier); 283 } 284 bool should_render(const proto::common::MarkedSource& node) const { 285 return (render_context && in_context) || 286 (render_identifier && in_identifier) || 287 (render_parameters && in_parameter) || (render_types && in_type) || 288 (render_initializer && in_initializer) || 289 (render_modifier && in_modifier); 290 } 291 bool will_render(const proto::common::MarkedSource& child, size_t depth) { 292 if (depth >= kMaxRenderDepth) return false; 293 switch (child.kind()) { 294 case proto::common::MarkedSource::IDENTIFIER: 295 return true; 296 case proto::common::MarkedSource::BOX: 297 return true; 298 case proto::common::MarkedSource::PARAMETER: 299 return render_parameters; 300 case proto::common::MarkedSource::TYPE: 301 return render_types; 302 case proto::common::MarkedSource::CONTEXT: 303 return render_context; 304 case proto::common::MarkedSource::INITIALIZER: 305 return render_initializer; 306 case proto::common::MarkedSource::MODIFIER: 307 return render_modifier; 308 default: 309 return false; 310 } 311 } 312 }; 313 314 void RenderSimpleIdentifier(const proto::common::MarkedSource& sig, 315 RenderSimpleIdentifierTarget* out, 316 RenderSimpleIdentifierState state, size_t depth) { 317 if (depth >= kMaxRenderDepth) { 318 return; 319 } 320 switch (sig.kind()) { 321 case proto::common::MarkedSource::IDENTIFIER: 322 state.in_identifier = true; 323 break; 324 case proto::common::MarkedSource::PARAMETER: 325 if (!state.render_parameters) { 326 return; 327 } 328 state.in_parameter = true; 329 break; 330 case proto::common::MarkedSource::TYPE: 331 if (!state.render_types) { 332 return; 333 } 334 state.in_type = true; 335 break; 336 case proto::common::MarkedSource::CONTEXT: 337 if (!state.render_context) { 338 return; 339 } 340 state.in_context = true; 341 break; 342 case proto::common::MarkedSource::INITIALIZER: 343 if (!state.render_initializer) { 344 return; 345 } 346 state.in_initializer = true; 347 break; 348 case proto::common::MarkedSource::BOX: 349 break; 350 case proto::common::MarkedSource::MODIFIER: 351 if (!state.render_modifier) { 352 return; 353 } 354 state.in_modifier = true; 355 break; 356 default: 357 return; 358 } 359 bool has_open_link = false; 360 if (state.should_render(sig)) { 361 std::string link_text = state.get_link(sig); 362 if (!link_text.empty()) { 363 out->AppendRaw("<a href=\""); 364 out->AppendRaw(link_text); 365 out->AppendRaw("\" title=\""); 366 { 367 RenderSimpleIdentifierTarget target; 368 RenderSimpleIdentifierState state; 369 state.render_identifier = true; 370 state.render_context = true; 371 state.render_types = true; 372 RenderSimpleIdentifier(sig, &target, state, 0); 373 out->AppendRaw(target.buffer()); 374 } 375 out->AppendRaw("\">"); 376 has_open_link = true; 377 } 378 out->Append(sig.pre_text()); 379 } 380 int last_rendered_child = -1; 381 for (int child = 0; child < sig.child_size(); ++child) { 382 if (state.will_render(sig.child(child), depth + 1)) { 383 last_rendered_child = child; 384 } 385 } 386 for (int child = 0; child < sig.child_size(); ++child) { 387 if (state.will_render(sig.child(child), depth + 1)) { 388 RenderSimpleIdentifier(sig.child(child), out, state, depth + 1); 389 if (state.should_render(sig)) { 390 if (last_rendered_child > child) { 391 out->Append(sig.post_child_text()); 392 } else if (sig.add_final_list_token()) { 393 out->AppendFinalListToken(sig.post_child_text()); 394 } 395 } 396 } 397 } 398 if (state.should_render(sig)) { 399 out->Append(sig.post_text()); 400 if (has_open_link) { 401 out->AppendRaw("</a>"); 402 } 403 if (sig.kind() == proto::common::MarkedSource::TYPE) { 404 out->AppendHeuristicSpace(); 405 } 406 } 407 } 408 409 /// Render identifiers underneath PARAMETER nodes with no other non-BOXes in 410 /// between. 411 void RenderSimpleParams(const proto::common::MarkedSource& sig, 412 std::vector<std::string>* out, size_t depth) { 413 if (depth >= kMaxRenderDepth) { 414 return; 415 } 416 switch (sig.kind()) { 417 case proto::common::MarkedSource::BOX: 418 for (const auto& child : sig.child()) { 419 RenderSimpleParams(child, out, depth + 1); 420 } 421 break; 422 case proto::common::MarkedSource::PARAMETER: 423 for (const auto& child : sig.child()) { 424 out->emplace_back(); 425 RenderSimpleIdentifierTarget target; 426 RenderSimpleIdentifierState state; 427 state.render_identifier = true; 428 RenderSimpleIdentifier(child, &target, state, depth + 1); 429 out->back().append(target.buffer()); 430 } 431 break; 432 default: 433 break; 434 } 435 } 436 } // anonymous namespace 437 438 const proto::common::NodeInfo* DocumentHtmlRendererOptions::node_info( 439 const std::string& ticket) const { 440 auto node = document_.nodes().find(ticket); 441 return node == document_.nodes().end() ? nullptr : &node->second; 442 } 443 444 const proto::Anchor* DocumentHtmlRendererOptions::anchor_for_ticket( 445 const std::string& ticket) const { 446 auto anchor = document_.definition_locations().find(ticket); 447 return anchor == document_.definition_locations().end() ? nullptr 448 : &anchor->second; 449 } 450 451 std::string RenderSignature(const HtmlRendererOptions& options, 452 const proto::common::MarkedSource& sig, 453 bool linkify, const std::string& base_ticket) { 454 RenderSimpleIdentifierTarget target; 455 RenderSimpleIdentifierState state; 456 state.render_identifier = true; 457 state.render_types = true; 458 state.render_parameters = true; 459 state.render_modifier = true; 460 state.linkify = linkify; 461 state.options = &options; 462 state.base_ticket = base_ticket; 463 RenderSimpleIdentifier(sig, &target, state, 0); 464 return target.buffer(); 465 } 466 467 std::string RenderSimpleIdentifier(const proto::common::MarkedSource& sig) { 468 RenderSimpleIdentifierTarget target; 469 RenderSimpleIdentifierState state; 470 state.render_identifier = true; 471 RenderSimpleIdentifier(sig, &target, state, 0); 472 return target.buffer(); 473 } 474 475 std::string RenderSimpleQualifiedName(const proto::common::MarkedSource& sig, 476 bool include_identifier) { 477 RenderSimpleIdentifierTarget target; 478 RenderSimpleIdentifierState state; 479 state.render_identifier = include_identifier; 480 state.render_context = true; 481 RenderSimpleIdentifier(sig, &target, state, 0); 482 return target.buffer(); 483 } 484 485 std::string RenderInitializer(const proto::common::MarkedSource& sig) { 486 RenderSimpleIdentifierTarget target; 487 RenderSimpleIdentifierState state; 488 state.render_initializer = true; 489 RenderSimpleIdentifier(sig, &target, state, 0); 490 return target.buffer(); 491 } 492 493 std::vector<std::string> RenderSimpleParams( 494 const proto::common::MarkedSource& sig) { 495 std::vector<std::string> result; 496 RenderSimpleParams(sig, &result, 0); 497 return result; 498 } 499 500 std::string RenderHtml(const HtmlRendererOptions& options, 501 const Printable& printable) { 502 struct OpenSpan { 503 const PrintableSpan* span; 504 bool valid; 505 }; 506 struct FormatState { 507 bool in_pre_block; 508 }; 509 std::stack<OpenSpan> open_spans; 510 // To avoid entering multiple <pre> blocks, we keep track of whether we're 511 // currently in a <pre> context. This does not affect escaping, since 512 // tags can appear in a <pre>. 513 std::stack<FormatState> format_states; 514 // Elements on `open_tags` point to values of `tag_blocks`. The element on 515 // top of the stack is the tag block whose buffer we're currently appending 516 // data to (if any). This stack should usually have one or zero elements, 517 // given the syntactic restrictions of the markup languages we're translating 518 // from. 519 std::stack<TaggedText*> open_tags; 520 std::map<std::pair<PrintableSpan::TagBlockId, size_t>, TaggedText> tag_blocks; 521 TaggedText main_text; 522 // `out` points to either `main_text` if `open_tags` is empty or a value of 523 // `tag_blocks` (particularly, the one referenced by the top of `open_tags`) 524 // if the stack is non-empty. 525 TaggedText* out = &main_text; 526 PrintableSpan default_span(0, printable.text().size(), 527 PrintableSpan::Semantic::Raw); 528 open_spans.push(OpenSpan{&default_span, true}); 529 format_states.push(FormatState{false}); 530 size_t current_span = 0; 531 for (size_t i = 0; i <= printable.text().size(); ++i) { 532 // Normalized PrintableSpans have all empty or negative-length spans 533 // dropped. 534 while (!open_spans.empty() && open_spans.top().span->end() == i) { 535 switch (open_spans.top().span->semantic()) { 536 case PrintableSpan::Semantic::TagBlock: { 537 if (!open_tags.empty()) { 538 open_tags.pop(); 539 } 540 out = open_tags.empty() ? &main_text : open_tags.top(); 541 } break; 542 case PrintableSpan::Semantic::UriLink: 543 out->buffer.append("</a>"); 544 break; 545 case PrintableSpan::Semantic::Uri: 546 out->buffer.append("\">"); 547 break; 548 case PrintableSpan::Semantic::Link: 549 if (open_spans.top().valid) { 550 out->buffer.append("</a>"); 551 } 552 break; 553 case PrintableSpan::Semantic::CodeRef: 554 out->buffer.append("</tt>"); 555 break; 556 case PrintableSpan::Semantic::Paragraph: 557 out->buffer.append("</p>"); 558 break; 559 case PrintableSpan::Semantic::ListItem: 560 out->buffer.append("</li>"); 561 break; 562 case PrintableSpan::Semantic::UnorderedList: 563 out->buffer.append("</ul>"); 564 break; 565 case PrintableSpan::Semantic::Styled: 566 out->buffer.append("</"); 567 out->buffer.append(TagNameForStyle(open_spans.top().span->style())); 568 out->buffer.append(">"); 569 break; 570 case PrintableSpan::Semantic::CodeBlock: 571 if (!format_states.empty()) { 572 format_states.pop(); 573 if (!format_states.empty() && !format_states.top().in_pre_block) { 574 out->buffer.append("</pre>"); 575 } 576 } 577 break; 578 default: 579 break; 580 } 581 open_spans.pop(); 582 } 583 if (open_spans.empty() || i == printable.text().size()) { 584 // default_span is first to enter and last to leave; there also may 585 // be no empty spans. 586 break; 587 } 588 while (current_span < printable.spans().size() && 589 printable.spans().span(current_span).begin() == i) { 590 open_spans.push({&printable.spans().span(current_span), true}); 591 ++current_span; 592 switch (open_spans.top().span->semantic()) { 593 case PrintableSpan::Semantic::TagBlock: { 594 auto block = open_spans.top().span->tag_block(); 595 out = &tag_blocks[block]; 596 open_tags.push(out); 597 } break; 598 case PrintableSpan::Semantic::UriLink: 599 out->buffer.append("<a "); 600 break; 601 case PrintableSpan::Semantic::Uri: 602 out->buffer.append("href=\""); 603 break; 604 case PrintableSpan::Semantic::Link: 605 open_spans.top().valid = false; // Invalid until proven otherwise. 606 if (open_spans.top().span->link().definition_size() != 0) { 607 const auto& definition = 608 open_spans.top().span->link().definition(0); 609 if (const auto* def_info = options.node_info(definition)) { 610 if (!def_info->definition().empty()) { 611 if (const auto* def_anchor = 612 options.anchor_for_ticket(def_info->definition())) { 613 open_spans.top().valid = true; 614 out->buffer.append("<a href=\""); 615 auto link_uri = 616 options.make_semantic_link_uri(*def_anchor, definition); 617 if (link_uri.empty()) { 618 link_uri = options.make_link_uri(*def_anchor); 619 } 620 // + 2 for the closing ">. 621 out->buffer.reserve(out->buffer.size() + link_uri.size() + 2); 622 for (auto c : link_uri) { 623 AppendEscapedHtmlCharacter(&out->buffer, c); 624 } 625 out->buffer.append("\">"); 626 } 627 } 628 } 629 } 630 break; 631 case PrintableSpan::Semantic::CodeRef: 632 out->buffer.append("<tt>"); 633 break; 634 case PrintableSpan::Semantic::Paragraph: 635 out->buffer.append("<p>"); 636 break; 637 case PrintableSpan::Semantic::ListItem: 638 out->buffer.append("<li>"); 639 break; 640 case PrintableSpan::Semantic::UnorderedList: 641 out->buffer.append("<ul>"); 642 break; 643 case PrintableSpan::Semantic::Styled: 644 out->buffer.append("<"); 645 out->buffer.append(TagNameForStyle(open_spans.top().span->style())); 646 out->buffer.append(">"); 647 break; 648 case PrintableSpan::Semantic::CodeBlock: 649 if (!format_states.empty() && !format_states.top().in_pre_block) { 650 out->buffer.append("<pre>"); 651 } 652 format_states.push(FormatState{true}); 653 break; 654 default: 655 break; 656 } 657 } 658 if (open_spans.top().span->semantic() == PrintableSpan::Semantic::Escaped) { 659 out->buffer.push_back(printable.text()[i]); 660 } else if (open_spans.top().span->semantic() != 661 PrintableSpan::Semantic::Markup) { 662 char c = printable.text()[i]; 663 AppendEscapedHtmlCharacter(&out->buffer, c); 664 } 665 } 666 RenderTagBlocks(options, tag_blocks, out); 667 return out->buffer; 668 } 669 670 std::string RenderDocument( 671 const HtmlRendererOptions& options, 672 const std::vector<MarkupHandler>& handlers, 673 const proto::DocumentationReply::Document& document) { 674 std::string text_out; 675 { 676 CssTag root(CssTag::Kind::Div, options.doc_div, &text_out); 677 { 678 CssTag sig(CssTag::Kind::Div, options.signature_div, &text_out); 679 { 680 CssTag type_div(CssTag::Kind::Div, options.type_name_div, &text_out); 681 CssTag type_name(CssTag::Kind::Span, options.name_span, &text_out); 682 text_out.append(RenderSignature(options, document.marked_source(), true, 683 document.ticket())); 684 } 685 text_out.append(options.make_post_signature_markup( 686 document.ticket(), document.marked_source())); 687 { 688 CssTag detail_div(CssTag::Kind::Div, options.sig_detail_div, &text_out); 689 auto kind_name = options.kind_name(document.ticket()); 690 if (!kind_name.empty()) { 691 AppendEscapedHtmlString(kind_name, &text_out); 692 text_out.append(" "); 693 } 694 auto declared_context = 695 RenderSimpleQualifiedName(document.marked_source(), false); 696 if (declared_context.empty()) { 697 if (const auto* node_info = options.node_info(document.ticket())) { 698 if (const auto* anchor = 699 options.anchor_for_ticket(node_info->definition())) { 700 auto anchor_text = options.format_location(*anchor); 701 if (!anchor_text.empty()) { 702 text_out.append("declared in "); 703 auto anchor_link = 704 options.make_semantic_link_uri(*anchor, document.ticket()); 705 if (!anchor_link.empty()) { 706 text_out.append("<a href=\""); 707 AppendEscapedHtmlString(anchor_link, &text_out); 708 text_out.append("\">"); 709 } 710 AppendEscapedHtmlString(anchor_text, &text_out); 711 if (!anchor_link.empty()) { 712 text_out.append("</a>"); 713 } 714 } 715 } 716 } 717 } else { 718 text_out.append("declared by "); 719 text_out.append(declared_context); 720 } 721 } 722 } 723 auto initializer = RenderInitializer(document.marked_source()); 724 if (!initializer.empty()) { 725 CssTag init_div(CssTag::Kind::Div, options.initializer_div, &text_out); 726 text_out.append("Initializer: "); 727 bool multiline = initializer.find('\n') != decltype(initializer)::npos; 728 CssTag code_div(CssTag::Kind::Pre, 729 multiline ? options.initializer_multiline_pre 730 : options.initializer_pre, 731 &text_out); 732 text_out.append(initializer); 733 } 734 { 735 CssTag content_div(CssTag::Kind::Div, options.content_div, &text_out); 736 text_out.append(RenderPrintable(options, handlers, document.text())); 737 } 738 for (const auto& child : document.children()) { 739 text_out.append(RenderDocument(options, handlers, child)); 740 } 741 } 742 return text_out; 743 } 744 745 } // namespace kythe