Serenity Operating System
at master 169 lines 4.7 kB view raw
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 14namespace Markdown { 15 16DeprecatedString List::render_to_html(bool) const 17{ 18 StringBuilder builder; 19 20 char const* tag = m_is_ordered ? "ol" : "ul"; 21 builder.appendff("<{}", tag); 22 23 if (m_start_number != 1) 24 builder.appendff(" start=\"{}\"", m_start_number); 25 26 builder.append(">\n"sv); 27 28 for (auto& item : m_items) { 29 builder.append("<li>"sv); 30 if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast<Paragraph const*>(item->blocks()[0].ptr()))) 31 builder.append('\n'); 32 builder.append(item->render_to_html(m_is_tight)); 33 builder.append("</li>\n"sv); 34 } 35 36 builder.appendff("</{}>\n", tag); 37 38 return builder.to_deprecated_string(); 39} 40 41Vector<DeprecatedString> List::render_lines_for_terminal(size_t view_width) const 42{ 43 Vector<DeprecatedString> lines; 44 45 int i = 0; 46 for (auto& item : m_items) { 47 auto item_lines = item->render_lines_for_terminal(view_width); 48 auto first_line = item_lines.take_first(); 49 50 StringBuilder builder; 51 builder.append(" "sv); 52 if (m_is_ordered) 53 builder.appendff("{}.", ++i); 54 else 55 builder.append('*'); 56 auto item_indentation = builder.length(); 57 58 builder.append(first_line); 59 60 lines.append(builder.to_deprecated_string()); 61 62 for (auto& line : item_lines) { 63 builder.clear(); 64 builder.append(DeprecatedString::repeated(' ', item_indentation)); 65 builder.append(line); 66 lines.append(builder.to_deprecated_string()); 67 } 68 } 69 70 return lines; 71} 72 73RecursionDecision List::walk(Visitor& visitor) const 74{ 75 RecursionDecision rd = visitor.visit(*this); 76 if (rd != RecursionDecision::Recurse) 77 return rd; 78 79 for (auto const& block : m_items) { 80 rd = block->walk(visitor); 81 if (rd == RecursionDecision::Break) 82 return rd; 83 } 84 85 return RecursionDecision::Continue; 86} 87 88OwnPtr<List> List::parse(LineIterator& lines) 89{ 90 Vector<OwnPtr<ContainerBlock>> items; 91 92 bool first = true; 93 bool is_ordered = false; 94 95 bool is_tight = true; 96 bool has_trailing_blank_lines = false; 97 size_t start_number = 1; 98 99 while (!lines.is_end()) { 100 101 size_t offset = 0; 102 103 StringView line = *lines; 104 105 bool appears_unordered = false; 106 107 while (offset < line.length() && line[offset] == ' ') 108 ++offset; 109 110 if (offset + 2 <= line.length()) { 111 if (line[offset + 1] == ' ' && (line[offset] == '*' || line[offset] == '-' || line[offset] == '+')) { 112 appears_unordered = true; 113 offset++; 114 } 115 } 116 117 bool appears_ordered = false; 118 for (size_t i = offset; i < 10 && i < line.length(); i++) { 119 char ch = line[i]; 120 if ('0' <= ch && ch <= '9') 121 continue; 122 if (ch == '.' || ch == ')') 123 if (i + 1 < line.length() && line[i + 1] == ' ') { 124 auto maybe_start_number = line.substring_view(offset, i - offset).to_uint<size_t>(); 125 if (!maybe_start_number.has_value()) 126 break; 127 if (first) 128 start_number = maybe_start_number.value(); 129 appears_ordered = true; 130 offset = i + 1; 131 } 132 break; 133 } 134 135 VERIFY(!(appears_unordered && appears_ordered)); 136 if (!appears_unordered && !appears_ordered) { 137 if (first) 138 return {}; 139 140 break; 141 } 142 143 while (offset < line.length() && line[offset] == ' ') 144 offset++; 145 146 if (first) { 147 is_ordered = appears_ordered; 148 } else if (appears_ordered != is_ordered) { 149 break; 150 } 151 152 is_tight = is_tight && !has_trailing_blank_lines; 153 154 lines.push_context(LineIterator::Context::list_item(offset)); 155 156 auto list_item = ContainerBlock::parse(lines); 157 is_tight = is_tight && !list_item->has_blank_lines(); 158 has_trailing_blank_lines = has_trailing_blank_lines || list_item->has_trailing_blank_lines(); 159 items.append(move(list_item)); 160 161 lines.pop_context(); 162 163 first = false; 164 } 165 166 return make<List>(move(items), is_ordered, is_tight, start_number); 167} 168 169}