diff --git a/AUTHORS b/AUTHORS
index 04e0cc0..698a0d0 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -11,3 +11,4 @@ Andrew Mettlach (dmmettlach@gmail.com)
Evan Klitzke (evan@eklitzke.org)
Albert Schwarzkopf (dev-maddy@quitesimple.org)
Ivans Saponenko (ivans.saponenko+maddy@gmail.com)
+Lucian Smith (lpsmith@uw.edu)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9594374..1795c10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,10 @@ maddy uses [semver versioning](https://semver.org/).
*  for now removed features.
## Upcoming
+*  Correctly parse links with title text, i.e. `[link](http://example.com "example")`.
+*  Do not create invalid URLs from links with spaces, i.e. `[link](/ABC/some file)`.
+*  Do not create invalid HTML from links with quotes, i.e. `[link](/ABC/some"file)`.
+
## version 1.4.0 2025-03-28
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7758e6e..2154759 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,4 +16,4 @@ improve the code? Then [create a GitHub issue](https://github.com/progsource/mad
* Explain for what your PR is for - like providing a use-case or something similar.
* Update documentation of the Markdown syntax if anything changed there. (`docs/definitions.md`)
* Add a changelog entry at "Upcoming" inside of `CHANGELOG.md`
-* Make sure, that the tests are successful and if you wrote a bugfix, to have a test, that highlights the issue.
+* Make sure that the tests are successful and if you wrote a bugfix, to have a test that highlights the issue.
diff --git a/docs/definitions.md b/docs/definitions.md
index a886ffb..1a3800e 100644
--- a/docs/definitions.md
+++ b/docs/definitions.md
@@ -41,6 +41,14 @@ results in
Text of the link
```
+```
+[Text of the link](http://example.com "title text")
+```
+results in
+```html
+Text of the link
+```
+
## Lists
### unordered
diff --git a/include/maddy/linkparser.h b/include/maddy/linkparser.h
index 2866a22..d555d40 100644
--- a/include/maddy/linkparser.h
+++ b/include/maddy/linkparser.h
@@ -40,10 +40,17 @@ public:
*/
void Parse(std::string& line) override
{
- static std::regex re(R"(\[([^\]]*)\]\(([^)]*)\))");
- static std::string replacement = "$1";
-
+ // Match [name](http:://link "title text")
+ // NOTE: the 'no quote' bit at the beginning (^") is a hack for now:
+ // there should eventually be something that replaces it with '%22'.
+ static std::regex re(R"(\[([^\]]*)\]\( *([^)^ ^"]*) *\"([^\"]*)\" *\))");
+ static std::string replacement = "$1";
line = std::regex_replace(line, re, replacement);
+
+ // Match [name](http:://link)
+ static std::regex re2(R"(\[([^\]]*)\]\( *([^)^ ^"]*) *\))");
+ static std::string replacement2 = "$1";
+ line = std::regex_replace(line, re2, replacement2);
}
}; // class LinkParser
diff --git a/tests/maddy/test_maddy_linkparser.cpp b/tests/maddy/test_maddy_linkparser.cpp
index 529c122..e65d2fe 100644
--- a/tests/maddy/test_maddy_linkparser.cpp
+++ b/tests/maddy/test_maddy_linkparser.cpp
@@ -37,14 +37,47 @@ TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithLinks)
ASSERT_EQ(expected, text);
}
+TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithSpacesAfterLink)
+{
+ std::string text =
+ "Some text [Link Title](http://example.com ) bla [Link "
+ "Title](http://example.com)";
+ std::string expected =
+ "Some text Link Title bla Link Title";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItHandlesURLsWithOfficiallyIllegalCharacters)
+{
+ // Some links in the real world have characters that are not
+ // 'official' characters that are supposedly allowed in URLs.
+ std::string text =
+ "Wikipedia's [Möbius strip]"
+ "(https://en.wikipedia.org/wiki/Möbius_strip) link.";
+ std::string expected =
+ "Wikipedia's "
+ "Möbius strip link.";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
TEST(
MADDY_LINKPARSER, ItReplacesMarkdownProperlyEvenWithMultipleParenthesisInLine
)
{
std::string text =
- "(This is a [link](/ABC/some file) (the URL will include this).)";
+ "(This is a [link](/ABC/some_file) (the URL will not include this).)";
std::string expected =
- "(This is a link (the URL will include "
+ "(This is a link (the URL will not include "
"this).)";
auto linkParser = std::make_shared();
@@ -53,6 +86,99 @@ TEST(
ASSERT_EQ(expected, text);
}
+TEST(MADDY_LINKPARSER, ItDoesntReplaceMarkdownWithSpaceInURL)
+{
+ // Spaces are not allowed in URLs, so don't match them.
+ std::string text = "This is an invalid [link](/ABC/some file)";
+ std::string expected = "This is an invalid [link](/ABC/some file)";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithTitleText)
+{
+ std::string text = "Link to [name](http:://example.com \"title text\")";
+ std::string expected =
+ "Link to name";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithSpacesWithTitleText)
+{
+ std::string text = "Link to [name](http:://example.com \"title text\")";
+ std::string expected =
+ "Link to name";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithMoreSpacesWithTitleText)
+{
+ std::string text =
+ "Link to [name](http:://example.com \"title text\" )";
+ std::string expected =
+ "Link to name";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItReplacesMarkdownWithParentheticalText)
+{
+ std::string text = "Link to [name](http:://example.com \"title (text)\")";
+ std::string expected =
+ "Link to name";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItDoesntReplaceMarkdownWithTooManyQuotes)
+{
+ // If you have too many quotation marks, don't match:
+ std::string text =
+ "This is an invalid [link](/ABC/some_file \"title \" text \")";
+ std::string expected =
+ "This is an invalid [link](/ABC/some_file \"title \" text \")";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}
+
+TEST(MADDY_LINKPARSER, ItDoesntReplaceMarkdownWithQuoteInLink)
+{
+ // This is actually legal markdown, but hard to parse with regexes;
+ // See disabled 'ItReplacesMarkdownWithQuoteInLink' below.
+ //
+ // For now, don't try to translate it; it would produce invalid HTML.
+
+ std::string text = "Some text [Link Title](http://example.com/\"foo ) bla.";
+ std::string current_expected =
+ "Some text [Link Title](http://example.com/\"foo ) bla.";
+ std::string correct_expected =
+ "Some text Link Title bla.";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(current_expected, text);
+}
// -----------------------------------------------------------------------------
class DISABLED_MADDY_LINKPARSER : public ::testing::Test
@@ -70,3 +196,17 @@ TEST_F(DISABLED_MADDY_LINKPARSER, ItReplacesNoImageMarkdownWithLinks)
ASSERT_EQ(expected, text);
}
+
+TEST(DISABLED_MADDY_LINKPARSER, ItReplacesMarkdownWithQuoteInLink)
+{
+ // This is legal markdown, but hard to parse with regexes; dropping it
+ // here for a future update.
+ std::string text = "Some text [Link Title](http://example.com/\"foo ) bla.";
+ std::string expected =
+ "Some text Link Title bla.";
+ auto linkParser = std::make_shared();
+
+ linkParser->Parse(text);
+
+ ASSERT_EQ(expected, text);
+}