mirror of
https://github.com/RGBCube/serenity
synced 2025-05-22 04:55:07 +00:00

This patch adds contexts to line iterator for nesting list items and blockquotes. It also incidentally makes the api for LineIterator simpler, and will make it easier to add other containers in the future.
139 lines
3.7 KiB
C++
139 lines
3.7 KiB
C++
/*
|
|
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
|
|
* Copyright (c) 2021, Peter Elliott <pelliott@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibMarkdown/List.h>
|
|
#include <LibMarkdown/Paragraph.h>
|
|
|
|
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("<li>");
|
|
if (!m_is_tight || (item->blocks().size() != 0 && !dynamic_cast<Paragraph const*>(&(item->blocks()[0]))))
|
|
builder.append("\n");
|
|
builder.append(item->render_to_html(m_is_tight));
|
|
builder.append("</li>\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");
|
|
}
|
|
builder.append("\n");
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
OwnPtr<List> List::parse(LineIterator& lines)
|
|
{
|
|
Vector<OwnPtr<ContainerBlock>> 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<size_t>();
|
|
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<List>(move(items), is_ordered, is_tight, start_number);
|
|
}
|
|
|
|
}
|