1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 20:37:34 +00:00

LibMarkdown: Change internal MD API to return OwnPtrs

Previously, all Markdown blocks had a virtual parse method which has
been swapped out for a static parse method returning an OwnPtr of
that block's type.

The Text class also now has a static parse method that will return an
Optional<Text>.
This commit is contained in:
FalseHonesty 2020-05-18 16:58:00 -04:00 committed by Andreas Kling
parent 7ca562b200
commit 20faa93cb0
15 changed files with 110 additions and 64 deletions

View file

@ -128,10 +128,13 @@ int main(int argc, char* argv[])
auto buffer = file->read_all(); auto buffer = file->read_all();
StringView source { (const char*)buffer.data(), buffer.size() }; StringView source { (const char*)buffer.data(), buffer.size() };
auto md_document = Markdown::Document::parse(source); String html;
ASSERT(md_document); {
auto md_document = Markdown::Document::parse(source);
ASSERT(md_document);
html = md_document->render_to_html();
}
String html = md_document->render_to_html();
auto html_document = Web::parse_html_document(html); auto html_document = Web::parse_html_document(html);
page_view.set_document(html_document); page_view.set_document(html_document);

View file

@ -37,7 +37,6 @@ public:
virtual String render_to_html() const = 0; virtual String render_to_html() const = 0;
virtual String render_for_terminal() const = 0; virtual String render_for_terminal() const = 0;
virtual bool parse(Vector<StringView>::ConstIterator& lines) = 0;
}; };
} }

View file

@ -105,16 +105,16 @@ String CodeBlock::render_for_terminal() const
return builder.build(); return builder.build();
} }
bool CodeBlock::parse(Vector<StringView>::ConstIterator& lines) OwnPtr<CodeBlock> CodeBlock::parse(Vector<StringView>::ConstIterator& lines)
{ {
if (lines.is_end()) if (lines.is_end())
return false; return nullptr;
constexpr auto tick_tick_tick = "```"; constexpr auto tick_tick_tick = "```";
StringView line = *lines; StringView line = *lines;
if (!line.starts_with(tick_tick_tick)) if (!line.starts_with(tick_tick_tick))
return false; return nullptr;
// Our Markdown extension: we allow // Our Markdown extension: we allow
// specifying a style and a language // specifying a style and a language
@ -128,8 +128,9 @@ bool CodeBlock::parse(Vector<StringView>::ConstIterator& lines)
// and if possible syntax-highlighted // and if possible syntax-highlighted
// as appropriate for a shell script. // as appropriate for a shell script.
StringView style_spec = line.substring_view(3, line.length() - 3); StringView style_spec = line.substring_view(3, line.length() - 3);
bool success = m_style_spec.parse(style_spec); auto spec = Text::parse(style_spec);
ASSERT(success); if (!spec.has_value())
return nullptr;
++lines; ++lines;
@ -149,8 +150,7 @@ bool CodeBlock::parse(Vector<StringView>::ConstIterator& lines)
first = false; first = false;
} }
m_code = builder.build(); return make<CodeBlock>(move(spec.value()), builder.build());
return true;
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/OwnPtr.h>
#include <LibMarkdown/Block.h> #include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h> #include <LibMarkdown/Text.h>
@ -33,11 +34,16 @@ namespace Markdown {
class CodeBlock final : public Block { class CodeBlock final : public Block {
public: public:
virtual ~CodeBlock() override {} CodeBlock(Text&& style_spec, const String& code)
: m_code(move(code))
, m_style_spec(move(style_spec))
{
}
virtual ~CodeBlock() override { }
virtual String render_to_html() const override; virtual String render_to_html() const override;
virtual String render_for_terminal() const override; virtual String render_for_terminal() const override;
virtual bool parse(Vector<StringView>::ConstIterator& lines) override; static OwnPtr<CodeBlock> parse(Vector<StringView>::ConstIterator& lines);
private: private:
String style_language() const; String style_language() const;

View file

@ -67,19 +67,18 @@ String Document::render_for_terminal() const
template<typename BlockType> template<typename BlockType>
static bool helper(Vector<StringView>::ConstIterator& lines, NonnullOwnPtrVector<Block>& blocks) static bool helper(Vector<StringView>::ConstIterator& lines, NonnullOwnPtrVector<Block>& blocks)
{ {
NonnullOwnPtr<BlockType> block = make<BlockType>(); OwnPtr<BlockType> block = BlockType::parse(lines);
bool success = block->parse(lines); if (!block)
if (!success)
return false; return false;
blocks.append(move(block)); blocks.append(block.release_nonnull());
return true; return true;
} }
RefPtr<Document> Document::parse(const StringView& str) OwnPtr<Document> Document::parse(const StringView& str)
{ {
const Vector<StringView> lines_vec = str.lines(); const Vector<StringView> lines_vec = str.lines();
auto lines = lines_vec.begin(); auto lines = lines_vec.begin();
auto document = adopt(*new Document); auto document = make<Document>();
auto& blocks = document->m_blocks; auto& blocks = document->m_blocks;
while (true) { while (true) {

View file

@ -32,12 +32,12 @@
namespace Markdown { namespace Markdown {
class Document final : public RefCounted<Document> { class Document final {
public: public:
String render_to_html() const; String render_to_html() const;
String render_for_terminal() const; String render_for_terminal() const;
static RefPtr<Document> parse(const StringView&); static OwnPtr<Document> parse(const StringView&);
private: private:
NonnullOwnPtrVector<Block> m_blocks; NonnullOwnPtrVector<Block> m_blocks;

View file

@ -59,26 +59,30 @@ String Heading::render_for_terminal() const
return builder.build(); return builder.build();
} }
bool Heading::parse(Vector<StringView>::ConstIterator& lines) OwnPtr<Heading> Heading::parse(Vector<StringView>::ConstIterator& lines)
{ {
if (lines.is_end()) if (lines.is_end())
return false; return nullptr;
const StringView& line = *lines; const StringView& line = *lines;
size_t level;
for (m_level = 0; m_level < (int)line.length(); m_level++) for (level = 0; level < line.length(); level++)
if (line[(size_t)m_level] != '#') if (line[level] != '#')
break; break;
if (m_level >= (int)line.length() || line[(size_t)m_level] != ' ') if (level >= line.length() || line[level] != ' ')
return false; return nullptr;
StringView title_view = line.substring_view((size_t)m_level + 1, line.length() - (size_t)m_level - 1); StringView title_view = line.substring_view(level + 1, line.length() - level - 1);
bool success = m_text.parse(title_view); auto text = Text::parse(title_view);
ASSERT(success); if (!text.has_value())
return nullptr;
auto heading = make<Heading>(move(text.value()), level);
++lines; ++lines;
return true; return heading;
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/OwnPtr.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibMarkdown/Block.h> #include <LibMarkdown/Block.h>
@ -35,15 +36,20 @@ namespace Markdown {
class Heading final : public Block { class Heading final : public Block {
public: public:
virtual ~Heading() override {} Heading(Text&& text, size_t level)
: m_text(move(text))
, m_level(level)
{
}
virtual ~Heading() override { }
virtual String render_to_html() const override; virtual String render_to_html() const override;
virtual String render_for_terminal() const override; virtual String render_for_terminal() const override;
virtual bool parse(Vector<StringView>::ConstIterator& lines) override; static OwnPtr<Heading> parse(Vector<StringView>::ConstIterator& lines);
private: private:
Text m_text; Text m_text;
int m_level { -1 }; size_t m_level { 0 };
}; };
} }

View file

@ -66,21 +66,26 @@ String List::render_for_terminal() const
return builder.build(); return builder.build();
} }
bool List::parse(Vector<StringView>::ConstIterator& lines) OwnPtr<List> List::parse(Vector<StringView>::ConstIterator& lines)
{ {
Vector<Text> items;
bool is_ordered = false;
bool first = true; bool first = true;
size_t offset = 0; size_t offset = 0;
StringBuilder item_builder; StringBuilder item_builder;
auto flush_item_if_needed = [&] { auto flush_item_if_needed = [&] {
if (first) if (first)
return; return true;
Text text; auto text = Text::parse(item_builder.string_view());
bool success = text.parse(item_builder.string_view()); if (!text.has_value())
ASSERT(success); return false;
m_items.append(move(text));
items.append(move(text.value()));
item_builder.clear(); item_builder.clear();
return true;
}; };
while (true) { while (true) {
@ -115,21 +120,22 @@ bool List::parse(Vector<StringView>::ConstIterator& lines)
if (appears_unordered || appears_ordered) { if (appears_unordered || appears_ordered) {
if (first) if (first)
m_is_ordered = appears_ordered; is_ordered = appears_ordered;
else if (m_is_ordered != appears_ordered) else if (is_ordered != appears_ordered)
return false; return nullptr;
flush_item_if_needed(); if (!flush_item_if_needed())
return nullptr;
while (offset + 1 < line.length() && line[offset + 1] == ' ') while (offset + 1 < line.length() && line[offset + 1] == ' ')
offset++; offset++;
} else { } else {
if (first) if (first)
return false; return nullptr;
for (size_t i = 0; i < offset; i++) { for (size_t i = 0; i < offset; i++) {
if (line[i] != ' ') if (line[i] != ' ')
return false; return nullptr;
} }
} }
@ -140,8 +146,9 @@ bool List::parse(Vector<StringView>::ConstIterator& lines)
++lines; ++lines;
} }
flush_item_if_needed(); if (!flush_item_if_needed() || first)
return !first; return nullptr;
return make<List>(move(items), is_ordered);
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/OwnPtr.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibMarkdown/Block.h> #include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h> #include <LibMarkdown/Text.h>
@ -34,11 +35,17 @@ namespace Markdown {
class List final : public Block { class List final : public Block {
public: public:
List(Vector<Text>&& text, bool is_ordered)
: m_items(move(text))
, m_is_ordered(is_ordered)
{
}
virtual ~List() override {} virtual ~List() override {}
virtual String render_to_html() const override; virtual String render_to_html() const override;
virtual String render_for_terminal() const override; virtual String render_for_terminal() const override;
virtual bool parse(Vector<StringView>::ConstIterator& lines) override;
static OwnPtr<List> parse(Vector<StringView>::ConstIterator& lines);
private: private:
// TODO: List items should be considered blocks of their own kind. // TODO: List items should be considered blocks of their own kind.

View file

@ -46,10 +46,10 @@ String Paragraph::render_for_terminal() const
return builder.build(); return builder.build();
} }
bool Paragraph::parse(Vector<StringView>::ConstIterator& lines) OwnPtr<Paragraph> Paragraph::parse(Vector<StringView>::ConstIterator& lines)
{ {
if (lines.is_end()) if (lines.is_end())
return false; return nullptr;
bool first = true; bool first = true;
StringBuilder builder; StringBuilder builder;
@ -86,11 +86,13 @@ bool Paragraph::parse(Vector<StringView>::ConstIterator& lines)
} }
if (first) if (first)
return false; return nullptr;
bool success = m_text.parse(builder.build()); auto text = Text::parse(builder.build());
ASSERT(success); if (!text.has_value())
return true; return nullptr;
return make<Paragraph>(move(text.value()));
} }
} }

View file

@ -26,6 +26,7 @@
#pragma once #pragma once
#include <AK/OwnPtr.h>
#include <LibMarkdown/Block.h> #include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h> #include <LibMarkdown/Text.h>
@ -33,11 +34,12 @@ namespace Markdown {
class Paragraph final : public Block { class Paragraph final : public Block {
public: public:
explicit Paragraph(Text&& text) : m_text(move(text)) {}
virtual ~Paragraph() override {} virtual ~Paragraph() override {}
virtual String render_to_html() const override; virtual String render_to_html() const override;
virtual String render_for_terminal() const override; virtual String render_for_terminal() const override;
virtual bool parse(Vector<StringView>::ConstIterator& lines) override; static OwnPtr<Paragraph> parse(Vector<StringView>::ConstIterator& lines);
private: private:
Text m_text; Text m_text;

View file

@ -181,12 +181,13 @@ String Text::render_for_terminal() const
return builder.build(); return builder.build();
} }
bool Text::parse(const StringView& str) Optional<Text> Text::parse(const StringView& str)
{ {
Style current_style; Style current_style;
size_t current_span_start = 0; size_t current_span_start = 0;
int first_span_in_the_current_link = -1; int first_span_in_the_current_link = -1;
bool current_link_is_actually_img = false; bool current_link_is_actually_img = false;
Vector<Span> spans;
auto append_span_if_needed = [&](size_t offset) { auto append_span_if_needed = [&](size_t offset) {
ASSERT(current_span_start <= offset); ASSERT(current_span_start <= offset);
@ -195,7 +196,7 @@ bool Text::parse(const StringView& str)
unescape(str.substring_view(current_span_start, offset - current_span_start)), unescape(str.substring_view(current_span_start, offset - current_span_start)),
current_style current_style
}; };
m_spans.append(move(span)); spans.append(move(span));
current_span_start = offset; current_span_start = offset;
} }
}; };
@ -239,7 +240,7 @@ bool Text::parse(const StringView& str)
case '[': case '[':
if (first_span_in_the_current_link != -1) if (first_span_in_the_current_link != -1)
dbg() << "Dropping the outer link"; dbg() << "Dropping the outer link";
first_span_in_the_current_link = m_spans.size(); first_span_in_the_current_link = spans.size();
break; break;
case ']': { case ']': {
if (first_span_in_the_current_link == -1) { if (first_span_in_the_current_link == -1) {
@ -262,11 +263,11 @@ bool Text::parse(const StringView& str)
offset--; offset--;
const StringView href = str.substring_view(start_of_href, offset - start_of_href); const StringView href = str.substring_view(start_of_href, offset - start_of_href);
for (size_t i = first_span_in_the_current_link; i < m_spans.size(); i++) { for (size_t i = first_span_in_the_current_link; i < spans.size(); i++) {
if (current_link_is_actually_img) if (current_link_is_actually_img)
m_spans[i].style.img = href; spans[i].style.img = href;
else else
m_spans[i].style.href = href; spans[i].style.href = href;
} }
break; break;
} }
@ -282,7 +283,7 @@ bool Text::parse(const StringView& str)
append_span_if_needed(str.length()); append_span_if_needed(str.length());
return true; return Text(move(spans));
} }
} }

View file

@ -26,12 +26,14 @@
#pragma once #pragma once
#include <AK/Noncopyable.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/Vector.h> #include <AK/Vector.h>
namespace Markdown { namespace Markdown {
class Text final { class Text final {
AK_MAKE_NONCOPYABLE(Text);
public: public:
struct Style { struct Style {
bool emph { false }; bool emph { false };
@ -46,14 +48,21 @@ public:
Style style; Style style;
}; };
Text(Text&& text) = default;
const Vector<Span>& spans() const { return m_spans; } const Vector<Span>& spans() const { return m_spans; }
String render_to_html() const; String render_to_html() const;
String render_for_terminal() const; String render_for_terminal() const;
bool parse(const StringView&); static Optional<Text> parse(const StringView&);
private: private:
Text(Vector<Span>&& spans)
: m_spans(move(spans))
{
}
Vector<Span> m_spans; Vector<Span> m_spans;
}; };

View file

@ -25,6 +25,7 @@
*/ */
#include <AK/ByteBuffer.h> #include <AK/ByteBuffer.h>
#include <AK/OwnPtr.h>
#include <AK/String.h> #include <AK/String.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibMarkdown/Document.h> #include <LibMarkdown/Document.h>