1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 22:37:35 +00:00

Libraries: Move to Userland/Libraries/

This commit is contained in:
Andreas Kling 2021-01-12 12:17:30 +01:00
parent dc28c07fa5
commit 13d7c09125
1857 changed files with 266 additions and 274 deletions

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/StringView.h>
#include <AK/Vector.h>
namespace Markdown {
class Block {
public:
virtual ~Block() { }
virtual String render_to_html() const = 0;
virtual String render_for_terminal(size_t view_width = 0) const = 0;
};
}

View file

@ -0,0 +1,13 @@
set(SOURCES
CodeBlock.cpp
Document.cpp
Heading.cpp
HorizontalRule.cpp
List.cpp
Paragraph.cpp
Table.cpp
Text.cpp
)
serenity_lib(LibMarkdown markdown)
target_link_libraries(LibMarkdown LibJS)

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibJS/MarkupGenerator.h>
#include <LibMarkdown/CodeBlock.h>
namespace Markdown {
Text::Style CodeBlock::style() const
{
if (m_style_spec.spans().is_empty())
return {};
return m_style_spec.spans()[0].style;
}
String CodeBlock::style_language() const
{
if (m_style_spec.spans().is_empty())
return {};
return m_style_spec.spans()[0].text;
}
String CodeBlock::render_to_html() const
{
StringBuilder builder;
String style_language = this->style_language();
Text::Style style = this->style();
if (style.strong)
builder.append("<b>");
if (style.emph)
builder.append("<i>");
if (style_language.is_empty())
builder.append("<code>");
else
builder.appendff("<code class=\"{}\">", style_language);
if (style_language == "js")
builder.append(JS::MarkupGenerator::html_from_source(m_code));
else
builder.append(escape_html_entities(m_code));
builder.append("</code>");
if (style.emph)
builder.append("</i>");
if (style.strong)
builder.append("</b>");
builder.append('\n');
return builder.build();
}
String CodeBlock::render_for_terminal(size_t) const
{
StringBuilder builder;
Text::Style style = this->style();
bool needs_styling = style.strong || style.emph;
if (needs_styling) {
builder.append("\033[");
bool first = true;
if (style.strong) {
builder.append('1');
first = false;
}
if (style.emph) {
if (!first)
builder.append(';');
builder.append('4');
}
builder.append('m');
}
builder.append(m_code);
if (needs_styling)
builder.append("\033[0m");
builder.append("\n\n");
return builder.build();
}
OwnPtr<CodeBlock> CodeBlock::parse(Vector<StringView>::ConstIterator& lines)
{
if (lines.is_end())
return {};
constexpr auto tick_tick_tick = "```";
StringView line = *lines;
if (!line.starts_with(tick_tick_tick))
return {};
// Our Markdown extension: we allow
// specifying a style and a language
// for a code block, like so:
//
// ```**sh**
// $ echo hello friends!
// ````
//
// The code block will be made bold,
// and if possible syntax-highlighted
// as appropriate for a shell script.
StringView style_spec = line.substring_view(3, line.length() - 3);
auto spec = Text::parse(style_spec);
if (!spec.has_value())
return {};
++lines;
bool first = true;
StringBuilder builder;
while (true) {
if (lines.is_end())
break;
line = *lines;
++lines;
if (line == tick_tick_tick)
break;
if (!first)
builder.append('\n');
builder.append(line);
first = false;
}
return make<CodeBlock>(move(spec.value()), builder.build());
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/OwnPtr.h>
#include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h>
namespace Markdown {
class CodeBlock final : public Block {
public:
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_for_terminal(size_t view_width = 0) const override;
static OwnPtr<CodeBlock> parse(Vector<StringView>::ConstIterator& lines);
private:
String style_language() const;
Text::Style style() const;
String m_code;
Text m_style_spec;
};
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibMarkdown/CodeBlock.h>
#include <LibMarkdown/Document.h>
#include <LibMarkdown/Heading.h>
#include <LibMarkdown/HorizontalRule.h>
#include <LibMarkdown/List.h>
#include <LibMarkdown/Paragraph.h>
#include <LibMarkdown/Table.h>
namespace Markdown {
String Document::render_to_html() const
{
StringBuilder builder;
builder.append("<!DOCTYPE html>\n");
builder.append("<html>\n");
builder.append("<head>\n");
builder.append("<style>\n");
builder.append("code { white-space: pre; }\n");
builder.append("</style>\n");
builder.append("</head>\n");
builder.append("<body>\n");
for (auto& block : m_blocks) {
auto s = block.render_to_html();
builder.append(s);
}
builder.append("</body>\n");
builder.append("</html>\n");
return builder.build();
}
String Document::render_for_terminal(size_t view_width) const
{
StringBuilder builder;
for (auto& block : m_blocks) {
auto s = block.render_for_terminal(view_width);
builder.append(s);
}
return builder.build();
}
template<typename BlockType>
static bool helper(Vector<StringView>::ConstIterator& lines, NonnullOwnPtrVector<Block>& blocks)
{
OwnPtr<BlockType> block = BlockType::parse(lines);
if (!block)
return false;
blocks.append(block.release_nonnull());
return true;
}
OwnPtr<Document> Document::parse(const StringView& str)
{
const Vector<StringView> lines_vec = str.lines();
auto lines = lines_vec.begin();
auto document = make<Document>();
auto& blocks = document->m_blocks;
NonnullOwnPtrVector<Paragraph::Line> paragraph_lines;
auto flush_paragraph = [&] {
if (paragraph_lines.is_empty())
return;
auto paragraph = make<Paragraph>(move(paragraph_lines));
document->m_blocks.append(move(paragraph));
paragraph_lines.clear();
};
while (true) {
if (lines.is_end())
break;
if ((*lines).is_empty()) {
++lines;
flush_paragraph();
continue;
}
bool any = helper<Table>(lines, blocks) || helper<List>(lines, blocks) || helper<CodeBlock>(lines, blocks)
|| helper<Heading>(lines, blocks) || helper<HorizontalRule>(lines, blocks);
if (any) {
if (!paragraph_lines.is_empty()) {
auto last_block = document->m_blocks.take_last();
flush_paragraph();
document->m_blocks.append(move(last_block));
}
continue;
}
auto line = Paragraph::Line::parse(lines);
if (!line)
return {};
paragraph_lines.append(line.release_nonnull());
}
if (!paragraph_lines.is_empty())
flush_paragraph();
return document;
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/String.h>
#include <LibMarkdown/Block.h>
namespace Markdown {
class Document final {
public:
String render_to_html() const;
String render_for_terminal(size_t view_width = 0) const;
static OwnPtr<Document> parse(const StringView&);
private:
NonnullOwnPtrVector<Block> m_blocks;
};
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibMarkdown/Heading.h>
namespace Markdown {
String Heading::render_to_html() const
{
StringBuilder builder;
builder.appendf("<h%zu>", m_level);
builder.append(m_text.render_to_html());
builder.appendf("</h%zu>\n", m_level);
return builder.build();
}
String Heading::render_for_terminal(size_t) const
{
StringBuilder builder;
switch (m_level) {
case 1:
case 2:
builder.append("\n\033[1m");
builder.append(m_text.render_for_terminal().to_uppercase());
builder.append("\033[0m\n");
break;
default:
builder.append("\n\033[1m");
builder.append(m_text.render_for_terminal());
builder.append("\033[0m\n");
break;
}
return builder.build();
}
OwnPtr<Heading> Heading::parse(Vector<StringView>::ConstIterator& lines)
{
if (lines.is_end())
return {};
const StringView& line = *lines;
size_t level;
for (level = 0; level < line.length(); level++) {
if (line[level] != '#')
break;
}
if (!level || level >= line.length() || line[level] != ' ')
return {};
StringView title_view = line.substring_view(level + 1, line.length() - level - 1);
auto text = Text::parse(title_view);
if (!text.has_value())
return {};
auto heading = make<Heading>(move(text.value()), level);
++lines;
return heading;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h>
namespace Markdown {
class Heading final : public Block {
public:
Heading(Text&& text, size_t level)
: m_text(move(text))
, m_level(level)
{
ASSERT(m_level > 0);
}
virtual ~Heading() override { }
virtual String render_to_html() const override;
virtual String render_for_terminal(size_t view_width = 0) const override;
static OwnPtr<Heading> parse(Vector<StringView>::ConstIterator& lines);
private:
Text m_text;
size_t m_level { 0 };
};
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibMarkdown/HorizontalRule.h>
namespace Markdown {
String HorizontalRule::render_to_html() const
{
return "<hr>\n";
}
String HorizontalRule::render_for_terminal(size_t view_width) const
{
StringBuilder builder(view_width + 1);
for (size_t i = 0; i < view_width; ++i)
builder.append('-');
builder.append("\n\n");
return builder.to_string();
}
OwnPtr<HorizontalRule> HorizontalRule::parse(Vector<StringView>::ConstIterator& lines)
{
if (lines.is_end())
return {};
const StringView& line = *lines;
if (line.length() < 3)
return {};
if (!line.starts_with('-') && !line.starts_with('_') && !line.starts_with('*'))
return {};
auto first_character = line.characters_without_null_termination()[0];
for (auto ch : line) {
if (ch != first_character)
return {};
}
++lines;
return make<HorizontalRule>();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibMarkdown/Block.h>
namespace Markdown {
class HorizontalRule final : public Block {
public:
HorizontalRule()
{
}
virtual ~HorizontalRule() override { }
virtual String render_to_html() const override;
virtual String render_for_terminal(size_t view_width = 0) const override;
static OwnPtr<HorizontalRule> parse(Vector<StringView>::ConstIterator& lines);
};
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibMarkdown/List.h>
namespace Markdown {
String List::render_to_html() const
{
StringBuilder builder;
const char* tag = m_is_ordered ? "ol" : "ul";
builder.appendf("<%s>", tag);
for (auto& item : m_items) {
builder.append("<li>");
builder.append(item.render_to_html());
builder.append("</li>\n");
}
builder.appendf("</%s>\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.appendf("%d. ", ++i);
else
builder.append("* ");
builder.append(item.render_for_terminal());
builder.append("\n");
}
builder.append("\n");
return builder.build();
}
OwnPtr<List> List::parse(Vector<StringView>::ConstIterator& lines)
{
Vector<Text> items;
bool is_ordered = false;
bool first = true;
size_t offset = 0;
StringBuilder item_builder;
auto flush_item_if_needed = [&] {
if (first)
return true;
auto text = Text::parse(item_builder.string_view());
if (!text.has_value())
return false;
items.append(move(text.value()));
item_builder.clear();
return true;
};
while (true) {
if (lines.is_end())
break;
const StringView& line = *lines;
if (line.is_empty())
break;
bool appears_unordered = false;
if (line.length() > 2) {
if (line[1] == ' ' && (line[0] == '*' || line[0] == '-')) {
appears_unordered = true;
offset = 2;
}
}
bool appears_ordered = false;
for (size_t i = 0; 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] == ' ') {
appears_ordered = true;
offset = i + 1;
}
break;
}
ASSERT(!(appears_unordered && appears_ordered));
if (appears_unordered || appears_ordered) {
if (first)
is_ordered = appears_ordered;
else if (is_ordered != appears_ordered)
return {};
if (!flush_item_if_needed())
return {};
while (offset + 1 < line.length() && line[offset + 1] == ' ')
offset++;
} else {
if (first)
return {};
for (size_t i = 0; i < offset; i++) {
if (line[i] != ' ')
return {};
}
}
first = false;
if (!item_builder.is_empty())
item_builder.append(' ');
ASSERT(offset <= line.length());
item_builder.append(line.substring_view(offset, line.length() - offset));
++lines;
offset = 0;
}
if (!flush_item_if_needed() || first)
return {};
return make<List>(move(items), is_ordered);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/Vector.h>
#include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h>
namespace Markdown {
class List final : public Block {
public:
List(Vector<Text>&& text, bool is_ordered)
: m_items(move(text))
, m_is_ordered(is_ordered)
{
}
virtual ~List() override { }
virtual String render_to_html() const override;
virtual String render_for_terminal(size_t view_width = 0) const override;
static OwnPtr<List> parse(Vector<StringView>::ConstIterator& lines);
private:
// TODO: List items should be considered blocks of their own kind.
Vector<Text> m_items;
bool m_is_ordered { false };
};
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibMarkdown/Paragraph.h>
namespace Markdown {
String Paragraph::render_to_html() const
{
StringBuilder builder;
builder.appendf("<p>");
bool first = true;
for (auto& line : m_lines) {
if (!first)
builder.append(' ');
first = false;
builder.append(line.text().render_to_html());
}
builder.appendf("</p>\n");
return builder.build();
}
String Paragraph::render_for_terminal(size_t) const
{
StringBuilder builder;
bool first = true;
for (auto& line : m_lines) {
if (!first)
builder.append(' ');
first = false;
builder.append(line.text().render_for_terminal());
}
builder.appendf("\n\n");
return builder.build();
}
OwnPtr<Paragraph::Line> Paragraph::Line::parse(Vector<StringView>::ConstIterator& lines)
{
if (lines.is_end())
return {};
auto text = Text::parse(*lines++);
if (!text.has_value())
return {};
return make<Paragraph::Line>(text.release_value());
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/OwnPtr.h>
#include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h>
namespace Markdown {
class Paragraph final : public Block {
public:
class Line {
public:
explicit Line(Text&& text)
: m_text(move(text))
{
}
static OwnPtr<Line> parse(Vector<StringView>::ConstIterator& lines);
const Text& text() const { return m_text; }
private:
Text m_text;
};
Paragraph(NonnullOwnPtrVector<Line>&& lines)
: m_lines(move(lines))
{
}
virtual ~Paragraph() override { }
virtual String render_to_html() const override;
virtual String render_for_terminal(size_t view_width = 0) const override;
void add_line(NonnullOwnPtr<Line>&& line);
private:
NonnullOwnPtrVector<Line> m_lines;
};
}

View file

@ -0,0 +1,238 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/StringBuilder.h>
#include <LibMarkdown/Table.h>
namespace Markdown {
String Table::render_for_terminal(size_t view_width) const
{
auto unit_width_length = view_width == 0 ? 4 : ((float)(view_width - m_columns.size()) / (float)m_total_width);
StringBuilder builder;
auto write_aligned = [&](const auto& text, auto width, auto alignment) {
size_t original_length = 0;
for (auto& span : text.spans())
original_length += span.text.length();
auto string = text.render_for_terminal();
if (alignment == Alignment::Center) {
auto padding_length = (width - original_length) / 2;
builder.appendf("%*s%s%*s", (int)padding_length, "", string.characters(), (int)padding_length, "");
if ((width - original_length) % 2)
builder.append(' ');
} else {
builder.appendf(alignment == Alignment::Left ? "%-*s" : "%*s", (int)(width + (string.length() - original_length)), string.characters());
}
};
bool first = true;
for (auto& col : m_columns) {
if (!first)
builder.append('|');
first = false;
size_t width = col.relative_width * unit_width_length;
write_aligned(col.header, width, col.alignment);
}
builder.append("\n");
for (size_t i = 0; i < view_width; ++i)
builder.append('-');
builder.append("\n");
for (size_t i = 0; i < m_row_count; ++i) {
bool first = true;
for (auto& col : m_columns) {
ASSERT(i < col.rows.size());
auto& cell = col.rows[i];
if (!first)
builder.append('|');
first = false;
size_t width = col.relative_width * unit_width_length;
write_aligned(cell, width, col.alignment);
}
builder.append("\n");
}
return builder.to_string();
}
String Table::render_to_html() const
{
StringBuilder builder;
builder.append("<table>");
builder.append("<thead>");
builder.append("<tr>");
for (auto& column : m_columns) {
builder.append("<th>");
builder.append(column.header.render_to_html());
builder.append("</th>");
}
builder.append("</tr>");
builder.append("</thead>");
builder.append("<tbody>");
for (size_t i = 0; i < m_row_count; ++i) {
builder.append("<tr>");
for (auto& column : m_columns) {
ASSERT(i < column.rows.size());
builder.append("<td>");
builder.append(column.rows[i].render_to_html());
builder.append("</td>");
}
builder.append("</tr>");
}
builder.append("</tbody>");
builder.append("</table>");
return builder.to_string();
}
OwnPtr<Table> Table::parse(Vector<StringView>::ConstIterator& lines)
{
auto peek_it = lines;
auto first_line = *peek_it;
if (!first_line.starts_with('|'))
return {};
++peek_it;
if (peek_it.is_end())
return {};
auto header_segments = first_line.split_view('|', true);
auto header_delimiters = peek_it->split_view('|', true);
if (!header_segments.is_empty())
header_segments.take_first();
if (!header_segments.is_empty() && header_segments.last().is_empty())
header_segments.take_last();
if (!header_delimiters.is_empty())
header_delimiters.take_first();
if (!header_delimiters.is_empty() && header_delimiters.last().is_empty())
header_delimiters.take_last();
++peek_it;
if (header_delimiters.size() != header_segments.size())
return {};
if (header_delimiters.is_empty())
return {};
size_t total_width = 0;
auto table = make<Table>();
table->m_columns.resize(header_delimiters.size());
for (size_t i = 0; i < header_segments.size(); ++i) {
auto text_option = Text::parse(header_segments[i]);
if (!text_option.has_value())
return {}; // An invalid 'text' in the header should just fail the table parse.
auto text = text_option.release_value();
auto& column = table->m_columns[i];
column.header = move(text);
auto delimiter = header_delimiters[i].trim_whitespace();
auto align_left = delimiter.starts_with(':');
auto align_right = delimiter != ":" && delimiter.ends_with(':');
if (align_left)
delimiter = delimiter.substring_view(1, delimiter.length() - 1);
if (align_right)
delimiter = delimiter.substring_view(0, delimiter.length() - 1);
if (align_left && align_right)
column.alignment = Alignment::Center;
else if (align_right)
column.alignment = Alignment::Right;
else
column.alignment = Alignment::Left;
size_t relative_width = delimiter.length();
for (auto ch : delimiter) {
if (ch != '-') {
#ifdef DEBUG_MARKDOWN
dbg() << "Invalid character _" << ch << "_ in table heading delimiter (ignored)";
#endif
--relative_width;
}
}
column.relative_width = relative_width;
total_width += relative_width;
}
table->m_total_width = total_width;
for (off_t i = 0; i < peek_it - lines; ++i)
++lines;
size_t row_count = 0;
++lines;
while (!lines.is_end()) {
auto& line = *lines;
if (!line.starts_with('|'))
break;
++lines;
auto segments = line.split_view('|', true);
segments.take_first();
if (!segments.is_empty() && segments.last().is_empty())
segments.take_last();
++row_count;
for (size_t i = 0; i < header_segments.size(); ++i) {
if (i >= segments.size()) {
// Ran out of segments, but still have headers.
// Just make an empty cell.
table->m_columns[i].rows.append(Text { "" });
} else {
auto text_option = Text::parse(segments[i]);
// We treat an invalid 'text' as a literal.
if (text_option.has_value()) {
auto text = text_option.release_value();
table->m_columns[i].rows.append(move(text));
} else {
table->m_columns[i].rows.append(Text { segments[i] });
}
}
}
}
table->m_row_count = row_count;
return move(table);
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/OwnPtr.h>
#include <LibMarkdown/Block.h>
#include <LibMarkdown/Text.h>
namespace Markdown {
class Table final : public Block {
public:
enum class Alignment {
Center,
Left,
Right,
};
struct Column {
Text header;
Vector<Text> rows;
Alignment alignment { Alignment::Left };
size_t relative_width { 0 };
};
Table() { }
virtual ~Table() override { }
virtual String render_to_html() const override;
virtual String render_for_terminal(size_t view_width = 0) const override;
static OwnPtr<Table> parse(Vector<StringView>::ConstIterator& lines);
private:
Vector<Column> m_columns;
size_t m_total_width { 1 };
size_t m_row_count { 0 };
};
}

View file

@ -0,0 +1,298 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/ScopeGuard.h>
#include <AK/StringBuilder.h>
#include <LibMarkdown/Text.h>
#include <string.h>
//#define DEBUG_MARKDOWN
namespace Markdown {
static String unescape(const StringView& text)
{
StringBuilder builder;
for (size_t i = 0; i < text.length(); ++i) {
if (text[i] == '\\' && i != text.length() - 1) {
builder.append(text[i + 1]);
i++;
continue;
}
builder.append(text[i]);
}
return builder.build();
}
Text::Text(String&& text)
{
m_spans.append({ move(text), Style {} });
}
String Text::render_to_html() const
{
StringBuilder builder;
Vector<String> open_tags;
Style current_style;
for (auto& span : m_spans) {
struct TagAndFlag {
String tag;
bool Style::*flag;
};
TagAndFlag tags_and_flags[] = {
{ "i", &Style::emph },
{ "b", &Style::strong },
{ "code", &Style::code }
};
auto it = open_tags.find_if([&](const String& open_tag) {
if (open_tag == "a" && current_style.href != span.style.href)
return true;
if (open_tag == "img" && current_style.img != span.style.img)
return true;
for (auto& tag_and_flag : tags_and_flags) {
if (open_tag == tag_and_flag.tag && !(span.style.*tag_and_flag.flag))
return true;
}
return false;
});
if (!it.is_end()) {
// We found an open tag that should
// not be open for the new span. Close
// it and all the open tags that follow
// it.
for (ssize_t j = open_tags.size() - 1; j >= static_cast<ssize_t>(it.index()); --j) {
auto& tag = open_tags[j];
if (tag == "img") {
builder.append("\" />");
current_style.img = {};
continue;
}
builder.appendf("</%s>", tag.characters());
if (tag == "a") {
current_style.href = {};
continue;
}
for (auto& tag_and_flag : tags_and_flags)
if (tag == tag_and_flag.tag)
current_style.*tag_and_flag.flag = false;
}
open_tags.shrink(it.index());
}
if (current_style.href.is_null() && !span.style.href.is_null()) {
open_tags.append("a");
builder.appendf("<a href=\"%s\">", span.style.href.characters());
}
if (current_style.img.is_null() && !span.style.img.is_null()) {
open_tags.append("img");
builder.appendf("<img src=\"%s\" alt=\"", span.style.img.characters());
}
for (auto& tag_and_flag : tags_and_flags) {
if (current_style.*tag_and_flag.flag != span.style.*tag_and_flag.flag) {
open_tags.append(tag_and_flag.tag);
builder.appendf("<%s>", tag_and_flag.tag.characters());
}
}
current_style = span.style;
builder.append(escape_html_entities(span.text));
}
for (ssize_t i = open_tags.size() - 1; i >= 0; --i) {
auto& tag = open_tags[i];
if (tag == "img") {
builder.append("\" />");
continue;
}
builder.appendf("</%s>", tag.characters());
}
return builder.build();
}
String Text::render_for_terminal() const
{
StringBuilder builder;
for (auto& span : m_spans) {
bool needs_styling = span.style.strong || span.style.emph || span.style.code;
if (needs_styling) {
builder.append("\033[");
bool first = true;
if (span.style.strong || span.style.code) {
builder.append('1');
first = false;
}
if (span.style.emph) {
if (!first)
builder.append(';');
builder.append('4');
}
builder.append('m');
}
if (!span.style.href.is_null()) {
if (strstr(span.style.href.characters(), "://") != nullptr) {
builder.append("\033]8;;");
builder.append(span.style.href);
builder.append("\033\\");
}
}
builder.append(span.text.characters());
if (needs_styling)
builder.append("\033[0m");
if (!span.style.href.is_null()) {
// When rendering for the terminal, ignore any
// non-absolute links, because the user has no
// chance to follow them anyway.
if (strstr(span.style.href.characters(), "://") != nullptr) {
builder.appendf(" <%s>", span.style.href.characters());
builder.append("\033]8;;\033\\");
}
}
if (!span.style.img.is_null()) {
if (strstr(span.style.img.characters(), "://") != nullptr) {
builder.appendf(" <%s>", span.style.img.characters());
}
}
}
return builder.build();
}
Optional<Text> Text::parse(const StringView& str)
{
Style current_style;
size_t current_span_start = 0;
int first_span_in_the_current_link = -1;
bool current_link_is_actually_img = false;
Vector<Span> spans;
auto append_span_if_needed = [&](size_t offset) {
ASSERT(current_span_start <= offset);
if (current_span_start != offset) {
Span span {
unescape(str.substring_view(current_span_start, offset - current_span_start)),
current_style
};
spans.append(move(span));
current_span_start = offset;
}
};
for (size_t offset = 0; offset < str.length(); offset++) {
char ch = str[offset];
bool is_escape = ch == '\\';
if (is_escape && offset != str.length() - 1) {
offset++;
continue;
}
bool is_special_character = false;
is_special_character |= ch == '`';
if (!current_style.code)
is_special_character |= ch == '*' || ch == '_' || ch == '[' || ch == ']' || (ch == '!' && offset + 1 < str.length() && str[offset + 1] == '[');
if (!is_special_character)
continue;
append_span_if_needed(offset);
switch (ch) {
case '`':
current_style.code = !current_style.code;
break;
case '*':
case '_':
if (offset + 1 < str.length() && str[offset + 1] == ch) {
offset++;
current_style.strong = !current_style.strong;
} else {
current_style.emph = !current_style.emph;
}
break;
case '!':
current_link_is_actually_img = true;
break;
case '[':
#ifdef DEBUG_MARKDOWN
if (first_span_in_the_current_link != -1)
dbgln("Dropping the outer link");
#endif
first_span_in_the_current_link = spans.size();
break;
case ']': {
if (first_span_in_the_current_link == -1) {
#ifdef DEBUG_MARKDOWN
dbgln("Unmatched ]");
#endif
continue;
}
ScopeGuard guard = [&] {
first_span_in_the_current_link = -1;
current_link_is_actually_img = false;
};
if (offset + 2 >= str.length() || str[offset + 1] != '(')
continue;
offset += 2;
size_t start_of_href = offset;
do
offset++;
while (offset < str.length() && str[offset] != ')');
if (offset == str.length())
offset--;
const StringView href = str.substring_view(start_of_href, offset - start_of_href);
for (size_t i = first_span_in_the_current_link; i < spans.size(); i++) {
if (current_link_is_actually_img)
spans[i].style.img = href;
else
spans[i].style.href = href;
}
break;
}
default:
ASSERT_NOT_REACHED();
}
// We've processed the character as a special, so the next offset will
// start after it. Note that explicit continue statements skip over this
// line, effectively treating the character as not special.
current_span_start = offset + 1;
}
append_span_if_needed(str.length());
return Text(move(spans));
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/String.h>
#include <AK/Vector.h>
namespace Markdown {
class Text final {
AK_MAKE_NONCOPYABLE(Text);
public:
struct Style {
bool emph { false };
bool strong { false };
bool code { false };
String href;
String img;
};
struct Span {
String text;
Style style;
};
explicit Text(String&& text);
Text(Text&& text) = default;
Text() = default;
Text& operator=(Text&&) = default;
const Vector<Span>& spans() const { return m_spans; }
String render_to_html() const;
String render_for_terminal() const;
static Optional<Text> parse(const StringView&);
private:
Text(Vector<Span>&& spans)
: m_spans(move(spans))
{
}
Vector<Span> m_spans;
};
}