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/). * ![**REMOVED**](https://img.shields.io/badge/-REMOVED-%23900) for now removed features. ## Upcoming +* ![**ADDED**](https://img.shields.io/badge/-ADDED-%23099) Correctly parse links with title text, i.e. `[link](http://example.com "example")`. +* ![**FIXED**](https://img.shields.io/badge/-FIXED-%23090) Do not create invalid URLs from links with spaces, i.e. `[link](/ABC/some file)`. +* ![**FIXED**](https://img.shields.io/badge/-FIXED-%23090) 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); +}