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