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:
parent
7ca562b200
commit
20faa93cb0
15 changed files with 110 additions and 64 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue