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("&lt;");
    84        break;
    85      case '>':
    86        buffer->append("&gt;");
    87        break;
    88      case '&':
    89        buffer->append("&amp;");
    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