mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 15:37:46 +00:00
Libraries: Move to Userland/Libraries/
This commit is contained in:
parent
dc28c07fa5
commit
13d7c09125
1857 changed files with 266 additions and 274 deletions
42
Userland/Libraries/LibMarkdown/Block.h
Normal file
42
Userland/Libraries/LibMarkdown/Block.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
13
Userland/Libraries/LibMarkdown/CMakeLists.txt
Normal file
13
Userland/Libraries/LibMarkdown/CMakeLists.txt
Normal 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)
|
160
Userland/Libraries/LibMarkdown/CodeBlock.cpp
Normal file
160
Userland/Libraries/LibMarkdown/CodeBlock.cpp
Normal 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());
|
||||
}
|
||||
|
||||
}
|
56
Userland/Libraries/LibMarkdown/CodeBlock.h
Normal file
56
Userland/Libraries/LibMarkdown/CodeBlock.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
133
Userland/Libraries/LibMarkdown/Document.cpp
Normal file
133
Userland/Libraries/LibMarkdown/Document.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
46
Userland/Libraries/LibMarkdown/Document.h
Normal file
46
Userland/Libraries/LibMarkdown/Document.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
89
Userland/Libraries/LibMarkdown/Heading.cpp
Normal file
89
Userland/Libraries/LibMarkdown/Heading.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
56
Userland/Libraries/LibMarkdown/Heading.h
Normal file
56
Userland/Libraries/LibMarkdown/Heading.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
69
Userland/Libraries/LibMarkdown/HorizontalRule.cpp
Normal file
69
Userland/Libraries/LibMarkdown/HorizontalRule.cpp
Normal 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>();
|
||||
}
|
||||
|
||||
}
|
48
Userland/Libraries/LibMarkdown/HorizontalRule.h
Normal file
48
Userland/Libraries/LibMarkdown/HorizontalRule.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
156
Userland/Libraries/LibMarkdown/List.cpp
Normal file
156
Userland/Libraries/LibMarkdown/List.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
56
Userland/Libraries/LibMarkdown/List.h
Normal file
56
Userland/Libraries/LibMarkdown/List.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
72
Userland/Libraries/LibMarkdown/Paragraph.cpp
Normal file
72
Userland/Libraries/LibMarkdown/Paragraph.cpp
Normal 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());
|
||||
}
|
||||
}
|
68
Userland/Libraries/LibMarkdown/Paragraph.h
Normal file
68
Userland/Libraries/LibMarkdown/Paragraph.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
238
Userland/Libraries/LibMarkdown/Table.cpp
Normal file
238
Userland/Libraries/LibMarkdown/Table.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
64
Userland/Libraries/LibMarkdown/Table.h
Normal file
64
Userland/Libraries/LibMarkdown/Table.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
298
Userland/Libraries/LibMarkdown/Text.cpp
Normal file
298
Userland/Libraries/LibMarkdown/Text.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
74
Userland/Libraries/LibMarkdown/Text.h
Normal file
74
Userland/Libraries/LibMarkdown/Text.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue