/* * Copyright (c) 2019-2020, Sergey Bugaev * Copyright (c) 2021, Peter Elliott * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include namespace Markdown { String List::render_to_html(bool) const { StringBuilder builder; const char* tag = m_is_ordered ? "ol" : "ul"; builder.appendff("<{}", tag); if (m_start_number != 1) builder.appendff(" start=\"{}\"", m_start_number); builder.append(">\n"); for (auto& item : m_items) { builder.append("
  • "); if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast(&(item->blocks()[0])))) builder.append("\n"); builder.append(item->render_to_html(m_is_tight)); builder.append("
  • \n"); } builder.appendff("\n", tag); return builder.build(); } String List::render_for_terminal(size_t) const { StringBuilder builder; int i = 0; for (auto& item : m_items) { builder.append(" "); if (m_is_ordered) builder.appendff("{}. ", ++i); else builder.append("* "); builder.append(item->render_for_terminal()); } builder.append("\n"); return builder.build(); } RecursionDecision List::walk(Visitor& visitor) const { RecursionDecision rd = visitor.visit(*this); if (rd != RecursionDecision::Recurse) return rd; for (auto const& block : m_items) { rd = block->walk(visitor); if (rd == RecursionDecision::Break) return rd; } return RecursionDecision::Continue; } OwnPtr List::parse(LineIterator& lines) { Vector> items; bool first = true; bool is_ordered = false; bool is_tight = true; bool has_trailing_blank_lines = false; size_t start_number = 1; while (!lines.is_end()) { size_t offset = 0; const StringView& line = *lines; bool appears_unordered = false; while (offset < line.length() && line[offset] == ' ') ++offset; if (offset + 2 <= line.length()) { if (line[offset + 1] == ' ' && (line[offset] == '*' || line[offset] == '-' || line[offset] == '+')) { appears_unordered = true; offset++; } } bool appears_ordered = false; for (size_t i = offset; i < 10 && i < line.length(); i++) { char ch = line[i]; if ('0' <= ch && ch <= '9') continue; if (ch == '.' || ch == ')') if (i + 1 < line.length() && line[i + 1] == ' ') { auto maybe_start_number = line.substring_view(offset, i - offset).to_uint(); if (!maybe_start_number.has_value()) break; if (first) start_number = maybe_start_number.value(); appears_ordered = true; offset = i + 1; } break; } VERIFY(!(appears_unordered && appears_ordered)); if (!appears_unordered && !appears_ordered) { if (first) return {}; break; } while (offset < line.length() && line[offset] == ' ') offset++; if (first) { is_ordered = appears_ordered; } else if (appears_ordered != is_ordered) { break; } is_tight = is_tight && !has_trailing_blank_lines; lines.push_context(LineIterator::Context::list_item(offset)); auto list_item = ContainerBlock::parse(lines); is_tight = is_tight && !list_item->has_blank_lines(); has_trailing_blank_lines = has_trailing_blank_lines || list_item->has_trailing_blank_lines(); items.append(move(list_item)); lines.pop_context(); first = false; } return make(move(items), is_ordered, is_tight, start_number); } }