initial release 1.0.0

This commit is contained in:
M. Petra Baranski
2017-12-25 12:22:35 +01:00
commit 569794c4b6
46 changed files with 4073 additions and 0 deletions

190
include/maddy/blockparser.h Normal file
View File

@ -0,0 +1,190 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* BlockParser
*
* The code expects every child to have the following static function to be
* implemented:
* `static bool IsStartingLine(const std::string& line)`
*
* @class
*/
class BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
BlockParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: result("", std::ios_base::ate | std::ios_base::in | std::ios_base::out)
, childParser(nullptr)
, parseLineCallback(parseLineCallback)
, getBlockParserForLineCallback(getBlockParserForLineCallback)
{}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
virtual void
AddLine(std::string& line)
{
this->parseBlock(line);
if (this->isInlineBlockAllowed() && !this->childParser)
{
this->childParser = this->getBlockParserForLine(line);
}
if (this->childParser)
{
this->childParser->AddLine(line);
if (this->childParser->IsFinished())
{
this->result << this->childParser->GetResult().str();
this->childParser = nullptr;
}
return;
}
if (this->isLineParserAllowed())
{
this->parseLine(line);
}
this->result << line;
}
/**
* IsFinished
*
* Check if the BlockParser is done
*
* @method
* @return {bool}
*/
virtual bool IsFinished() const = 0;
/**
* GetResult
*
* Get the parsed HTML output.
*
* @method
* @return {std::stringstream}
*/
std::stringstream&
GetResult()
{
return this->result;
}
/**
* Clear
*
* Clear the result to reuse the parser object.
*
* It is only used by one test for now.
*
* @method
* @return {void}
*/
void
Clear()
{
this->result.str("");
}
protected:
std::stringstream result;
std::shared_ptr<BlockParser> childParser;
virtual bool isInlineBlockAllowed() const = 0;
virtual bool isLineParserAllowed() const = 0;
virtual void parseBlock(std::string& line) = 0;
void
parseLine(std::string& line)
{
if (parseLineCallback)
{
parseLineCallback(line);
}
}
uint32_t
getIndentationWidth(const std::string& line) const
{
bool hasMetNonSpace = false;
uint32_t indentation = std::count_if(
line.begin(),
line.end(),
[&hasMetNonSpace](unsigned char c)
{
if (hasMetNonSpace)
{
return false;
}
if (std::isspace(c))
{
return true;
}
hasMetNonSpace = true;
return false;
}
);
return indentation;
}
std::shared_ptr<BlockParser>
getBlockParserForLine(const std::string& line)
{
if (getBlockParserForLineCallback)
{
return getBlockParserForLineCallback(line);
}
return nullptr;
}
private:
std::function<void(std::string&)> parseLineCallback;
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback;
}; // class BlockParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,140 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ChecklistParser
*
* @class
*/
class ChecklistParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
ChecklistParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An unordered list starts with `* `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^- \\[[x| ]\\] .*");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return true;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string& line) override
{
bool isStartOfNewListItem = IsStartingLine(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex lineRegex("^(- )");
line = std::regex_replace(line, lineRegex, "");
static std::regex emptyBoxRegex("^\\[ \\]");
static std::string emptyBoxReplacement = "<input type=\"checkbox\"/>";
line = std::regex_replace(line, emptyBoxRegex, emptyBoxReplacement);
static std::regex boxRegex("^\\[x\\]");
static std::string boxReplacement = "<input type=\"checkbox\" checked=\"checked\"/>";
line = std::regex_replace(line, boxRegex, boxReplacement);
if (!this->isStarted)
{
line = "<ul class=\"checklist\"><li><label>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (
line.empty() ||
line.find("</label></li><li><label>") != std::string::npos ||
line.find("</label></li></ul>") != std::string::npos
)
{
line = "</label></li></ul>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</label></li><li><label>" + line;
}
}
private:
bool isStarted;
bool isFinished;
}; // class ChecklistParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,137 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include <regex>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* CodeBlockParser
*
* From Markdown: 3 times surrounded code (without space in the beginning)
*
* ```
* ```
* some code
* ```
* ```
*
* To HTML:
*
* ```
* <pre><code>
* some code
* </code></pre>
* ```
*
* @class
*/
class CodeBlockParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
CodeBlockParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* If the line starts with three code signs, then it is a code block.
*
* ```
* ```
* ```
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^(?:`){3}$");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return false;
}
bool
isLineParserAllowed() const override
{
return false;
}
void
parseBlock(std::string& line) override
{
if (line == "```")
{
if (!this->isStarted)
{
line = "<pre><code>\n";
this->isStarted = true;
this->isFinished = false;
return;
}
else
{
line = "</code></pre>";
this->isFinished = true;
this->isStarted = false;
return;
}
}
line += "\n";
}
private:
bool isStarted;
bool isFinished;
}; // class CodeBlockParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,53 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* EmphasizedParser
*
* Has to be used after the `StrongParser`.
*
* @class
*/
class EmphasizedParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text *text*`
*
* To HTML: `text <em>text</em>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("\\*([^\\*]*)\\*");
static std::string replacement = "<em>$1</em>";
line = std::regex_replace(line, re, replacement);
}
}; // class EmphasizedParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,138 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include <regex>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* HeadlineParser
*
* From Markdown:
*
* ```
* # Headline 1
* ## Headline 2
* ### Headline 3
* #### Headline 4
* ##### Headline 5
* ###### Headline 6
* ```
*
* To HTML:
*
* ```
* <h1>Headline 1</h1>
* <h2>Headline 2</h2>
* <h3>Headline 3</h3>
* <h4>Headline 4</h4>
* <h5>Headline 5</h5>
* <h6>Headline 6</h6>
* ```
*
* @class
*/
class HeadlineParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
HeadlineParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
{}
/**
* IsStartingLine
*
* If the line starts with 1 - 6 `#`, then it is a headline.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^(?:#){1,6} (.*)");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* The headline is always only one line long, so this method always returns
* true.
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return true;
}
protected:
bool
isInlineBlockAllowed() const override
{
return false;
}
bool
isLineParserAllowed() const override
{
return false;
}
void
parseBlock(std::string& line) override
{
static std::vector<std::regex> hlRegex = {
std::regex("^# (.*)")
, std::regex("^(?:#){2} (.*)")
, std::regex("^(?:#){3} (.*)")
, std::regex("^(?:#){4} (.*)")
, std::regex("^(?:#){5} (.*)")
, std::regex("^(?:#){6} (.*)")
};
static std::vector<std::string> hlReplacement = {
"<h1>$1</h1>"
, "<h2>$1</h2>"
, "<h3>$1</h3>"
, "<h4>$1</h4>"
, "<h5>$1</h5>"
, "<h6>$1</h6>"
};
for (uint8_t i = 0; i < 6; ++i)
{
line = std::regex_replace(line, hlRegex[i], hlReplacement[i]);
}
}
}; // class HeadlineParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,106 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include <regex>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* HorizontalLineParser
*
* From Markdown: `---`
*
* To HTML: `<hr/>`
*
* @class
*/
class HorizontalLineParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
HorizontalLineParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, lineRegex("^---$")
{}
/**
* IsStartingLine
*
* If the line has exact three dashes `---`, then it is a horizontal line.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^---$");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* The horizontal line is always only one line long, so this method always
* returns true.
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return true;
}
protected:
bool
isInlineBlockAllowed() const override
{
return false;
}
bool
isLineParserAllowed() const override
{
return false;
}
void
parseBlock(std::string& line) override
{
static std::string replacement = "<hr/>";
line = std::regex_replace(line, lineRegex, replacement);
}
private:
std::regex lineRegex;
}; // class HorizontalLineParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,53 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ImageParser
*
* Has to be used before the `LinkParser`.
*
* @class
*/
class ImageParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `![text](http://example.com/a.png)`
*
* To HTML: `<img src="http://example.com/a.png" alt="text"/>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("\\!\\[([^\\]]*)\\]\\(([^\\]]*)\\)");
static std::string replacement = "<img src=\"$2\" alt=\"$1\"/>";
line = std::regex_replace(line, re, replacement);
}
}; // class ImageParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,51 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* InlineCodeParser
*
* @class
*/
class InlineCodeParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text `some code``
*
* To HTML: `text <code>some code</code>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("`([^`]*)`");
static std::string replacement = "<code>$1</code>";
line = std::regex_replace(line, re, replacement);
}
}; // class InlineCodeParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,39 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* LineParser
*
* @class
*/
class LineParser
{
public:
/**
* Parse
*
* From Markdown to HTML
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
virtual void Parse(std::string& line) = 0;
}; // class LineParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,53 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* LinkParser
*
* Has to be used after the `ImageParser`.
*
* @class
*/
class LinkParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `[text](http://example.com)`
*
* To HTML: `<a href="http://example.com">text</a>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("\\[([^\\]]*)\\]\\(([^\\]]*)\\)");
static std::string replacement = "<a href=\"$2\">$1</a>";
line = std::regex_replace(line, re, replacement);
}
}; // class LinkParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,142 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* OrderedListParser
*
* @class
*/
class OrderedListParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
OrderedListParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An ordered list starts with `1. `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^1\\. .*");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return true;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string& line) override
{
bool isStartOfNewListItem = this->isStartOfNewListItem(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex orderedlineRegex("^1\\. ");
line = std::regex_replace(line, orderedlineRegex, "");
static std::regex unorderedlineRegex("^(\\* )");
line = std::regex_replace(line, unorderedlineRegex, "");
if (!this->isStarted)
{
line = "<ol><li>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (
line.empty() ||
line.find("</li><li>") != std::string::npos ||
line.find("</li></ol>") != std::string::npos ||
line.find("</li></ul>") != std::string::npos
)
{
line = "</li></ol>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</li><li>" + line;
}
}
private:
bool isStarted;
bool isFinished;
bool
isStartOfNewListItem(const std::string& line) const
{
static std::regex re("^(?:1\\. |\\* ).*");
return std::regex_match(line, re);
}
}; // class OrderedListParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,114 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* ParagraphParser
*
* @class
*/
class ParagraphParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
ParagraphParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* If the line is not empty, it will be a paragraph. So this block parser has
* to always run as the last one!
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
return !line.empty();
}
/**
* IsFinished
*
* An empty line will end the paragraph.
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return false;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string& line) override
{
if (!this->isStarted)
{
line = "<p>" + line + " ";
this->isStarted = true;
return;
}
if (line.empty())
{
line += "</p>";
this->isFinished = true;
return;
}
line += " ";
}
private:
bool isStarted;
bool isFinished;
}; // class ParagraphParser
// -----------------------------------------------------------------------------
} // namespace maddy

255
include/maddy/parser.h Normal file
View File

@ -0,0 +1,255 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <memory>
#include <functional>
#include <string>
// BlockParser
#include "maddy/checklistparser.h"
#include "maddy/codeblockparser.h"
#include "maddy/headlineparser.h"
#include "maddy/horizontallineparser.h"
#include "maddy/orderedlistparser.h"
#include "maddy/paragraphparser.h"
#include "maddy/quoteparser.h"
#include "maddy/tableparser.h"
#include "maddy/unorderedlistparser.h"
// LineParser
#include "maddy/emphasizedparser.h"
#include "maddy/imageparser.h"
#include "maddy/inlinecodeparser.h"
#include "maddy/linkparser.h"
#include "maddy/strikethroughparser.h"
#include "maddy/strongparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* Parser
*
* Transforms Markdown to HTML
*
* @class
*/
class Parser
{
public:
/**
* ctor
*
* Initializes all `LineParser`
*
* @method
*/
Parser()
: emphasizedParser(std::make_shared<EmphasizedParser>())
, imageParser(std::make_shared<ImageParser>())
, inlineCodeParser(std::make_shared<InlineCodeParser>())
, linkParser(std::make_shared<LinkParser>())
, strikeThroughParser(std::make_shared<StrikeThroughParser>())
, strongParser(std::make_shared<StrongParser>())
{}
/**
* Parse
*
* @method
* @param {const std::stringstream&} markdown
* @return {std::string} HTML
*/
std::string
Parse(std::stringstream& markdown) const
{
std::string result = "";
std::shared_ptr<BlockParser> currentBlockParser = nullptr;
for (std::string line; std::getline(markdown, line);)
{
if (!currentBlockParser)
{
currentBlockParser = getBlockParserForLine(line);
}
if (currentBlockParser)
{
currentBlockParser->AddLine(line);
if (currentBlockParser->IsFinished())
{
result += currentBlockParser->GetResult().str();
currentBlockParser = nullptr;
}
}
}
return result;
}
private:
std::shared_ptr<EmphasizedParser> emphasizedParser;
std::shared_ptr<ImageParser> imageParser;
std::shared_ptr<InlineCodeParser> inlineCodeParser;
std::shared_ptr<LinkParser> linkParser;
std::shared_ptr<StrikeThroughParser> strikeThroughParser;
std::shared_ptr<StrongParser> strongParser;
// block parser have to run before
void
runLineParser(std::string& line) const
{
// Attention! ImageParser has to be before LinkParser
this->imageParser->Parse(line);
this->linkParser->Parse(line);
// Attention! StrongParser has to be before EmphasizedParser
this->strongParser->Parse(line);
this->emphasizedParser->Parse(line);
this->strikeThroughParser->Parse(line);
this->inlineCodeParser->Parse(line);
}
std::shared_ptr<BlockParser>
getBlockParserForLine(const std::string& line) const
{
std::shared_ptr<BlockParser> parser;
if (maddy::CodeBlockParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::CodeBlockParser>(
nullptr,
nullptr
);
}
else if (maddy::HeadlineParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::HeadlineParser>(
nullptr,
nullptr
);
}
else if (maddy::HorizontalLineParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::HorizontalLineParser>(
nullptr,
nullptr
);
}
else if (maddy::QuoteParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::QuoteParser>(
[this](std::string& line){ this->runLineParser(line); },
[this](const std::string& line){ return this->getBlockParserForLine(line); }
);
}
else if (maddy::TableParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::TableParser>(
[this](std::string& line){ this->runLineParser(line); },
nullptr
);
}
else if (maddy::ChecklistParser::IsStartingLine(line))
{
parser = this->createChecklistParser();
}
else if (maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if (maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
else if (maddy::ParagraphParser::IsStartingLine(line))
{
parser = std::make_shared<maddy::ParagraphParser>(
[this](std::string& line){ this->runLineParser(line); },
nullptr
);
}
return parser;
}
std::shared_ptr<BlockParser>
createChecklistParser() const
{
return std::make_shared<maddy::ChecklistParser>(
[this](std::string& line){ this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if (maddy::ChecklistParser::IsStartingLine(line))
{
parser = this->createChecklistParser();
}
return parser;
}
);
}
std::shared_ptr<BlockParser>
createOrderedListParser() const
{
return std::make_shared<maddy::OrderedListParser>(
[this](std::string& line){ this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if (maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if (maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
return parser;
}
);
}
std::shared_ptr<BlockParser>
createUnorderedListParser() const
{
return std::make_shared<maddy::UnorderedListParser>(
[this](std::string& line){ this->runLineParser(line); },
[this](const std::string& line)
{
std::shared_ptr<BlockParser> parser;
if (maddy::OrderedListParser::IsStartingLine(line))
{
parser = this->createOrderedListParser();
}
else if (maddy::UnorderedListParser::IsStartingLine(line))
{
parser = this->createUnorderedListParser();
}
return parser;
}
);
}
}; // class Parser
// -----------------------------------------------------------------------------
} // namespace maddy

165
include/maddy/quoteparser.h Normal file
View File

@ -0,0 +1,165 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* QuoteParser
*
* @class
*/
class QuoteParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
QuoteParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* A quote starts with `> `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^\\>.*");
return std::regex_match(line, re);
}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
void
AddLine(std::string& line) override
{
if (!this->isStarted)
{
this->result << "<blockquote>";
this->isStarted = true;
}
bool finish = false;
if (line.empty())
{
finish = true;
}
this->parseBlock(line);
if (this->isInlineBlockAllowed() && !this->childParser)
{
this->childParser = this->getBlockParserForLine(line);
}
if (this->childParser)
{
this->childParser->AddLine(line);
if (this->childParser->IsFinished())
{
this->result << this->childParser->GetResult().str();
this->childParser = nullptr;
}
return;
}
if (this->isLineParserAllowed())
{
this->parseLine(line);
}
if (finish)
{
this->result << "</blockquote>";
this->isFinished = true;
}
this->result << line;
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return true;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string& line) override
{
static std::regex lineRegexWithSpace("^\\> ");
line = std::regex_replace(line, lineRegexWithSpace, "");
static std::regex lineRegexWithoutSpace("^\\>");
line = std::regex_replace(line, lineRegexWithoutSpace, "");
if (!line.empty())
{
line += " ";
}
}
private:
bool isStarted;
bool isFinished;
}; // class QuoteParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,51 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* StrikeThroughParser
*
* @class
*/
class StrikeThroughParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text ~~text~~`
*
* To HTML: `text <s>text</s>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("\\~\\~([^\\~]*)\\~\\~");
static std::string replacement = "<s>$1</s>";
line = std::regex_replace(line, re, replacement);
}
}; // class StrikeThroughParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,53 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <string>
#include <regex>
#include "maddy/lineparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* StrongParser
*
* Has to be used before the `EmphasizedParser`.
*
* @class
*/
class StrongParser : public LineParser
{
public:
/**
* Parse
*
* From Markdown: `text **text**`
*
* To HTML: `text <strong>text</strong>`
*
* @method
* @param {std::string&} line The line to interpret
* @return {void}
*/
void
Parse(std::string& line) override
{
static std::regex re("\\*\\*([^\\*\\*]*)\\*\\*");
static std::string replacement = "<strong>$1</strong>";
line = std::regex_replace(line, re, replacement);
}
}; // class StrongParser
// -----------------------------------------------------------------------------
} // namespace maddy

246
include/maddy/tableparser.h Normal file
View File

@ -0,0 +1,246 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <string>
#include <regex>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* TableParser
*
* For more information, see the docs folder.
*
* @class
*/
class TableParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
TableParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
, currentBlock(0)
, currentRow(0)
{}
/**
* IsStartingLine
*
* If the line has exact `|table>`, then it is starting the table.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::string matchString("|table>");
return line == matchString;
}
/**
* AddLine
*
* Adding a line which has to be parsed.
*
* @method
* @param {std::string&} line
* @return {void}
*/
void
AddLine(std::string& line) override
{
if (!this->isStarted && line == "|table>")
{
this->isStarted = true;
return;
}
if (this->isStarted)
{
if (line == "- | - | -")
{
++this->currentBlock;
this->currentRow = 0;
return;
}
if (line == "|<table")
{
static std::string emptyLine = "";
this->parseBlock(emptyLine);
this->isFinished = true;
return;
}
if (this->table.size() < this->currentBlock + 1)
{
this->table.push_back(std::vector<std::vector<std::string>>());
}
this->table[this->currentBlock].push_back(std::vector<std::string>());
std::string segment;
std::stringstream streamToSplit(line);
while (std::getline(streamToSplit, segment, '|'))
{
this->parseLine(segment);
this->table[this->currentBlock][this->currentRow].push_back(segment);
}
++this->currentRow;
}
}
/**
* IsFinished
*
* A table ends with `|<table`.
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return false;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string&) override
{
result << "<table>";
bool hasHeader = false;
bool hasFooter = false;
bool isFirstBlock = true;
uint32_t currentBlockNumber = 0;
if (this->table.size() > 1)
{
hasHeader = true;
}
if (this->table.size() >= 3)
{
hasFooter = true;
}
for (const std::vector<std::vector<std::string>>& block : this->table)
{
bool isInHeader = false;
bool isInFooter = false;
++currentBlockNumber;
if (hasHeader && isFirstBlock)
{
result << "<thead>";
isInHeader = true;
}
else if (hasFooter && currentBlockNumber == this->table.size())
{
result << "<tfoot>";
isInFooter = true;
}
else
{
result << "<tbody>";
}
for (const std::vector<std::string>& row : block)
{
result << "<tr>";
for (const std::string& column : row)
{
if (isInHeader)
{
result << "<th>";
}
else
{
result << "<td>";
}
result << column;
if (isInHeader)
{
result << "</th>";
}
else
{
result << "</td>";
}
}
result << "</tr>";
}
if (isInHeader)
{
result << "</thead>";
}
else if (isInFooter)
{
result << "</tfoot>";
}
else
{
result << "</tbody>";
}
isFirstBlock = false;
}
result << "</table>";
}
private:
bool isStarted;
bool isFinished;
uint32_t currentBlock;
uint32_t currentRow;
std::vector<std::vector<std::vector<std::string>>> table;
}; // class TableParser
// -----------------------------------------------------------------------------
} // namespace maddy

View File

@ -0,0 +1,133 @@
/*
* This project is licensed under the MIT license. For more information see the
* LICENSE file.
*/
#pragma once
// -----------------------------------------------------------------------------
#include <functional>
#include <regex>
#include <string>
#include "maddy/blockparser.h"
// -----------------------------------------------------------------------------
namespace maddy {
// -----------------------------------------------------------------------------
/**
* UnorderedListParser
*
* @class
*/
class UnorderedListParser : public BlockParser
{
public:
/**
* ctor
*
* @method
* @param {std::function<void(std::string&)>} parseLineCallback
* @param {std::function<std::shared_ptr<BlockParser>(const std::string& line)>} getBlockParserForLineCallback
*/
UnorderedListParser(
std::function<void(std::string&)> parseLineCallback,
std::function<std::shared_ptr<BlockParser>(const std::string& line)> getBlockParserForLineCallback
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
, isStarted(false)
, isFinished(false)
{}
/**
* IsStartingLine
*
* An unordered list starts with `* `.
*
* @method
* @param {const std::string&} line
* @return {bool}
*/
static bool
IsStartingLine(const std::string& line)
{
static std::regex re("^\\* .*");
return std::regex_match(line, re);
}
/**
* IsFinished
*
* @method
* @return {bool}
*/
bool
IsFinished() const override
{
return this->isFinished;
}
protected:
bool
isInlineBlockAllowed() const override
{
return true;
}
bool
isLineParserAllowed() const override
{
return true;
}
void
parseBlock(std::string& line) override
{
bool isStartOfNewListItem = IsStartingLine(line);
uint32_t indentation = getIndentationWidth(line);
static std::regex lineRegex("^(\\* )");
line = std::regex_replace(line, lineRegex, "");
if (!this->isStarted)
{
line = "<ul><li>" + line;
this->isStarted = true;
return;
}
if (indentation >= 2)
{
line = line.substr(2);
return;
}
if (
line.empty() ||
line.find("</li><li>") != std::string::npos ||
line.find("</li></ol>") != std::string::npos ||
line.find("</li></ul>") != std::string::npos
)
{
line = "</li></ul>" + line;
this->isFinished = true;
return;
}
if (isStartOfNewListItem)
{
line = "</li><li>" + line;
}
}
private:
bool isStarted;
bool isFinished;
}; // class UnorderedListParser
// -----------------------------------------------------------------------------
} // namespace maddy