Serenity Operating System
at master 265 lines 8.0 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Debug.h> 8#include <AK/StringBuilder.h> 9#include <AK/Vector.h> 10#include <LibMarkdown/Table.h> 11#include <LibMarkdown/Visitor.h> 12 13namespace Markdown { 14 15Vector<DeprecatedString> Table::render_lines_for_terminal(size_t view_width) const 16{ 17 auto unit_width_length = view_width == 0 ? 4 : ((float)(view_width - m_columns.size()) / (float)m_total_width); 18 StringBuilder builder; 19 Vector<DeprecatedString> lines; 20 21 auto write_aligned = [&](auto const& text, auto width, auto alignment) { 22 size_t original_length = text.terminal_length(); 23 auto string = text.render_for_terminal(); 24 if (alignment == Alignment::Center) { 25 auto padding_length = (width - original_length) / 2; 26 // FIXME: We're using a StringView literal to bypass the compile-time AK::Format checking here, since it can't handle the "}}" 27 builder.appendff("{:{1}}"sv, "", (int)padding_length); 28 builder.append(string); 29 builder.appendff("{:{1}}"sv, "", (int)padding_length); 30 if ((width - original_length) % 2) 31 builder.append(' '); 32 } else { 33 // FIXME: We're using StringView literals to bypass the compile-time AK::Format checking here, since it can't handle the "}}" 34 builder.appendff(alignment == Alignment::Left ? "{:<{1}}"sv : "{:>{1}}"sv, string, (int)(width + (string.length() - original_length))); 35 } 36 }; 37 38 bool first = true; 39 for (auto& col : m_columns) { 40 if (!first) 41 builder.append('|'); 42 first = false; 43 size_t width = col.relative_width * unit_width_length; 44 write_aligned(col.header, width, col.alignment); 45 } 46 47 lines.append(builder.to_deprecated_string()); 48 builder.clear(); 49 50 for (size_t i = 0; i < view_width; ++i) 51 builder.append('-'); 52 lines.append(builder.to_deprecated_string()); 53 builder.clear(); 54 55 for (size_t i = 0; i < m_row_count; ++i) { 56 bool first = true; 57 for (auto& col : m_columns) { 58 VERIFY(i < col.rows.size()); 59 auto& cell = col.rows[i]; 60 61 if (!first) 62 builder.append('|'); 63 first = false; 64 65 size_t width = col.relative_width * unit_width_length; 66 write_aligned(cell, width, col.alignment); 67 } 68 lines.append(builder.to_deprecated_string()); 69 builder.clear(); 70 } 71 72 lines.append(""); 73 74 return lines; 75} 76 77DeprecatedString Table::render_to_html(bool) const 78{ 79 auto alignment_string = [](Alignment alignment) { 80 switch (alignment) { 81 case Alignment::Center: 82 return "center"sv; 83 case Alignment::Left: 84 return "left"sv; 85 case Alignment::Right: 86 return "right"sv; 87 } 88 VERIFY_NOT_REACHED(); 89 }; 90 91 StringBuilder builder; 92 93 builder.append("<table>"sv); 94 builder.append("<thead>"sv); 95 builder.append("<tr>"sv); 96 for (auto& column : m_columns) { 97 builder.appendff("<th style='text-align: {}'>", alignment_string(column.alignment)); 98 builder.append(column.header.render_to_html()); 99 builder.append("</th>"sv); 100 } 101 builder.append("</tr>"sv); 102 builder.append("</thead>"sv); 103 builder.append("<tbody>"sv); 104 for (size_t i = 0; i < m_row_count; ++i) { 105 builder.append("<tr>"sv); 106 for (auto& column : m_columns) { 107 VERIFY(i < column.rows.size()); 108 builder.appendff("<td style='text-align: {}'>", alignment_string(column.alignment)); 109 builder.append(column.rows[i].render_to_html()); 110 builder.append("</td>"sv); 111 } 112 builder.append("</tr>"sv); 113 } 114 builder.append("</tbody>"sv); 115 builder.append("</table>"sv); 116 117 return builder.to_deprecated_string(); 118} 119 120RecursionDecision Table::walk(Visitor& visitor) const 121{ 122 RecursionDecision rd = visitor.visit(*this); 123 if (rd != RecursionDecision::Recurse) 124 return rd; 125 126 for (auto const& column : m_columns) { 127 rd = column.walk(visitor); 128 if (rd == RecursionDecision::Break) 129 return rd; 130 } 131 132 return RecursionDecision::Continue; 133} 134 135OwnPtr<Table> Table::parse(LineIterator& lines) 136{ 137 auto peek_it = lines; 138 auto first_line = *peek_it; 139 if (!first_line.starts_with('|')) 140 return {}; 141 142 ++peek_it; 143 144 if (peek_it.is_end()) 145 return {}; 146 147 auto header_segments = first_line.split_view('|', SplitBehavior::KeepEmpty); 148 auto header_delimiters = peek_it->split_view('|', SplitBehavior::KeepEmpty); 149 150 if (!header_segments.is_empty()) 151 header_segments.take_first(); 152 if (!header_segments.is_empty() && header_segments.last().is_empty()) 153 header_segments.take_last(); 154 155 if (!header_delimiters.is_empty()) 156 header_delimiters.take_first(); 157 if (!header_delimiters.is_empty() && header_delimiters.last().is_empty()) 158 header_delimiters.take_last(); 159 160 ++peek_it; 161 162 if (header_delimiters.size() != header_segments.size()) 163 return {}; 164 165 if (header_delimiters.is_empty()) 166 return {}; 167 168 size_t total_width = 0; 169 170 auto table = make<Table>(); 171 table->m_columns.resize(header_delimiters.size()); 172 173 for (size_t i = 0; i < header_segments.size(); ++i) { 174 auto text = Text::parse(header_segments[i]); 175 176 auto& column = table->m_columns[i]; 177 178 column.header = move(text); 179 180 auto delimiter = header_delimiters[i].trim_whitespace(); 181 182 auto align_left = delimiter.starts_with(':'); 183 auto align_right = delimiter != ":" && delimiter.ends_with(':'); 184 185 if (align_left) 186 delimiter = delimiter.substring_view(1, delimiter.length() - 1); 187 if (align_right) 188 delimiter = delimiter.substring_view(0, delimiter.length() - 1); 189 190 if (align_left && align_right) 191 column.alignment = Alignment::Center; 192 else if (align_right) 193 column.alignment = Alignment::Right; 194 else 195 column.alignment = Alignment::Left; 196 197 size_t relative_width = delimiter.length(); 198 for (auto ch : delimiter) { 199 if (ch != '-') { 200 dbgln_if(MARKDOWN_DEBUG, "Invalid character _{}_ in table heading delimiter (ignored)", ch); 201 --relative_width; 202 } 203 } 204 205 column.relative_width = relative_width; 206 total_width += relative_width; 207 } 208 209 table->m_total_width = total_width; 210 211 for (off_t i = 0; i < peek_it - lines; ++i) 212 ++lines; 213 214 size_t row_count = 0; 215 ++lines; 216 while (!lines.is_end()) { 217 auto line = *lines; 218 if (!line.starts_with('|')) 219 break; 220 221 ++lines; 222 223 auto segments = line.split_view('|', SplitBehavior::KeepEmpty); 224 segments.take_first(); 225 if (!segments.is_empty() && segments.last().is_empty()) 226 segments.take_last(); 227 ++row_count; 228 229 for (size_t i = 0; i < header_segments.size(); ++i) { 230 if (i >= segments.size()) { 231 // Ran out of segments, but still have headers. 232 // Just make an empty cell. 233 table->m_columns[i].rows.append(Text::parse(""sv)); 234 } else { 235 auto text = Text::parse(segments[i]); 236 table->m_columns[i].rows.append(move(text)); 237 } 238 } 239 } 240 241 table->m_row_count = row_count; 242 243 return table; 244} 245 246RecursionDecision Table::Column::walk(Visitor& visitor) const 247{ 248 RecursionDecision rd = visitor.visit(*this); 249 if (rd != RecursionDecision::Recurse) 250 return rd; 251 252 rd = header.walk(visitor); 253 if (rd != RecursionDecision::Recurse) 254 return rd; 255 256 for (auto const& row : rows) { 257 rd = row.walk(visitor); 258 if (rd == RecursionDecision::Break) 259 return rd; 260 } 261 262 return RecursionDecision::Continue; 263} 264 265}