| Left header | +middle header | +last header | +
|---|---|---|
| cell 1 | +cell 2 | +cell 3 | +
| cell 4 | +cell 5 | +cell 6 | +
.*)_(?!.*`.*|.*<\/code>.*)([^_]*)_(?!.*`.*|.*<\/code>.*))"
+ R"((?!.*`.*|.*.*)\b(_*)_(?![\s_])(?!.*`.*|.*<\/code>.*)(.*?[^\s])_(_*)\b(?!.*`.*|.*<\/code>.*))"
);
- static std::string replacement = "$1";
+ static std::string replacement = "$1$2$3";
line = std::regex_replace(line, re, replacement);
}
diff --git a/include/maddy/parser.h b/include/maddy/parser.h
index 660752f..a3c8a18 100644
--- a/include/maddy/parser.h
+++ b/include/maddy/parser.h
@@ -59,7 +59,7 @@ class Parser
*/
static const std::string& version()
{
- static const std::string v = "1.6.0"; // MADDY_VERSION_LINE_REPLACEMENT
+ static const std::string v = "1.5.0";
return v;
}
@@ -278,10 +278,17 @@ class Parser
}
else if ((!this->config || (this->config->enabledParsers &
maddy::types::TABLE_PARSER) != 0) &&
- maddy::TableParser::IsStartingLine(line))
+ maddy::TableParser::IsStartingLine(
+ line,
+ !this->config || (this->config->enabledParsers &
+ maddy::types::MADDY_SPECIFIC_PARSER) != 0
+ ))
{
parser = std::make_shared(
- [this](std::string& line) { this->runLineParser(line); }, nullptr
+ [this](std::string& line) { this->runLineParser(line); },
+ nullptr,
+ !this->config || (this->config->enabledParsers &
+ maddy::types::MADDY_SPECIFIC_PARSER) != 0
);
}
else if ((!this->config || (this->config->enabledParsers &
diff --git a/include/maddy/parserconfig.h b/include/maddy/parserconfig.h
index f95ee04..74ab2c9 100644
--- a/include/maddy/parserconfig.h
+++ b/include/maddy/parserconfig.h
@@ -44,8 +44,13 @@ enum PARSER_TYPE : uint32_t
UNORDERED_LIST_PARSER = 0b100000000000000000,
LATEX_BLOCK_PARSER = 0b1000000000000000000,
- DEFAULT = 0b0111111111110111111,
- ALL = 0b1111111111111111111,
+ // Not a parser of its own: gates maddy's own historical markdown dialect
+ // wherever a parser supports both that and a more standard alternative
+ // (currently just TableParser's `|table>` syntax vs. GFM pipe tables).
+ MADDY_SPECIFIC_PARSER = 0b10000000000000000000,
+
+ DEFAULT = 0b10111111111110111111,
+ ALL = 0b11111111111111111111,
};
// clang-format on
diff --git a/include/maddy/strongparser.h b/include/maddy/strongparser.h
index 348f2d4..b6c0de7 100644
--- a/include/maddy/strongparser.h
+++ b/include/maddy/strongparser.h
@@ -40,19 +40,21 @@ class StrongParser : public LineParser
*/
void Parse(std::string& line) override
{
- static std::vector res{
- std::regex{
- R"((?!.*`.*|.*.*)\*\*(?!.*`.*|.*<\/code>.*)([^\*\*]*)\*\*(?!.*`.*|.*<\/code>.*))"
- },
- std::regex{
- R"((?!.*`.*|.*.*)__(?!.*`.*|.*<\/code>.*)([^__]*)__(?!.*`.*|.*<\/code>.*))"
- }
+ // `*` is not a word character, so `\b` next to it does not mean
+ // "edge of a delimiter run" the way it does for `_`; the asterisk
+ // variant is left without a word-boundary anchor.
+ static std::regex reAsterisk{
+ R"((?!.*`.*|.*.*)\*\*(?![\s])(?!.*`.*|.*<\/code>.*)(.*?[^\s])\*\*(?!.*`.*|.*<\/code>.*))"
};
- static std::string replacement = "$1";
- for (const auto& re : res)
- {
- line = std::regex_replace(line, re, replacement);
- }
+ // The leading and trailing `(_*)` groups absorb any leftover underscores
+ // from an unbalanced run on either side (e.g. `___text__` or
+ // `__text_______`), re-emitted outside the tag by the caller
+ // instead of being swallowed into its content.
+ static std::regex reUnderscore{
+ R"((?!.*`.*|.*.*)\b(_*)__(?![\s_])(?!.*`.*|.*<\/code>.*)(.*?[^\s])__(_*)\b(?!.*`.*|.*<\/code>.*))"
+ };
+ line = std::regex_replace(line, reAsterisk, "$1");
+ line = std::regex_replace(line, reUnderscore, "$1$2$3");
}
}; // class StrongParser
diff --git a/include/maddy/tableparser.h b/include/maddy/tableparser.h
index 9f1051f..7f7e6bc 100644
--- a/include/maddy/tableparser.h
+++ b/include/maddy/tableparser.h
@@ -7,10 +7,14 @@
// -----------------------------------------------------------------------------
#include
+#include
#include
+#include
#include
+#include
#include "maddy/blockparser.h"
+#include "maddy/paragraphparser.h"
// -----------------------------------------------------------------------------
@@ -21,7 +25,37 @@ namespace maddy {
/**
* TableParser
*
- * For more information, see the docs folder.
+ * Supports two independent syntaxes, chosen with `useMaddySpecificMarkdown`
+ * (see `maddy::types::MADDY_SPECIFIC_PARSER`).
+ *
+ * When true (the default, and maddy's original behavior), a table uses
+ * maddy's own sigils:
+ *
+ * ```
+ * |table>
+ * Left header|middle header|last header
+ * - | - | -
+ * Cell A1|Cell B1|Cell C1
+ * - | - | -
+ * Foot A|Foot B|Foot C
+ * |`. Since `IsStartingLine` only sees one line at a time in this
+ * mode, it can't yet tell a table header from an ordinary line that happens
+ * to contain a `|`; if the following line isn't a valid separator row, both
+ * lines are handed off to a ParagraphParser instead.
*
* @class
*/
@@ -35,32 +69,47 @@ class TableParser : public BlockParser
* @param {std::function} parseLineCallback
* @param {std::function(const std::string&
* line)>} getBlockParserForLineCallback
+ * @param {bool} useMaddySpecificMarkdown
*/
TableParser(
std::function parseLineCallback,
std::function(const std::string& line)>
- getBlockParserForLineCallback
+ getBlockParserForLineCallback,
+ bool useMaddySpecificMarkdown = true
)
: BlockParser(parseLineCallback, getBlockParserForLineCallback)
+ , useMaddySpecificMarkdown(useMaddySpecificMarkdown)
, isStarted(false)
, isFinished(false)
, currentBlock(0)
, currentRow(0)
+ , gfmState(GfmState::EXPECT_HEADER)
{}
/**
* IsStartingLine
*
- * If the line has exact `|table>`, then it is starting the table.
+ * With maddy-specific markdown, a table starts with exact `|table>`.
+ * With GFM markdown, a table can only start with a row that has at least
+ * one `|` separating two cells; whether it really is a table is only
+ * known once the following line (the separator row) has been seen.
*
* @method
* @param {const std::string&} line
+ * @param {bool} useMaddySpecificMarkdown
* @return {bool}
*/
- static bool IsStartingLine(const std::string& line)
+ static bool IsStartingLine(
+ const std::string& line, bool useMaddySpecificMarkdown = true
+ )
{
- static std::string matchString("|table>");
- return line == matchString;
+ if (useMaddySpecificMarkdown)
+ {
+ static std::string matchString("|table>");
+ return line == matchString;
+ }
+
+ return IsTableRow(line);
}
/**
@@ -74,52 +123,21 @@ class TableParser : public BlockParser
*/
void AddLine(std::string& line) override
{
- if (!this->isStarted && line == "|table>")
+ if (this->useMaddySpecificMarkdown)
{
- this->isStarted = true;
- return;
+ this->AddLineMaddyStyle(line);
}
-
- if (this->isStarted)
+ else
{
- if (line == "- | - | -")
- {
- ++this->currentBlock;
- this->currentRow = 0;
- return;
- }
-
- if (line == "|parseBlock(emptyLine);
- this->isFinished = true;
- return;
- }
-
- if (this->table.size() < this->currentBlock + 1)
- {
- this->table.push_back(std::vector>());
- }
- this->table[this->currentBlock].push_back(std::vector());
-
- 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;
+ this->AddLineGfm(line);
}
}
/**
* IsFinished
*
- * A table ends with `|";
@@ -221,11 +241,223 @@ class TableParser : public BlockParser
}
private:
+ bool useMaddySpecificMarkdown;
+
+ // --- maddy-specific-markdown mode state ---
bool isStarted;
bool isFinished;
uint32_t currentBlock;
uint32_t currentRow;
std::vector>> table;
+
+ void AddLineMaddyStyle(std::string& line)
+ {
+ if (!this->isStarted && line == "|table>")
+ {
+ this->isStarted = true;
+ return;
+ }
+
+ if (this->isStarted)
+ {
+ if (line == "- | - | -")
+ {
+ ++this->currentBlock;
+ this->currentRow = 0;
+ return;
+ }
+
+ if (line == "|parseBlock(emptyLine);
+ this->isFinished = true;
+ return;
+ }
+
+ if (this->table.size() < this->currentBlock + 1)
+ {
+ this->table.push_back(std::vector>());
+ }
+ this->table[this->currentBlock].push_back(std::vector());
+
+ 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;
+ }
+ }
+
+ // --- GFM-pipe-table mode state ---
+ enum class GfmState
+ {
+ EXPECT_HEADER,
+ EXPECT_SEPARATOR,
+ IN_BODY
+ };
+
+ GfmState gfmState;
+ std::string headerLine;
+ std::shared_ptr fallbackParser;
+
+ void AddLineGfm(std::string& line)
+ {
+ if (this->fallbackParser)
+ {
+ this->fallbackParser->AddLine(line);
+
+ if (this->fallbackParser->IsFinished())
+ {
+ this->result << this->fallbackParser->GetResult().str();
+ this->isFinished = true;
+ }
+
+ return;
+ }
+
+ switch (this->gfmState)
+ {
+ case GfmState::EXPECT_HEADER:
+ this->headerLine = line;
+ this->gfmState = GfmState::EXPECT_SEPARATOR;
+ return;
+
+ case GfmState::EXPECT_SEPARATOR:
+ if (IsSeparatorRow(line) &&
+ SplitRow(line).size() == SplitRow(this->headerLine).size())
+ {
+ this->WriteGfmHeader();
+ this->gfmState = GfmState::IN_BODY;
+ }
+ else
+ {
+ this->FallBackToParagraph(line);
+ }
+ return;
+
+ case GfmState::IN_BODY:
+ if (line.empty())
+ {
+ this->result << "
";
+ this->isFinished = true;
+ }
+ else
+ {
+ this->WriteGfmRow(line);
+ }
+ return;
+ }
+ }
+
+ static bool IsTableRow(const std::string& line)
+ {
+ return line.find('|') != std::string::npos &&
+ line.find_first_not_of(" \t") != std::string::npos;
+ }
+
+ static bool IsSeparatorRow(const std::string& line)
+ {
+ if (!IsTableRow(line))
+ {
+ return false;
+ }
+
+ static const std::regex cellRe("^:?-+:?$");
+
+ for (const std::string& cell : SplitRow(line))
+ {
+ if (!std::regex_match(cell, cellRe))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static std::vector SplitRow(const std::string& line)
+ {
+ std::vector cells;
+ std::stringstream stream(line);
+ std::string cell;
+
+ while (std::getline(stream, cell, '|'))
+ {
+ Trim(cell);
+
+ if (!cell.empty())
+ {
+ cells.push_back(cell);
+ }
+ }
+
+ return cells;
+ }
+
+ static void Trim(std::string& str)
+ {
+ size_t first = str.find_first_not_of(" \t");
+
+ if (first == std::string::npos)
+ {
+ str.clear();
+ return;
+ }
+
+ size_t last = str.find_last_not_of(" \t");
+ str = str.substr(first, last - first + 1);
+ }
+
+ void WriteGfmHeader()
+ {
+ this->result << "";
+
+ for (std::string cell : SplitRow(this->headerLine))
+ {
+ this->parseLine(cell);
+ this->result << "" << cell << " ";
+ }
+
+ this->result << " ";
+ }
+
+ void WriteGfmRow(const std::string& line)
+ {
+ this->result << "";
+
+ for (std::string cell : SplitRow(line))
+ {
+ this->parseLine(cell);
+ this->result << "" << cell << " ";
+ }
+
+ this->result << " ";
+ }
+
+ void FallBackToParagraph(const std::string& secondLine)
+ {
+ this->fallbackParser = std::make_shared(
+ [this](std::string& l) { this->parseLine(l); }, nullptr, true
+ );
+
+ std::string first = this->headerLine;
+ this->fallbackParser->AddLine(first);
+
+ std::string second = secondLine;
+ this->fallbackParser->AddLine(second);
+
+ if (this->fallbackParser->IsFinished())
+ {
+ this->result << this->fallbackParser->GetResult().str();
+ this->isFinished = true;
+ }
+ }
}; // class TableParser
// -----------------------------------------------------------------------------
diff --git a/tests/maddy/test_maddy_emphasizedparser.cpp b/tests/maddy/test_maddy_emphasizedparser.cpp
index 6442779..9e1cd52 100644
--- a/tests/maddy/test_maddy_emphasizedparser.cpp
+++ b/tests/maddy/test_maddy_emphasizedparser.cpp
@@ -21,6 +21,121 @@ TEST(MADDY_EMPHASIZEDPARSER, ItReplacesMarkdownWithEmphasizedHTML)
ASSERT_EQ(expected, text);
}
+TEST(MADDY_EMPHASIZEDPARSER, ItReplacesUnderscoresAtStringEdges)
+{
+ std::string text = "_some text_";
+ std::string expected = "some text";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotReplaceMarkdownWithInlineUnderscores)
+{
+ std::string text = "some text_bla_text testing _it_ out";
+ std::string expected = "some text_bla_text testing it out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItOnlyReplacesUnderscoresAtWordBreaks)
+{
+ std::string text = "some _text_bla_ testing _it_ out";
+ std::string expected = "some text_bla testing it out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItReplacesUnderscoresWithMultipleWords)
+{
+ std::string text = "some _text testing it_ out";
+ std::string expected = "some text testing it out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItAllowsDoubleUnderscores)
+{
+ // Per CommonMark, a leftover delimiter from an unbalanced run renders
+ // outside the tag it didn't pair into, not inside it.
+ std::string text = "some __text testing it_ out";
+ std::string expected = "some _text testing it out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItAllowsTrailingDoubleUnderscores)
+{
+ std::string text = "some _text testing it__ out";
+ std::string expected = "some text testing it_ out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItAllowsManyLeadingLeftoverUnderscores)
+{
+ std::string text = "some ____text testing it_ out";
+ std::string expected = "some ___text testing it out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItAllowsManyTrailingLeftoverUnderscores)
+{
+ std::string text = "some _text testing it____ out";
+ std::string expected = "some text testing it___ out";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItDoesntReplaceUnderscoresInsideCodeBlocks)
+{
+ std::string text =
+ "Stuff inside blocks _shouldn't be emphasized_ at all";
+ std::string expected =
+ "Stuff inside blocks _shouldn't be emphasized_ at all";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotReplaceUnderscoresInURLs)
+{
+ std::string text = "[Link Title](http://example.com/what_you_didn't_know)";
+ std::string expected =
+ "[Link Title](http://example.com/what_you_didn't_know)";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotParseInsideInlineCode)
{
std::string text = "some text `*bla*` `/**text*/` testing _it_ out";
@@ -32,3 +147,33 @@ TEST(MADDY_EMPHASIZEDPARSER, ItDoesNotParseInsideInlineCode)
ASSERT_EQ(expected, text);
}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItParsesOutsideCodeBlocks)
+{
+ std::string text =
+ "Stuff inside blocks _shouldn't be emphasized_ "
+ " but outside _should_.";
+ std::string expected =
+ "Stuff inside blocks _shouldn't be emphasized_ "
+ " but outside should.";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_EMPHASIZEDPARSER, ItParsesOutsideTickBlocks)
+{
+ std::string text =
+ "Stuff inside `blocks _shouldn't be emphasized_ `"
+ " but outside _should_.";
+ std::string expected =
+ "Stuff inside `blocks _shouldn't be emphasized_ `"
+ " but outside should.";
+ auto emphasizedParser = std::make_shared();
+
+ emphasizedParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
diff --git a/tests/maddy/test_maddy_parser.cpp b/tests/maddy/test_maddy_parser.cpp
index ed8530a..83a20cb 100644
--- a/tests/maddy/test_maddy_parser.cpp
+++ b/tests/maddy/test_maddy_parser.cpp
@@ -82,3 +82,46 @@ TEST(MADDY_PARSER, ItShouldNotParseInlineCodeInHeadlineIfDisabled)
ASSERT_EQ(expectedHTML, output);
}
+
+TEST(MADDY_PARSER, ItShouldParseGfmTablesWhenMaddySpecificParserIsDisabled)
+{
+ const std::string tableTest =
+ "| Left header | middle header | last header |\n"
+ "| --- | --- | --- |\n"
+ "| cell 1 | cell 2 | cell 3 |\n"
+ "| cell 4 | cell 5 | cell 6 |\n";
+ const std::string expectedHTML =
+ "Left header middle header last "
+ "header cell 1 cell 2 cell "
+ "3 cell 4 cell 5 cell "
+ "6
";
+ std::stringstream markdown(tableTest);
+ auto config = std::make_shared();
+ config->enabledParsers &= ~maddy::types::MADDY_SPECIFIC_PARSER;
+ auto parser = std::make_shared(config);
+
+ const std::string output = parser->Parse(markdown);
+
+ ASSERT_EQ(expectedHTML, output);
+}
+
+TEST(
+ MADDY_PARSER,
+ ItShouldNotParseMaddySpecificTableSyntaxWhenMaddySpecificParserIsDisabled
+)
+{
+ const std::string tableTest =
+ "|table>\n"
+ "A|B\n"
+ "- | - | -\n"
+ "1|2\n"
+ "|();
+ config->enabledParsers &= ~maddy::types::MADDY_SPECIFIC_PARSER;
+ auto parser = std::make_shared(config);
+
+ const std::string output = parser->Parse(markdown);
+
+ ASSERT_EQ(std::string::npos, output.find(""));
+}
diff --git a/tests/maddy/test_maddy_strongparser.cpp b/tests/maddy/test_maddy_strongparser.cpp
index f006e26..1211b0e 100644
--- a/tests/maddy/test_maddy_strongparser.cpp
+++ b/tests/maddy/test_maddy_strongparser.cpp
@@ -83,3 +83,149 @@ TEST(MADDY_STRONGPARSER, ItDoesNotParseInsideInlineCode)
ASSERT_EQ(test.expected, test.text);
}
}
+
+TEST(MADDY_STRONGPARSER, ItReplacesUnderscoresAtStringEdges)
+{
+ std::string text = "__some text__";
+ std::string expected = "some text";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItDoesNotReplaceMarkdownWithInlineUnderscores)
+{
+ std::string text = "some text__bla__text testing __it__ out";
+ std::string expected = "some text__bla__text testing it out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItOnlyReplacesUnderscoresAtWordBreaks)
+{
+ std::string text = "some __text__bla__ testing __it__ out";
+ std::string expected =
+ "some text__bla testing it out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItReplacesUnderscoresWithMultipleWords)
+{
+ std::string text = "some __text testing it__ out";
+ std::string expected = "some text testing it out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItAllowsTripleUnderscores)
+{
+ // Per CommonMark, a leftover delimiter from an unbalanced run renders
+ // outside the tag it didn't pair into, not inside it.
+ std::string text = "some ___text testing it__ out";
+ std::string expected = "some _text testing it out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItAllowsTrailingTripleUnderscores)
+{
+ std::string text = "some __text testing it___ out";
+ std::string expected = "some text testing it_ out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItAllowsManyLeadingLeftoverUnderscores)
+{
+ std::string text = "some ________text testing it__ out";
+ std::string expected = "some ______text testing it out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItAllowsManyTrailingLeftoverUnderscores)
+{
+ std::string text = "some __text testing it_______ out";
+ std::string expected = "some text testing it_____ out";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItDoesntReplaceUnderscoresInsideCodeBlocks)
+{
+ std::string text =
+ "Stuff inside blocks __shouldn't be strong__ at all";
+ std::string expected =
+ "Stuff inside blocks __shouldn't be strong__ at all";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItDoesNotReplaceUnderscoresInURLs)
+{
+ std::string text = "[Link Title](http://example.com/what__you__didn't__know)";
+ std::string expected =
+ "[Link Title](http://example.com/what__you__didn't__know)";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItParsesOutsideCodeBlocks)
+{
+ std::string text =
+ "Stuff inside blocks __shouldn't be strong__ "
+ " but outside __should__.";
+ std::string expected =
+ "Stuff inside blocks __shouldn't be strong__ "
+ " but outside should.";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_STRONGPARSER, ItParsesOutsideTickBlocks)
+{
+ std::string text =
+ "Stuff inside `blocks __shouldn't be strong__ `"
+ " but outside __should__.";
+ std::string expected =
+ "Stuff inside `blocks __shouldn't be strong__ `"
+ " but outside should.";
+ auto strongParser = std::make_shared();
+
+ strongParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
diff --git a/tests/maddy/test_maddy_tableparser.cpp b/tests/maddy/test_maddy_tableparser.cpp
index 5d50d92..592fd7e 100644
--- a/tests/maddy/test_maddy_tableparser.cpp
+++ b/tests/maddy/test_maddy_tableparser.cpp
@@ -67,3 +67,98 @@ TEST_F(MADDY_TABLEPARSER, ItReplacesMarkdownWithAnHtmlTable)
ASSERT_EQ(expected, outputString);
}
+
+// -----------------------------------------------------------------------------
+// GFM pipe-table mode (useMaddySpecificMarkdown = false)
+// -----------------------------------------------------------------------------
+
+class MADDY_TABLEPARSER_GFM : public ::testing::Test
+{
+protected:
+ std::shared_ptr tableParser;
+
+ void SetUp() override
+ {
+ this->tableParser =
+ std::make_shared(nullptr, nullptr, false);
+ }
+};
+
+TEST_F(MADDY_TABLEPARSER_GFM, IsStartingLineReturnsTrueForAnyLineWithAPipe)
+{
+ ASSERT_TRUE(maddy::TableParser::IsStartingLine("| a | b |", false));
+}
+
+TEST_F(MADDY_TABLEPARSER_GFM, IsStartingLineReturnsFalseForABlankLine)
+{
+ ASSERT_FALSE(maddy::TableParser::IsStartingLine(" ", false));
+}
+
+TEST_F(MADDY_TABLEPARSER_GFM, IsFinishedReturnsFalseInTheBeginning)
+{
+ ASSERT_FALSE(tableParser->IsFinished());
+}
+
+TEST_F(MADDY_TABLEPARSER_GFM, ItReplacesMarkdownWithAnHtmlTableAndHasNoFooter)
+{
+ std::vector markdown = {
+ "| Left header | middle header | last header |",
+ "| --- | --- | --- |",
+ "| cell 1 | cell 2 | cell 3 |",
+ "| cell 4 | cell 5 | cell 6 |",
+ ""
+ };
+ std::string expected =
+ "Left header middle header last "
+ "header cell 1 cell 2 cell "
+ "3 cell 4 cell 5 cell "
+ "6
";
+
+ for (std::string md : markdown)
+ {
+ tableParser->AddLine(md);
+ }
+
+ ASSERT_TRUE(tableParser->IsFinished());
+ ASSERT_EQ(expected, tableParser->GetResult().str());
+}
+
+TEST_F(
+ MADDY_TABLEPARSER_GFM,
+ ItFallsBackToAParagraphWhenTheSecondLineIsNotASeparatorRow
+)
+{
+ std::string first = "not | a | table";
+ std::string second = "just some more text";
+ std::string third = "";
+ std::string expected = "not | a | table just some more text
";
+
+ tableParser->AddLine(first);
+ tableParser->AddLine(second);
+
+ if (!tableParser->IsFinished())
+ {
+ tableParser->AddLine(third);
+ }
+
+ ASSERT_TRUE(tableParser->IsFinished());
+ ASSERT_EQ(expected, tableParser->GetResult().str());
+}
+
+TEST_F(MADDY_TABLEPARSER_GFM, OldMaddySpecificSyntaxIsNotRecognizedAsATable)
+{
+ std::string first = "|table>";
+ std::string second = "Left header|middle header|last header";
+ std::string third = "";
+
+ tableParser->AddLine(first);
+ tableParser->AddLine(second);
+
+ if (!tableParser->IsFinished())
+ {
+ tableParser->AddLine(third);
+ }
+
+ ASSERT_TRUE(tableParser->IsFinished());
+ ASSERT_EQ(std::string::npos, tableParser->GetResult().str().find(""));
+}