ContainerBlock.cpp 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /*
  2. * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Forward.h>
  7. #include <LibMarkdown/BlockQuote.h>
  8. #include <LibMarkdown/CodeBlock.h>
  9. #include <LibMarkdown/ContainerBlock.h>
  10. #include <LibMarkdown/Heading.h>
  11. #include <LibMarkdown/HorizontalRule.h>
  12. #include <LibMarkdown/List.h>
  13. #include <LibMarkdown/Paragraph.h>
  14. #include <LibMarkdown/Table.h>
  15. #include <LibMarkdown/Visitor.h>
  16. namespace Markdown {
  17. ByteString ContainerBlock::render_to_html(bool tight) const
  18. {
  19. StringBuilder builder;
  20. for (size_t i = 0; i + 1 < m_blocks.size(); ++i) {
  21. auto s = m_blocks[i]->render_to_html(tight);
  22. builder.append(s);
  23. }
  24. // I don't like this edge case.
  25. if (m_blocks.size() != 0) {
  26. auto& block = m_blocks[m_blocks.size() - 1];
  27. auto s = block->render_to_html(tight);
  28. if (tight && dynamic_cast<Paragraph const*>(block.ptr())) {
  29. builder.append(s.substring_view(0, s.length() - 1));
  30. } else {
  31. builder.append(s);
  32. }
  33. }
  34. return builder.to_byte_string();
  35. }
  36. Vector<ByteString> ContainerBlock::render_lines_for_terminal(size_t view_width) const
  37. {
  38. Vector<ByteString> lines;
  39. for (auto& block : m_blocks) {
  40. for (auto& line : block->render_lines_for_terminal(view_width))
  41. lines.append(move(line));
  42. }
  43. return lines;
  44. }
  45. RecursionDecision ContainerBlock::walk(Visitor& visitor) const
  46. {
  47. RecursionDecision rd = visitor.visit(*this);
  48. if (rd != RecursionDecision::Recurse)
  49. return rd;
  50. for (auto const& block : m_blocks) {
  51. rd = block->walk(visitor);
  52. if (rd == RecursionDecision::Break)
  53. return rd;
  54. }
  55. return RecursionDecision::Continue;
  56. }
  57. template<class CodeBlock>
  58. static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks, Heading* current_section)
  59. {
  60. OwnPtr<CodeBlock> block = CodeBlock::parse(lines, current_section);
  61. if (!block)
  62. return false;
  63. blocks.append(block.release_nonnull());
  64. return true;
  65. }
  66. template<typename BlockType>
  67. static bool try_parse_block(LineIterator& lines, Vector<NonnullOwnPtr<Block>>& blocks)
  68. {
  69. OwnPtr<BlockType> block = BlockType::parse(lines);
  70. if (!block)
  71. return false;
  72. blocks.append(block.release_nonnull());
  73. return true;
  74. }
  75. OwnPtr<ContainerBlock> ContainerBlock::parse(LineIterator& lines)
  76. {
  77. Vector<NonnullOwnPtr<Block>> blocks;
  78. StringBuilder paragraph_text;
  79. Heading* current_section = nullptr;
  80. auto flush_paragraph = [&] {
  81. if (paragraph_text.is_empty())
  82. return;
  83. auto paragraph = make<Paragraph>(Text::parse(paragraph_text.to_byte_string()));
  84. blocks.append(move(paragraph));
  85. paragraph_text.clear();
  86. };
  87. bool has_blank_lines = false;
  88. bool has_trailing_blank_lines = false;
  89. while (true) {
  90. if (lines.is_end())
  91. break;
  92. if ((*lines).is_whitespace()) {
  93. has_trailing_blank_lines = true;
  94. ++lines;
  95. flush_paragraph();
  96. continue;
  97. } else {
  98. has_blank_lines = has_blank_lines || has_trailing_blank_lines;
  99. }
  100. bool heading = false;
  101. if ((heading = try_parse_block<Heading>(lines, blocks)))
  102. current_section = dynamic_cast<Heading*>(blocks.last().ptr());
  103. bool any = heading
  104. || try_parse_block<Table>(lines, blocks)
  105. || try_parse_block<HorizontalRule>(lines, blocks)
  106. || try_parse_block<List>(lines, blocks)
  107. // CodeBlock needs to know the current section's name for proper indentation
  108. || try_parse_block<CodeBlock>(lines, blocks, current_section)
  109. || try_parse_block<CommentBlock>(lines, blocks)
  110. || try_parse_block<BlockQuote>(lines, blocks);
  111. if (any) {
  112. if (!paragraph_text.is_empty()) {
  113. auto last_block = blocks.take_last();
  114. flush_paragraph();
  115. blocks.append(move(last_block));
  116. }
  117. continue;
  118. }
  119. if (!paragraph_text.is_empty())
  120. paragraph_text.append('\n');
  121. paragraph_text.append(*lines++);
  122. }
  123. flush_paragraph();
  124. return make<ContainerBlock>(move(blocks), has_blank_lines, has_trailing_blank_lines);
  125. }
  126. }