Serenity Operating System
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}