/src/serenity/Userland/Libraries/LibMarkdown/List.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org> |
3 | | * Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Forward.h> |
9 | | #include <AK/StringBuilder.h> |
10 | | #include <LibMarkdown/List.h> |
11 | | #include <LibMarkdown/Paragraph.h> |
12 | | #include <LibMarkdown/Visitor.h> |
13 | | |
14 | | namespace Markdown { |
15 | | |
16 | | ByteString List::render_to_html(bool) const |
17 | 0 | { |
18 | 0 | StringBuilder builder; |
19 | |
|
20 | 0 | char const* tag = m_is_ordered ? "ol" : "ul"; |
21 | 0 | builder.appendff("<{}", tag); |
22 | |
|
23 | 0 | if (m_start_number != 1) |
24 | 0 | builder.appendff(" start=\"{}\"", m_start_number); |
25 | |
|
26 | 0 | builder.append(">\n"sv); |
27 | |
|
28 | 0 | for (auto& item : m_items) { |
29 | 0 | builder.append("<li>"sv); |
30 | 0 | if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast<Paragraph const*>(item->blocks()[0].ptr()))) |
31 | 0 | builder.append('\n'); |
32 | 0 | builder.append(item->render_to_html(m_is_tight)); |
33 | 0 | builder.append("</li>\n"sv); |
34 | 0 | } |
35 | |
|
36 | 0 | builder.appendff("</{}>\n", tag); |
37 | |
|
38 | 0 | return builder.to_byte_string(); |
39 | 0 | } |
40 | | |
41 | | Vector<ByteString> List::render_lines_for_terminal(size_t view_width) const |
42 | 0 | { |
43 | 0 | Vector<ByteString> lines; |
44 | |
|
45 | 0 | int i = 0; |
46 | 0 | for (auto& item : m_items) { |
47 | 0 | auto item_lines = item->render_lines_for_terminal(view_width); |
48 | 0 | auto first_line = item_lines.take_first(); |
49 | |
|
50 | 0 | StringBuilder builder; |
51 | 0 | builder.append(" "sv); |
52 | 0 | if (m_is_ordered) |
53 | 0 | builder.appendff("{}.", ++i); |
54 | 0 | else |
55 | 0 | builder.append('*'); |
56 | 0 | auto item_indentation = builder.length(); |
57 | |
|
58 | 0 | builder.append(first_line); |
59 | |
|
60 | 0 | lines.append(builder.to_byte_string()); |
61 | |
|
62 | 0 | for (auto& line : item_lines) { |
63 | 0 | builder.clear(); |
64 | 0 | builder.append(ByteString::repeated(' ', item_indentation)); |
65 | 0 | builder.append(line); |
66 | 0 | lines.append(builder.to_byte_string()); |
67 | 0 | } |
68 | 0 | } |
69 | |
|
70 | 0 | return lines; |
71 | 0 | } |
72 | | |
73 | | RecursionDecision List::walk(Visitor& visitor) const |
74 | 0 | { |
75 | 0 | RecursionDecision rd = visitor.visit(*this); |
76 | 0 | if (rd != RecursionDecision::Recurse) |
77 | 0 | return rd; |
78 | | |
79 | 0 | for (auto const& block : m_items) { |
80 | 0 | rd = block->walk(visitor); |
81 | 0 | if (rd == RecursionDecision::Break) |
82 | 0 | return rd; |
83 | 0 | } |
84 | | |
85 | 0 | return RecursionDecision::Continue; |
86 | 0 | } |
87 | | |
88 | | OwnPtr<List> List::parse(LineIterator& lines) |
89 | 19.1M | { |
90 | 19.1M | Vector<OwnPtr<ContainerBlock>> items; |
91 | | |
92 | 19.1M | bool first = true; |
93 | 19.1M | bool is_ordered = false; |
94 | | |
95 | 19.1M | bool is_tight = true; |
96 | 19.1M | bool has_trailing_blank_lines = false; |
97 | 19.1M | size_t start_number = 1; |
98 | | |
99 | 19.7M | while (!lines.is_end()) { |
100 | | |
101 | 19.6M | size_t offset = 0; |
102 | | |
103 | 19.6M | StringView line = *lines; |
104 | | |
105 | 19.6M | bool appears_unordered = false; |
106 | | |
107 | 25.9M | while (offset < line.length() && line[offset] == ' ') |
108 | 6.25M | ++offset; |
109 | | |
110 | 19.6M | if (offset + 2 <= line.length()) { |
111 | 6.08M | if (line[offset + 1] == ' ' && (line[offset] == '*' || line[offset] == '-' || line[offset] == '+')) { |
112 | 555k | appears_unordered = true; |
113 | 555k | offset++; |
114 | 555k | } |
115 | 6.08M | } |
116 | | |
117 | 19.6M | bool appears_ordered = false; |
118 | 19.6M | for (size_t i = offset; i < 10 && i < line.length(); i++) { |
119 | 19.6M | char ch = line[i]; |
120 | 19.6M | if ('0' <= ch && ch <= '9') |
121 | 23.6k | continue; |
122 | 19.6M | if (ch == '.' || ch == ')') |
123 | 7.38M | if (i + 1 < line.length() && line[i + 1] == ' ') { |
124 | 237k | auto maybe_start_number = line.substring_view(offset, i - offset).to_number<size_t>(); |
125 | 237k | if (!maybe_start_number.has_value()) |
126 | 225k | break; |
127 | 11.8k | if (first) |
128 | 11.3k | start_number = maybe_start_number.value(); |
129 | 11.8k | appears_ordered = true; |
130 | 11.8k | offset = i + 1; |
131 | 11.8k | } |
132 | 19.4M | break; |
133 | 19.6M | } |
134 | | |
135 | 19.6M | VERIFY(!(appears_unordered && appears_ordered)); |
136 | 19.6M | if (!appears_unordered && !appears_ordered) { |
137 | 19.1M | if (first) |
138 | 18.9M | return {}; |
139 | | |
140 | 152k | break; |
141 | 19.1M | } |
142 | | |
143 | 3.71M | while (offset < line.length() && line[offset] == ' ') |
144 | 3.15M | offset++; |
145 | | |
146 | 567k | if (first) { |
147 | 188k | is_ordered = appears_ordered; |
148 | 378k | } else if (appears_ordered != is_ordered) { |
149 | 593 | break; |
150 | 593 | } |
151 | | |
152 | 566k | is_tight = is_tight && !has_trailing_blank_lines; |
153 | | |
154 | 566k | lines.push_context(LineIterator::Context::list_item(offset)); |
155 | | |
156 | 566k | auto list_item = ContainerBlock::parse(lines); |
157 | 566k | is_tight = is_tight && !list_item->has_blank_lines(); |
158 | 566k | has_trailing_blank_lines = has_trailing_blank_lines || list_item->has_trailing_blank_lines(); |
159 | 566k | items.append(move(list_item)); |
160 | | |
161 | 566k | lines.pop_context(); |
162 | | |
163 | 566k | first = false; |
164 | 566k | } |
165 | | |
166 | 188k | return make<List>(move(items), is_ordered, is_tight, start_number); |
167 | 19.1M | } |
168 | | |
169 | | } |