From 490a8f35a4163f59230f53c34f1fbb22a864c01e Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Wed, 9 Mar 2016 19:02:39 +0200 Subject: [PATCH 001/238] remove incompatible comment --- Parsedown.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6ffb402..646af86 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -278,9 +278,6 @@ class Parsedown return $markup; } - # - # Allow for plugin extensibility - # protected function isBlockContinuable($Type) { return method_exists($this, 'block'.$Type.'Continue'); From b5951e08c6886d71087c44398be22f7afba91ff6 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Fri, 24 Jun 2016 14:18:01 +0200 Subject: [PATCH 002/238] Create .gitattributes When using this library, all the files related to tests can be ignored. Tests are only useful when working on the library itself. --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..001c45c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Ignore all tests for archive +/test export-ignore +/.gitattributes export-ignore +/.travis.yml export-ignore +/phpunit.xml.dist export-ignore From f671ae73647b6666c1442cbaac98e5d60208e409 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Wed, 27 Jul 2016 11:05:24 +0300 Subject: [PATCH 003/238] improve readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e1f10e..fdb1a6f 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ Better Markdown Parser in PHP ### Features +* One File * Super Fast -* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) * Extensible +* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) * Tested in 5.3 to 7.0 and in HHVM * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) From e1bcc1c47279a945af7e04e7f5c1f3615b01a825 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 5 Sep 2016 04:51:28 +0200 Subject: [PATCH 004/238] Fix test/CommonMarkTest.php --- test/CommonMarkTest.php | 82 ++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index 9b8d116..680201b 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -1,74 +1,64 @@ parsedown = new Parsedown(); + $this->parsedown->setUrlsLinked(false); + } + /** * @dataProvider data * @param $section * @param $markdown * @param $expectedHtml */ - function test_($section, $markdown, $expectedHtml) + public function testExample($section, $markdown, $expectedHtml) { - $Parsedown = new Parsedown(); - $Parsedown->setUrlsLinked(false); - - $actualHtml = $Parsedown->text($markdown); - $actualHtml = $this->normalizeMarkup($actualHtml); - + $actualHtml = $this->parsedown->text($markdown); $this->assertEquals($expectedHtml, $actualHtml); } - function data() + /** + * @return array + */ + public function data() { $spec = file_get_contents(self::SPEC_URL); + if ($spec === false) { + $this->fail('Unable to load CommonMark spec from ' . self::SPEC_URL); + } + + $spec = str_replace("\r\n", "\n", $spec); $spec = strstr($spec, '', true); - $tests = array(); + $matches = array(); + preg_match_all('/^(?s)`{32} example\n(.*?)\n\.\n(.*?)\n`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); + + $data = array(); $currentSection = ''; + foreach ($matches as $match) { + if (isset($match[3])) { + $currentSection = $match[3]; + } else { + $data[] = array( + 'section' => $currentSection, + 'markdown' => str_replace('→', "\t", $match[1]), + 'expectedHtml' => str_replace('→', "\t", $match[2]) + ); + } + } - preg_replace_callback( - '/^\.\n([\s\S]*?)^\.\n([\s\S]*?)^\.$|^#{1,6} *(.*)$/m', - function($matches) use ( & $tests, & $currentSection, & $testCount) { - if (isset($matches[3]) and $matches[3]) { - $currentSection = $matches[3]; - } else { - $testCount++; - $markdown = $matches[1]; - $markdown = preg_replace('/→/', "\t", $markdown); - $expectedHtml = $matches[2]; - $expectedHtml = $this->normalizeMarkup($expectedHtml); - $tests []= array( - $currentSection, # section - $markdown, # markdown - $expectedHtml, # html - ); - } - }, - $spec - ); - - return $tests; - } - - private function normalizeMarkup($markup) - { - $markup = preg_replace("/\n+/", "\n", $markup); - $markup = preg_replace('/^\s+/m', '', $markup); - $markup = preg_replace('/^((?:<[\w]+>)+)\n/m', '$1', $markup); - $markup = preg_replace('/\n((?:<\/[\w]+>)+)$/m', '$1', $markup); - $markup = trim($markup); - - return $markup; + return $data; } } From 3a46a31e09e5c54f02dc170af83221e0a4b9fef3 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 5 Sep 2016 14:37:34 +0200 Subject: [PATCH 005/238] Fix test/CommonMarkTest.php example regex --- test/CommonMarkTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index 680201b..fd07a49 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -43,7 +43,7 @@ class CommonMarkTest extends PHPUnit_Framework_TestCase $spec = strstr($spec, '', true); $matches = array(); - preg_match_all('/^(?s)`{32} example\n(.*?)\n\.\n(.*?)\n`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); + preg_match_all('/^`{32} example\n((?s).*?)\n\.\n((?s).*?)\n`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); $data = array(); $currentSection = ''; From d33e736fa32cbab800936c2e910d986b9b32781e Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Mon, 5 Sep 2016 14:38:47 +0200 Subject: [PATCH 006/238] Add test/CommonMarkTestWeak.php --- test/CommonMarkTest.php | 4 ++- test/CommonMarkTestWeak.php | 61 +++++++++++++++++++++++++++++++++++++ test/TestParsedown.php | 4 +++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/CommonMarkTestWeak.php diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index fd07a49..c7a1d52 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -13,7 +13,9 @@ class CommonMarkTest extends PHPUnit_Framework_TestCase protected function setUp() { - $this->parsedown = new Parsedown(); + require_once(__DIR__ . '/TestParsedown.php'); + + $this->parsedown = new TestParsedown(); $this->parsedown->setUrlsLinked(false); } diff --git a/test/CommonMarkTestWeak.php b/test/CommonMarkTestWeak.php new file mode 100644 index 0000000..9b0b730 --- /dev/null +++ b/test/CommonMarkTestWeak.php @@ -0,0 +1,61 @@ +parsedown->getTextLevelElements(); + + array_walk($textLevelElements, function (&$element) { + $element = preg_quote($element, '/'); + }); + $this->textLevelElementRegex = '\b(?:' . implode('|', $textLevelElements) . ')\b'; + } + + /** + * @dataProvider data + * @param $section + * @param $markdown + * @param $expectedHtml + */ + public function testExample($section, $markdown, $expectedHtml) + { + $expectedHtml = $this->cleanupHtml($expectedHtml); + + $actualHtml = $this->parsedown->text($markdown); + $actualHtml = $this->cleanupHtml($actualHtml); + + $this->assertEquals($expectedHtml, $actualHtml); + } + + protected function cleanupHtml($markup) + { + // invisible whitespaces at the beginning and end of block elements + // however, whitespaces at the beginning of
 elements do matter
+        $markup = preg_replace(
+            array(
+                '/(<(?!(?:' . $this->textLevelElementRegex . '|\bpre\b))\w+\b[^>]*>(?:<' . $this->textLevelElementRegex . '[^>]*>)?)\s+/s',
+                '/\s+((?:<\/' . $this->textLevelElementRegex . '>)?<\/(?!' . $this->textLevelElementRegex . ')\w+\b>)/s'
+            ),
+            '$1',
+            $markup
+        );
+
+        return $markup;
+    }
+}
diff --git a/test/TestParsedown.php b/test/TestParsedown.php
index 7024dfb..2faa0ab 100644
--- a/test/TestParsedown.php
+++ b/test/TestParsedown.php
@@ -2,4 +2,8 @@
 
 class TestParsedown extends Parsedown
 {
+    public function getTextLevelElements()
+    {
+        return $this->textLevelElements;
+    }
 }

From 2cacfb8da4d81e8a417e17da20e52e99d5a0e98e Mon Sep 17 00:00:00 2001
From: Daniel Rudolf 
Date: Mon, 5 Sep 2016 15:17:52 +0200
Subject: [PATCH 007/238] Improve test/CommonMarkTestWeak.php

---
 test/CommonMarkTestWeak.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/CommonMarkTestWeak.php b/test/CommonMarkTestWeak.php
index 9b0b730..751cad5 100644
--- a/test/CommonMarkTestWeak.php
+++ b/test/CommonMarkTestWeak.php
@@ -49,8 +49,8 @@ class CommonMarkTestWeak extends CommonMarkTest
         // however, whitespaces at the beginning of 
 elements do matter
         $markup = preg_replace(
             array(
-                '/(<(?!(?:' . $this->textLevelElementRegex . '|\bpre\b))\w+\b[^>]*>(?:<' . $this->textLevelElementRegex . '[^>]*>)?)\s+/s',
-                '/\s+((?:<\/' . $this->textLevelElementRegex . '>)?<\/(?!' . $this->textLevelElementRegex . ')\w+\b>)/s'
+                '/(<(?!(?:' . $this->textLevelElementRegex . '|\bpre\b))\w+\b[^>]*>(?:<' . $this->textLevelElementRegex . '[^>]*>)*)\s+/s',
+                '/\s+((?:<\/' . $this->textLevelElementRegex . '>)*<\/(?!' . $this->textLevelElementRegex . ')\w+\b>)/s'
             ),
             '$1',
             $markup

From 228d5f4754fbd5e2f2e12437cab121c2f05004d8 Mon Sep 17 00:00:00 2001
From: Daniel Rudolf 
Date: Mon, 5 Sep 2016 15:31:07 +0200
Subject: [PATCH 008/238] Improve test/CommonMarkTestWeak.php

---
 test/CommonMarkTestWeak.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/CommonMarkTestWeak.php b/test/CommonMarkTestWeak.php
index 751cad5..e467201 100644
--- a/test/CommonMarkTestWeak.php
+++ b/test/CommonMarkTestWeak.php
@@ -15,12 +15,13 @@ require_once(__DIR__ . '/CommonMarkTest.php');
  */
 class CommonMarkTestWeak extends CommonMarkTest
 {
+    protected $textLevelElementRegex;
+
     protected function setUp()
     {
         parent::setUp();
 
         $textLevelElements = $this->parsedown->getTextLevelElements();
-
         array_walk($textLevelElements, function (&$element) {
             $element = preg_quote($element, '/');
         });

From 3d7a473aa955f28e69ad6e8be54ddc51530cd331 Mon Sep 17 00:00:00 2001
From: Haralan Dobrev 
Date: Mon, 5 Sep 2016 16:38:38 +0300
Subject: [PATCH 009/238] Update URL to dependants

As Packagist has now implemented the feature of listing packages
depending on another package, VersionEye is no longer needed for that.

As VersionEye scrapes the Packagist API to do the same, the original
source of information should be preferred.
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index fdb1a6f..b84025d 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w
 
 **Who uses it?**
 
-[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references).
+[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
 
 **How can I help?**
 

From 33a23fbfb22902e6268b8459af2784803aec8848 Mon Sep 17 00:00:00 2001
From: Daniel Rudolf 
Date: Mon, 5 Sep 2016 21:10:23 +0200
Subject: [PATCH 010/238] Refactor PHPUnit bootstrap

This allows Parsedown extensions (like Parsedown Extra) to reuse existing Parsedown tests. See erusev/parsedown-extra#96 for details.
---
 test/CommonMarkTest.php |  2 --
 test/ParsedownTest.php  |  7 +++----
 test/bootstrap.php      | 16 +++++++++++++++-
 3 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php
index c7a1d52..18bca8b 100644
--- a/test/CommonMarkTest.php
+++ b/test/CommonMarkTest.php
@@ -13,8 +13,6 @@ class CommonMarkTest extends PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        require_once(__DIR__ . '/TestParsedown.php');
-
         $this->parsedown = new TestParsedown();
         $this->parsedown->setUrlsLinked(false);
     }
diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php
index c922ab1..7d07183 100644
--- a/test/ParsedownTest.php
+++ b/test/ParsedownTest.php
@@ -27,7 +27,7 @@ class ParsedownTest extends PHPUnit_Framework_TestCase
      */
     protected function initParsedown()
     {
-        $Parsedown = new Parsedown();
+        $Parsedown = new TestParsedown();
 
         return $Parsedown;
     }
@@ -132,15 +132,14 @@ color: red;
 

comment

<!-- html comment -->

EXPECTED_HTML; - $parsedownWithNoMarkup = new Parsedown(); + + $parsedownWithNoMarkup = new TestParsedown(); $parsedownWithNoMarkup->setMarkupEscaped(true); $this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml)); } public function testLateStaticBinding() { - include 'test/TestParsedown.php'; - $parsedown = Parsedown::instance(); $this->assertInstanceOf('Parsedown', $parsedown); diff --git a/test/bootstrap.php b/test/bootstrap.php index 5f264d2..76011c9 100644 --- a/test/bootstrap.php +++ b/test/bootstrap.php @@ -1,3 +1,17 @@ Date: Mon, 5 Sep 2016 22:04:46 +0200 Subject: [PATCH 011/238] Remove PHPUnit bootstrap in favour of composer --- .travis.yml | 3 +++ composer.json | 8 ++++++++ phpunit.xml.dist | 4 ++-- test/bootstrap.php | 17 ----------------- 4 files changed, 13 insertions(+), 19 deletions(-) delete mode 100644 test/bootstrap.php diff --git a/.travis.yml b/.travis.yml index 256dcf1..5d420e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,6 @@ matrix: fast_finish: true allow_failures: - php: hhvm-nightly + +install: + - composer install diff --git a/composer.json b/composer.json index 28145af..b6a376d 100644 --- a/composer.json +++ b/composer.json @@ -17,5 +17,13 @@ }, "autoload": { "psr-0": {"Parsedown": ""} + }, + "autoload-dev": { + "psr-0": { + "TestParsedown": "test/", + "ParsedownTest": "test/", + "CommonMarkTest": "test/", + "CommonMarkTestWeak": "test/" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b2d5e9d..4fe3177 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,8 +1,8 @@ - + test/ParsedownTest.php - \ No newline at end of file + diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index 76011c9..0000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,17 +0,0 @@ - Date: Thu, 22 Sep 2016 12:21:39 +0200 Subject: [PATCH 012/238] Update Parsedown.php Made parsedown compatible with html-tags containing dashes. see https://github.com/erusev/parsedown/issues/407#issuecomment-248833563 --- Parsedown.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 646af86..6425b49 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -673,7 +673,7 @@ class Parsedown return; } - if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) { $element = strtolower($matches[1]); @@ -1258,7 +1258,7 @@ class Parsedown return; } - if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], @@ -1274,7 +1274,7 @@ class Parsedown ); } - if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { return array( 'markup' => $matches[0], From 2772b034c622e469684da29c7f36c51b8b6be7b0 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 00:53:51 +0100 Subject: [PATCH 013/238] Update Parsedown.php (I think this should work) Allow parsedown to specify list start attribute (see: https://github.com/erusev/parsedown/issues/100#issuecomment-249729602) --- Parsedown.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 646af86..f0efdc5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -502,10 +502,10 @@ class Parsedown protected function blockList($Line) { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); - + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '([*+-])') : array('ol', '([0-9]+)[.]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { + if($name === 'ol' && $matches[2] !== '1') $name. = ' start="' . $matches[2] . '"'; $Block = array( 'indent' => $Line['indent'], 'pattern' => $pattern, @@ -514,17 +514,14 @@ class Parsedown 'handler' => 'elements', ), ); - $Block['li'] = array( 'name' => 'li', 'handler' => 'li', 'text' => array( - $matches[2], + $matches[3], ), ); - $Block['element']['text'] []= & $Block['li']; - return $Block; } } @@ -1419,7 +1416,7 @@ class Parsedown $markup .= $Element['text']; } - $markup .= ''; + $markup .= ''; } else { From 1c58e9d8d5f90ab61d6cbb18d2c128a210576c60 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 00:57:57 +0100 Subject: [PATCH 014/238] oops oops --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index f0efdc5..c65b296 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -505,7 +505,7 @@ class Parsedown list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '([*+-])') : array('ol', '([0-9]+)[.]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { - if($name === 'ol' && $matches[2] !== '1') $name. = ' start="' . $matches[2] . '"'; + if($name === 'ol' && $matches[2] !== '1') $name .= ' start="' . $matches[2] . '"'; $Block = array( 'indent' => $Line['indent'], 'pattern' => $pattern, From cceefafd55b7c5574b0ffb7d9c23a067b801b51b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 01:16:00 +0100 Subject: [PATCH 015/238] test Attempting to determine which function change is causing test jobs to fail (in unexpected ways) --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index c65b296..ac3ef81 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1416,7 +1416,7 @@ class Parsedown $markup .= $Element['text']; } - $markup .= ''; + $markup .= ''; } else { From 2cee8d8a2d44e4a5f8584f949f0f6af08c5d3636 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 01:23:22 +0100 Subject: [PATCH 016/238] Update Parsedown.php Looks like I might need to return the pattern which was used previously Reverting last change as build still failed This build will still fail, but I'm hoping it will only fair where the list start value has been inserted --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index ac3ef81..5767fd6 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -508,7 +508,7 @@ class Parsedown if($name === 'ol' && $matches[2] !== '1') $name .= ' start="' . $matches[2] . '"'; $Block = array( 'indent' => $Line['indent'], - 'pattern' => $pattern, + 'pattern' => preg_replace('/\(|\)/', '', $pattern), 'element' => array( 'name' => $name, 'handler' => 'elements', @@ -1416,7 +1416,7 @@ class Parsedown $markup .= $Element['text']; } - $markup .= ''; + $markup .= ''; } else { From 38f4027d5e786aa2bbbc16ee56547552136007d2 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 02:15:09 +0100 Subject: [PATCH 017/238] Update Parsedown.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Okay, so maybe I should have looked 20 lines or so above where I made the edit in the element function – looks like it already supports adding attributes ;p Have amended the change to blocklist to use the already existing functionality, and have reverted the change that I made to the element function. --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 5767fd6..c8eb663 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -505,7 +505,6 @@ class Parsedown list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '([*+-])') : array('ol', '([0-9]+)[.]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { - if($name === 'ol' && $matches[2] !== '1') $name .= ' start="' . $matches[2] . '"'; $Block = array( 'indent' => $Line['indent'], 'pattern' => preg_replace('/\(|\)/', '', $pattern), @@ -514,6 +513,7 @@ class Parsedown 'handler' => 'elements', ), ); + if($name === 'ol' && $matches[2] !== '1') $Block['element']['attributes'] = array('start' => $matches[2]); $Block['li'] = array( 'name' => 'li', 'handler' => 'li', @@ -1416,7 +1416,7 @@ class Parsedown $markup .= $Element['text']; } - $markup .= ''; + $markup .= ''; } else { From f17aa0438a7cd5242f67bfa309c2e2459bdbcee5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Sep 2016 02:15:35 +0100 Subject: [PATCH 018/238] Update Parsedown.php --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index c8eb663..94515db 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1416,7 +1416,7 @@ class Parsedown $markup .= $Element['text']; } - $markup .= ''; + $markup .= ''; } else { From d6d5f53ff4dfd221596e15536ee9a5956133a29f Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 1 Oct 2016 15:56:14 +0100 Subject: [PATCH 019/238] =?UTF-8?q?Fix=20Issue=20#358=20=E2=80=93=20preven?= =?UTF-8?q?ting=20double=20nested=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add the ability for a parsed element to enforce that the line handler not parse any (immediate) child elements of a specified type. 2. Use 1. to allow parsed Url elements to tell the line handler not to parse any child Links or Urls where they are immediate children. --- Parsedown.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 646af86..e34efe8 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -987,7 +987,7 @@ class Parsedown # ~ # - public function line($text) + public function line($text, $non_nestables=array()) { $markup = ''; @@ -1003,6 +1003,12 @@ class Parsedown foreach ($this->InlineTypes[$marker] as $inlineType) { + + if(in_array($inlineType, $non_nestables)) + { + continue; + } + $Inline = $this->{'inline'.$inlineType}($Excerpt); if ( ! isset($Inline)) @@ -1183,6 +1189,7 @@ class Parsedown $Element = array( 'name' => 'a', 'handler' => 'line', + 'non_nestables' => array('Url', 'Link'), 'text' => null, 'attributes' => array( 'href' => null, @@ -1410,9 +1417,11 @@ class Parsedown { $markup .= '>'; + if(!isset($Element['non_nestables'])) $Element['non_nestables'] = array(); + if (isset($Element['handler'])) { - $markup .= $this->{$Element['handler']}($Element['text']); + $markup .= $this->{$Element['handler']}($Element['text'], $Element['non_nestables']); } else { From 4d3600f273cc3613cf420408c9016635216c15ea Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 2 Oct 2016 17:37:08 +0100 Subject: [PATCH 020/238] Extend disallowed assertion depth capabilities I've built on the functionality of feature 1. in the previous commit to allow non nestables to be asserted indefinitely, or to a specified depth. --- Parsedown.php | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e34efe8..b708236 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1003,10 +1003,23 @@ class Parsedown foreach ($this->InlineTypes[$marker] as $inlineType) { - - if(in_array($inlineType, $non_nestables)) + foreach ($non_nestables as $key => $non_nestable) { - continue; + if (is_array($non_nestable)) + { + + if ($non_nestable[0] === $inlineType) + { + continue 2; + } + } + else + { + if ($non_nestable === $inlineType) + { + continue 2; + } + } } $Inline = $this->{'inline'.$inlineType}($Excerpt); @@ -1030,6 +1043,26 @@ class Parsedown $Inline['position'] = $markerPosition; } + foreach ($non_nestables as $key => $non_nestable) + { + if (is_array($non_nestable) && isset($non_nestable[1]) && is_int($non_nestable[1])){ + if($non_nestable[1] > 1) + { + $Inline['element']['non_nestables'][] = array($non_nestable[0], $non_nestable[1] -1); + } + + } + elseif (is_array($non_nestable) && ! isset($non_nestable[1])) + { + $Inline['element']['non_nestables'][] = array($non_nestable[0]); + } + elseif ( ! is_array($non_nestable)) + { + $Inline['element']['non_nestables'][] = $non_nestable; + } + } + + # the text that comes before the inline $unmarkedText = substr($text, 0, $Inline['position']); From 50952b3243cbc4585b2e3f6f609445d34bc4d794 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 2 Oct 2016 18:26:13 +0100 Subject: [PATCH 021/238] Line handler may prevent specified element nesting This commit serves to add comments detailing parts of the new functionality, and to adjust syntax preferences to match that of the surrounding document. The commit title also now reflects the most significant change made. --- Parsedown.php | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index b708236..6b6df9c 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1003,22 +1003,19 @@ class Parsedown foreach ($this->InlineTypes[$marker] as $inlineType) { + # check to see if the current inline type is nestable in the current context + foreach ($non_nestables as $key => $non_nestable) { - if (is_array($non_nestable)) + # case that we used array syntax + if (is_array($non_nestable) and $non_nestable[0] === $inlineType) { - - if ($non_nestable[0] === $inlineType) - { - continue 2; - } + continue 2; } - else + # case that we used plain string syntax + elseif ( ! is_array($non_nestable) and $non_nestable === $inlineType) { - if ($non_nestable === $inlineType) - { - continue 2; - } + continue 2; } } @@ -1043,25 +1040,27 @@ class Parsedown $Inline['position'] = $markerPosition; } + # cause the new element to 'inherit' our non nestables, if appropriate + foreach ($non_nestables as $key => $non_nestable) { - if (is_array($non_nestable) && isset($non_nestable[1]) && is_int($non_nestable[1])){ - if($non_nestable[1] > 1) - { - $Inline['element']['non_nestables'][] = array($non_nestable[0], $non_nestable[1] -1); - } - + # array syntax, and depth is sufficient to pass on + if (is_array($non_nestable) and isset($non_nestable[1]) and + is_int($non_nestable[1]) and $non_nestable[1] > 1) + { + $Inline['element']['non_nestables'][] = array($non_nestable[0], $non_nestable[1] -1); } - elseif (is_array($non_nestable) && ! isset($non_nestable[1])) + # array syntax, and depth is indefinite + elseif (is_array($non_nestable) and ! isset($non_nestable[1])) { $Inline['element']['non_nestables'][] = array($non_nestable[0]); } + # string syntax, so depth is indefinite elseif ( ! is_array($non_nestable)) { $Inline['element']['non_nestables'][] = $non_nestable; } } - # the text that comes before the inline $unmarkedText = substr($text, 0, $Inline['position']); @@ -1450,7 +1449,10 @@ class Parsedown { $markup .= '>'; - if(!isset($Element['non_nestables'])) $Element['non_nestables'] = array(); + if (!isset($Element['non_nestables'])) + { + $Element['non_nestables'] = array(); + } if (isset($Element['handler'])) { From a81aedeb109ed16270bbf6aaead7b41e5593caeb Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 15:27:11 +0100 Subject: [PATCH 022/238] Line handler may prevent specified element nesting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed granularity controls – elements are assumed to be non nestable indefinitely once declared. --- Parsedown.php | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6b6df9c..f1e09d9 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1005,18 +1005,9 @@ class Parsedown { # check to see if the current inline type is nestable in the current context - foreach ($non_nestables as $key => $non_nestable) + if (in_array($inlineType, $non_nestables)) { - # case that we used array syntax - if (is_array($non_nestable) and $non_nestable[0] === $inlineType) - { - continue 2; - } - # case that we used plain string syntax - elseif ( ! is_array($non_nestable) and $non_nestable === $inlineType) - { - continue 2; - } + continue; } $Inline = $this->{'inline'.$inlineType}($Excerpt); @@ -1040,26 +1031,11 @@ class Parsedown $Inline['position'] = $markerPosition; } - # cause the new element to 'inherit' our non nestables, if appropriate + # cause the new element to 'inherit' our non nestables - foreach ($non_nestables as $key => $non_nestable) + foreach ($non_nestables as $non_nestable) { - # array syntax, and depth is sufficient to pass on - if (is_array($non_nestable) and isset($non_nestable[1]) and - is_int($non_nestable[1]) and $non_nestable[1] > 1) - { - $Inline['element']['non_nestables'][] = array($non_nestable[0], $non_nestable[1] -1); - } - # array syntax, and depth is indefinite - elseif (is_array($non_nestable) and ! isset($non_nestable[1])) - { - $Inline['element']['non_nestables'][] = array($non_nestable[0]); - } - # string syntax, so depth is indefinite - elseif ( ! is_array($non_nestable)) - { - $Inline['element']['non_nestables'][] = $non_nestable; - } + $Inline['element']['non_nestables'][] = $non_nestable; } # the text that comes before the inline From c145a758486e4aa95827c02952175e5c32bfac79 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 15:44:50 +0100 Subject: [PATCH 023/238] update test for new feature --- test/data/ordered_list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/ordered_list.html b/test/data/ordered_list.html index b6c5216..2ca7914 100644 --- a/test/data/ordered_list.html +++ b/test/data/ordered_list.html @@ -8,6 +8,6 @@
  • two
  • large numbers:

    -
      +
      1. one
      2. -
      \ No newline at end of file +
    From 846274996a97a91fe22859e08cc338c6358b539d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 17:28:43 +0100 Subject: [PATCH 024/238] Update ordered_list.html From ac857809abb40d4938f093c28589f9092fd3b20e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 17:41:57 +0100 Subject: [PATCH 025/238] update test for new feature --- test/data/ordered_list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/ordered_list.html b/test/data/ordered_list.html index 2ca7914..b6c5216 100644 --- a/test/data/ordered_list.html +++ b/test/data/ordered_list.html @@ -8,6 +8,6 @@
  • two
  • large numbers:

    -
      +
      1. one
      2. -
      +
    \ No newline at end of file From 932bafe0f0f7d9707a91bf7d7e185f5be450a190 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 17:43:37 +0100 Subject: [PATCH 026/238] update test for new feature --- test/data/ordered_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/data/ordered_list.html b/test/data/ordered_list.html index b6c5216..c4a69db 100644 --- a/test/data/ordered_list.html +++ b/test/data/ordered_list.html @@ -8,6 +8,6 @@
  • two
  • large numbers:

    -
      +
      1. one
      \ No newline at end of file From 543a6c4175a1debd148e933fb271f5b8f088b199 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 4 Oct 2016 18:59:36 +0100 Subject: [PATCH 027/238] Line handler may prevent specified element nesting Check if array is empty to shave some performance hits in the case than no non nestables are present. --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index f1e09d9..fc2158f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1005,7 +1005,7 @@ class Parsedown { # check to see if the current inline type is nestable in the current context - if (in_array($inlineType, $non_nestables)) + if ( ! empty($non_nestables) and in_array($inlineType, $non_nestables)) { continue; } From 1fa8fae301e33a79d7345e7c6db526228a322d05 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 10:03:21 +0100 Subject: [PATCH 028/238] Allow parsedown to specify list start attribute Readability improvements --- Parsedown.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 94515db..a8e8e95 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -502,23 +502,30 @@ class Parsedown protected function blockList($Line) { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '([*+-])') : array('ol', '([0-9]+)[.]'); + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { $Block = array( 'indent' => $Line['indent'], - 'pattern' => preg_replace('/\(|\)/', '', $pattern), + 'pattern' => $pattern, 'element' => array( 'name' => $name, 'handler' => 'elements', ), ); - if($name === 'ol' && $matches[2] !== '1') $Block['element']['attributes'] = array('start' => $matches[2]); + if($name === 'ol') + { + $list_num = explode ('.', $matches[0], 1)[0]; + if($list_num !== '1') + { + $Block['element']['attributes'] = array('start' => $list_num); + } + } $Block['li'] = array( 'name' => 'li', 'handler' => 'li', 'text' => array( - $matches[3], + $matches[2], ), ); $Block['element']['text'] []= & $Block['li']; From ed41fcf3d6714cef3c183386d1d111f54a9948fe Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 10:06:40 +0100 Subject: [PATCH 029/238] Allow parsedown to specify list start attribute oops --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index a8e8e95..a9fa5ae 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -515,7 +515,7 @@ class Parsedown ); if($name === 'ol') { - $list_num = explode ('.', $matches[0], 1)[0]; + $list_num = explode ('.', $matches[0], 2)[0]; if($list_num !== '1') { $Block['element']['attributes'] = array('start' => $list_num); From f0b7b61c16c1abe25756518c764d6e84e9914b12 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 11:36:27 +0100 Subject: [PATCH 030/238] Allow parsedown to specify list start attribute Should fix compatibility for PHP 5.3 --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index a9fa5ae..9693428 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -515,7 +515,7 @@ class Parsedown ); if($name === 'ol') { - $list_num = explode ('.', $matches[0], 2)[0]; + $list_num = preg_replace ('/^([0-9]+)[.].*+$/', '$1', $matches[0]); if($list_num !== '1') { $Block['element']['attributes'] = array('start' => $list_num); From e3cd271f1603134c31359043677fbc8c7dc8d1e5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 15:44:34 +0100 Subject: [PATCH 031/238] Allow parsedown to specify list start attribute Performance: Swap preg_replace for stristr to obtain list start --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 9693428..03b729b 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -515,7 +515,7 @@ class Parsedown ); if($name === 'ol') { - $list_num = preg_replace ('/^([0-9]+)[.].*+$/', '$1', $matches[0]); + $list_num = stristr($matches[0], ".", true); if($list_num !== '1') { $Block['element']['attributes'] = array('start' => $list_num); From a37797ef347a3777c284dc2e8f8058482b370c09 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 18:15:47 +0100 Subject: [PATCH 032/238] Allow parsedown to specify list start attribute Syntax preferences to match surrounding code --- Parsedown.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 03b729b..3a5f0d2 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -503,6 +503,7 @@ class Parsedown protected function blockList($Line) { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { $Block = array( @@ -513,14 +514,16 @@ class Parsedown 'handler' => 'elements', ), ); + if($name === 'ol') { - $list_num = stristr($matches[0], ".", true); - if($list_num !== '1') + $listStart = stristr($matches[0], ".", true); + if($listStart !== '1') { - $Block['element']['attributes'] = array('start' => $list_num); + $Block['element']['attributes'] = array('start' => $listStart); } } + $Block['li'] = array( 'name' => 'li', 'handler' => 'li', @@ -528,7 +531,9 @@ class Parsedown $matches[2], ), ); + $Block['element']['text'] []= & $Block['li']; + return $Block; } } From 3978e33fd0eb964f9faa8ce9862b8518492a7a54 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 18:17:12 +0100 Subject: [PATCH 033/238] Allow parsedown to specify list start attribute Remove github added tabs on blank lines --- Parsedown.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 3a5f0d2..cf78d7a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -503,7 +503,7 @@ class Parsedown protected function blockList($Line) { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); - + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { $Block = array( @@ -514,7 +514,7 @@ class Parsedown 'handler' => 'elements', ), ); - + if($name === 'ol') { $listStart = stristr($matches[0], ".", true); @@ -523,7 +523,7 @@ class Parsedown $Block['element']['attributes'] = array('start' => $listStart); } } - + $Block['li'] = array( 'name' => 'li', 'handler' => 'li', @@ -531,9 +531,9 @@ class Parsedown $matches[2], ), ); - + $Block['element']['text'] []= & $Block['li']; - + return $Block; } } From 5c22531e4dc535077e6dce163fbfc5389feddeab Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 5 Oct 2016 18:27:54 +0100 Subject: [PATCH 034/238] Allow parsedown to specify list start attribute Syntax preferences --- Parsedown.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index cf78d7a..4737eee 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -517,7 +517,8 @@ class Parsedown if($name === 'ol') { - $listStart = stristr($matches[0], ".", true); + $listStart = stristr($matches[0], '.', true); + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); From 3aef89b3994d942d4eb85d6df0c8c43a157ac5d7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 8 Oct 2016 17:54:04 +0100 Subject: [PATCH 035/238] Line handler may prevent specified element nesting Swap `under_scores` for `camelCasing` --- Parsedown.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index fc2158f..e2b9d60 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -987,7 +987,7 @@ class Parsedown # ~ # - public function line($text, $non_nestables=array()) + public function line($text, $nonNestables=array()) { $markup = ''; @@ -1005,7 +1005,7 @@ class Parsedown { # check to see if the current inline type is nestable in the current context - if ( ! empty($non_nestables) and in_array($inlineType, $non_nestables)) + if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) { continue; } @@ -1033,9 +1033,9 @@ class Parsedown # cause the new element to 'inherit' our non nestables - foreach ($non_nestables as $non_nestable) + foreach ($nonNestables as $non_nestable) { - $Inline['element']['non_nestables'][] = $non_nestable; + $Inline['element']['nonNestables'][] = $non_nestable; } # the text that comes before the inline @@ -1197,7 +1197,7 @@ class Parsedown $Element = array( 'name' => 'a', 'handler' => 'line', - 'non_nestables' => array('Url', 'Link'), + 'nonNestables' => array('Url', 'Link'), 'text' => null, 'attributes' => array( 'href' => null, @@ -1425,14 +1425,14 @@ class Parsedown { $markup .= '>'; - if (!isset($Element['non_nestables'])) + if (!isset($Element['nonNestables'])) { - $Element['non_nestables'] = array(); + $Element['nonNestables'] = array(); } if (isset($Element['handler'])) { - $markup .= $this->{$Element['handler']}($Element['text'], $Element['non_nestables']); + $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']); } else { From 0080ef218e14ba909120d122c7f97d96abc50893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Make=C5=A1?= Date: Sun, 9 Oct 2016 01:58:47 +0200 Subject: [PATCH 036/238] Added PHP version 7.1 for tests --- .travis.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 256dcf1..fa5ca98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php php: + - 7.1 - 7.0 - 5.6 - 5.5 diff --git a/README.md b/README.md index b84025d..7ef230f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Better Markdown Parser in PHP * Super Fast * Extensible * [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) -* Tested in 5.3 to 7.0 and in HHVM +* Tested in 5.3 to 7.1 and in HHVM * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) ### Installation From f0587d41a9783e4c3ad817e74909744d072d2874 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 9 Oct 2016 14:17:03 +0200 Subject: [PATCH 037/238] Add test/CommonMarkTestWeak.php to .travis.yml Failing tests don't break builds on purpose, Parsedown doesn't fully comply with the CommonMark specs at the moment. We should switch to test/CommonMarkTest.php later, see #423 for details. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5d420e1..09c8e2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,3 +16,6 @@ matrix: install: - composer install + +script: + - phpunit test/CommonMarkTestWeak.php || true From be671e72a39484ff9354cd7f1177d5901e1bdf9b Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Sun, 9 Oct 2016 14:21:17 +0200 Subject: [PATCH 038/238] Don't let Travis skip Parsedown's phpunit tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 09c8e2b..3320963 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,4 +18,5 @@ install: - composer install script: + - phpunit - phpunit test/CommonMarkTestWeak.php || true From c4d4a6800dfee4026b9f62921c8c759bea73aff8 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 12:05:33 +0100 Subject: [PATCH 039/238] (beginning to) improve commonmark compliance:lists These changes aren't fit for merge, nor do they work correctly (yet) --- Parsedown.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 4737eee..6c533c6 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -509,6 +509,11 @@ class Parsedown $Block = array( 'indent' => $Line['indent'], 'pattern' => $pattern, + 'data' => array( + 'type' => $name, + 'matchText' => ($name === 'ul' ? stristr($matches[1], ' ', true) : stristr($matches[1], '.', true)), + 'rootItem' => $matches[2] + ), 'element' => array( 'name' => $name, 'handler' => 'elements', @@ -541,7 +546,19 @@ class Parsedown protected function blockListContinue($Line, array $Block) { - if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) + if ( + ( + $Block['indent'] === $Line['indent'] + and $Block['data']['type'] === 'ol' + and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + ) + or + ( + $Block['indent'] === $Line['indent'] + and $Block['data']['type'] === 'ul' + and preg_match('/^'.preg_quote($Block['data']['matchText']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + ) + ) { if (isset($Block['interrupted'])) { @@ -566,6 +583,11 @@ class Parsedown return $Block; } + elseif ($Block['indent'] === $Line['indent']) + { + echo $Block['data']['rootItem'] ."\t" . $Block['indent'] ."\t" . $Block['data']['type'] ."\t" . $Line['indent'] . "\t". $Block['data']['matchText'] ."\t" .$Line['text'] . "\n"; + return null; + } if ($Line['text'][0] === '[' and $this->blockReference($Line)) { From 06c4344a71b64b00c2fbfce4faa4befb94417ce8 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 13:38:47 +0100 Subject: [PATCH 040/238] Contextual limits on indentation stripping --- Parsedown.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6c533c6..29e5fc8 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -585,7 +585,6 @@ class Parsedown } elseif ($Block['indent'] === $Line['indent']) { - echo $Block['data']['rootItem'] ."\t" . $Block['indent'] ."\t" . $Block['data']['type'] ."\t" . $Line['indent'] . "\t". $Block['data']['matchText'] ."\t" .$Line['text'] . "\n"; return null; } @@ -596,7 +595,7 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.($Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; @@ -607,7 +606,7 @@ class Parsedown { $Block['li']['text'] []= ''; - $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.($Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; From 0bd61a73eda9697b7daadf9187bbda305c0916a5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 13:48:38 +0100 Subject: [PATCH 041/238] Check that the current line is a list before starting a new one --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 29e5fc8..d9b4673 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -583,7 +583,7 @@ class Parsedown return $Block; } - elseif ($Block['indent'] === $Line['indent']) + elseif ($Block['indent'] === $Line['indent'] and $l = $this->blockList($Line)) { return null; } From d9679141fa938199c99c3866f0a8a28eec2e3957 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 13:50:47 +0100 Subject: [PATCH 042/238] Update test to comply with CommonMark --- test/data/unordered_list.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/data/unordered_list.html b/test/data/unordered_list.html index cd95567..a52e06b 100644 --- a/test/data/unordered_list.html +++ b/test/data/unordered_list.html @@ -5,6 +5,10 @@

      mixed markers:

      • li
      • +
      +
      • li
      • +
      +
      • li
      \ No newline at end of file From d26b33c20fb983d07162569b52da8b4510a78706 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 19:18:43 +0100 Subject: [PATCH 043/238] Add `)` as an ordered list marker Also added marker check to ordered list case when deciding to continue the current list --- Parsedown.php | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index d9b4673..bb2bd11 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -502,7 +502,7 @@ class Parsedown protected function blockList($Line) { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.\)]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { @@ -511,8 +511,7 @@ class Parsedown 'pattern' => $pattern, 'data' => array( 'type' => $name, - 'matchText' => ($name === 'ul' ? stristr($matches[1], ' ', true) : stristr($matches[1], '.', true)), - 'rootItem' => $matches[2] + 'marker' => ($name === 'ul' ? stristr($matches[1], ' ', true) : substr(stristr($matches[1], ' ', true), -1)), ), 'element' => array( 'name' => $name, @@ -522,7 +521,7 @@ class Parsedown if($name === 'ol') { - $listStart = stristr($matches[0], '.', true); + $listStart = stristr($matches[1], $Block['data']['marker'], true); if($listStart !== '1') { @@ -547,16 +546,18 @@ class Parsedown protected function blockListContinue($Line, array $Block) { if ( + $Block['indent'] === $Line['indent'] + and ( - $Block['indent'] === $Line['indent'] - and $Block['data']['type'] === 'ol' - and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches) - ) - or - ( - $Block['indent'] === $Line['indent'] - and $Block['data']['type'] === 'ul' - and preg_match('/^'.preg_quote($Block['data']['matchText']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + ( + $Block['data']['type'] === 'ol' + and preg_match('/^[0-9]+'.preg_quote($Block['data']['marker']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + ) + or + ( + $Block['data']['type'] === 'ul' + and preg_match('/^'.preg_quote($Block['data']['marker']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + ) ) ) { @@ -583,11 +584,13 @@ class Parsedown return $Block; } - elseif ($Block['indent'] === $Line['indent'] and $l = $this->blockList($Line)) + elseif ($Block['indent'] === $Line['indent'] and $placeholder = $this->blockList($Line)) { return null; } + unset($placeholder); + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { return $Block; @@ -595,7 +598,7 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - $text = preg_replace('/^[ ]{0,'.($Block['indent'] + 1).'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; @@ -606,7 +609,7 @@ class Parsedown { $Block['li']['text'] []= ''; - $text = preg_replace('/^[ ]{0,'.($Block['indent'] + 1).'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; From 8965c7864fa62394d37e5e53576643a443c4b24e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 11 Oct 2016 20:55:59 +0100 Subject: [PATCH 044/238] More appropriate tests for these changes --- test/data/unordered_list.html | 12 ++++++++++-- test/data/unordered_list.md | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/test/data/unordered_list.html b/test/data/unordered_list.html index a52e06b..6ed70a6 100644 --- a/test/data/unordered_list.html +++ b/test/data/unordered_list.html @@ -2,7 +2,7 @@
    1. li
    2. li
    3. -

      mixed markers:

      +

      mixed unordered markers:

      • li
      @@ -11,4 +11,12 @@
      • li
      • -
      \ No newline at end of file + +

      mixed ordered markers:

      +
        +
      1. starting at 1, list one
      2. +
      3. number 2, list one
      4. +
      +
        +
      1. starting at 3, list two
      2. +
      \ No newline at end of file diff --git a/test/data/unordered_list.md b/test/data/unordered_list.md index cf62c99..c1b8640 100644 --- a/test/data/unordered_list.md +++ b/test/data/unordered_list.md @@ -1,8 +1,14 @@ - li - li -mixed markers: +mixed unordered markers: * li + li -- li \ No newline at end of file +- li + +mixed ordered markers: + +1. starting at 1, list one +2. number 2, list one +3) starting at 3, list two \ No newline at end of file From 2423644d728075dfd55199e6314d4ad3b24d9072 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 12 Oct 2016 02:01:40 +0200 Subject: [PATCH 045/238] Move test/CommonMarkTest.php to test/CommonMarkTestStrict.php Add parameter `$id` to CommonMark tests --- test/{CommonMarkTest.php => CommonMarkTestStrict.php} | 7 +++++-- test/CommonMarkTestWeak.php | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) rename test/{CommonMarkTest.php => CommonMarkTestStrict.php} (88%) diff --git a/test/CommonMarkTest.php b/test/CommonMarkTestStrict.php similarity index 88% rename from test/CommonMarkTest.php rename to test/CommonMarkTestStrict.php index 18bca8b..bd16ffa 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTestStrict.php @@ -5,7 +5,7 @@ * * @link http://commonmark.org/ CommonMark */ -class CommonMarkTest extends PHPUnit_Framework_TestCase +class CommonMarkTestStrict extends PHPUnit_Framework_TestCase { const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt'; @@ -19,11 +19,12 @@ class CommonMarkTest extends PHPUnit_Framework_TestCase /** * @dataProvider data + * @param $id * @param $section * @param $markdown * @param $expectedHtml */ - public function testExample($section, $markdown, $expectedHtml) + public function testExample($id, $section, $markdown, $expectedHtml) { $actualHtml = $this->parsedown->text($markdown); $this->assertEquals($expectedHtml, $actualHtml); @@ -46,12 +47,14 @@ class CommonMarkTest extends PHPUnit_Framework_TestCase preg_match_all('/^`{32} example\n((?s).*?)\n\.\n((?s).*?)\n`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); $data = array(); + $currentId = 0; $currentSection = ''; foreach ($matches as $match) { if (isset($match[3])) { $currentSection = $match[3]; } else { $data[] = array( + 'id' => ++$currentId, 'section' => $currentSection, 'markdown' => str_replace('→', "\t", $match[1]), 'expectedHtml' => str_replace('→', "\t", $match[2]) diff --git a/test/CommonMarkTestWeak.php b/test/CommonMarkTestWeak.php index e467201..ef4081a 100644 --- a/test/CommonMarkTestWeak.php +++ b/test/CommonMarkTestWeak.php @@ -1,5 +1,5 @@ cleanupHtml($expectedHtml); From 2db31995104f9ad31b9b1a10f8cf2be9d7a32441 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 12 Oct 2016 18:10:44 +0100 Subject: [PATCH 046/238] Break less previously passed CommonMarkWeak tests --- Parsedown.php | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index bb2bd11..2ee8417 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -188,16 +188,34 @@ class Parsedown # ~ + $highPriority = array(); + $blockTypes = $this->unmarkedBlockTypes; if (isset($this->BlockTypes[$marker])) { foreach ($this->BlockTypes[$marker] as $blockType) { - $blockTypes []= $blockType; + if + ( + isset($CurrentBlock['type']) + and $CurrentBlock['type'] === $blockType + and ! isset($CurrentBlock['interrupted']) + and isset($CurrentBlock['continuable']) + and ! isset($CurrentBlock['complete']) + ) + { + $highPriority[] = $CurrentBlock['type']; + } + else + { + $blockTypes []= $blockType; + } } } + $blockTypes = array_merge($highPriority, $blockTypes); + # # ~ @@ -589,8 +607,6 @@ class Parsedown return null; } - unset($placeholder); - if ($Line['text'][0] === '[' and $this->blockReference($Line)) { return $Block; @@ -598,7 +614,7 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent']).'}/', '', $Line['body']); $Block['li']['text'] []= $text; @@ -609,7 +625,12 @@ class Parsedown { $Block['li']['text'] []= ''; - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + if ($placeholder = $this->blockList($Line)) + { + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent']).'}/', '', $Line['body']); + } $Block['li']['text'] []= $text; From 0a43799da4f6b10d9fc3bdbb6482e2e1713fb79f Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 13 Oct 2016 14:29:52 +0100 Subject: [PATCH 047/238] Prevent failure with data set 77 in CommonMarkWeak --- Parsedown.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 2ee8417..f774b88 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -115,7 +115,7 @@ class Parsedown # Blocks # - protected function lines(array $lines) + protected function lines(array $lines, $parentType = null) { $CurrentBlock = null; @@ -216,6 +216,12 @@ class Parsedown $blockTypes = array_merge($highPriority, $blockTypes); + if ( $parentType === 'List' and ($a = array_search('List', $blockTypes)) !== false and ($b = array_search('Code', $blockTypes)) !== false and $a > $b and ($placeholder = $this->blockList($Line))) + { + unset($blockTypes[$b]); + array_splice($blockTypes, $a + 1, 0, 'Code'); + } + # # ~ @@ -1502,7 +1508,7 @@ class Parsedown protected function li($lines) { - $markup = $this->lines($lines); + $markup = $this->lines($lines, 'List'); $trimmedMarkup = trim($markup); From 6973302ca8f6f8613cd76488ad3f87067458e215 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 13 Oct 2016 15:55:13 +0100 Subject: [PATCH 048/238] Prevent breaking remaining previously compliant CommonMarkWeak tests --- Parsedown.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f774b88..e6a4799 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -165,7 +165,7 @@ class Parsedown if (isset($CurrentBlock['continuable'])) { - $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock, $parentType); if (isset($Block)) { @@ -449,7 +449,7 @@ class Parsedown } } - protected function blockFencedCodeContinue($Line, $Block) + protected function blockFencedCodeContinue($Line, $Block, $parentType) { if (isset($Block['complete'])) { @@ -472,6 +472,11 @@ class Parsedown return $Block; } + if ($parentType === 'List') + { + $Line['body'] = preg_replace('/^[ ]{0,'.min(4, $Line['indent']).'}/', '', $Line['body']); + } + $Block['element']['text']['text'] .= "\n".$Line['body'];; return $Block; From eb853da92aef802deee84ea46476b14318ce3b12 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:25:30 +0200 Subject: [PATCH 049/238] Revert "Prevent breaking remaining previously compliant CommonMarkWeak tests" This reverts commit 6973302ca8f6f8613cd76488ad3f87067458e215. --- Parsedown.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e6a4799..f774b88 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -165,7 +165,7 @@ class Parsedown if (isset($CurrentBlock['continuable'])) { - $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock, $parentType); + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); if (isset($Block)) { @@ -449,7 +449,7 @@ class Parsedown } } - protected function blockFencedCodeContinue($Line, $Block, $parentType) + protected function blockFencedCodeContinue($Line, $Block) { if (isset($Block['complete'])) { @@ -472,11 +472,6 @@ class Parsedown return $Block; } - if ($parentType === 'List') - { - $Line['body'] = preg_replace('/^[ ]{0,'.min(4, $Line['indent']).'}/', '', $Line['body']); - } - $Block['element']['text']['text'] .= "\n".$Line['body'];; return $Block; From e691034861679341341d9cde4fcbf0a8cabb75d3 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:25:37 +0200 Subject: [PATCH 050/238] Revert "Prevent failure with data set 77 in CommonMarkWeak" This reverts commit 0a43799da4f6b10d9fc3bdbb6482e2e1713fb79f. --- Parsedown.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f774b88..2ee8417 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -115,7 +115,7 @@ class Parsedown # Blocks # - protected function lines(array $lines, $parentType = null) + protected function lines(array $lines) { $CurrentBlock = null; @@ -216,12 +216,6 @@ class Parsedown $blockTypes = array_merge($highPriority, $blockTypes); - if ( $parentType === 'List' and ($a = array_search('List', $blockTypes)) !== false and ($b = array_search('Code', $blockTypes)) !== false and $a > $b and ($placeholder = $this->blockList($Line))) - { - unset($blockTypes[$b]); - array_splice($blockTypes, $a + 1, 0, 'Code'); - } - # # ~ @@ -1508,7 +1502,7 @@ class Parsedown protected function li($lines) { - $markup = $this->lines($lines, 'List'); + $markup = $this->lines($lines); $trimmedMarkup = trim($markup); From 81025cd468f4f51eebedd2c159be893bfa414602 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:25:43 +0200 Subject: [PATCH 051/238] Revert "Break less previously passed CommonMarkWeak tests" This reverts commit 2db31995104f9ad31b9b1a10f8cf2be9d7a32441. --- Parsedown.php | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 2ee8417..bb2bd11 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -188,34 +188,16 @@ class Parsedown # ~ - $highPriority = array(); - $blockTypes = $this->unmarkedBlockTypes; if (isset($this->BlockTypes[$marker])) { foreach ($this->BlockTypes[$marker] as $blockType) { - if - ( - isset($CurrentBlock['type']) - and $CurrentBlock['type'] === $blockType - and ! isset($CurrentBlock['interrupted']) - and isset($CurrentBlock['continuable']) - and ! isset($CurrentBlock['complete']) - ) - { - $highPriority[] = $CurrentBlock['type']; - } - else - { - $blockTypes []= $blockType; - } + $blockTypes []= $blockType; } } - $blockTypes = array_merge($highPriority, $blockTypes); - # # ~ @@ -607,6 +589,8 @@ class Parsedown return null; } + unset($placeholder); + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { return $Block; @@ -614,7 +598,7 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent']).'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; @@ -625,12 +609,7 @@ class Parsedown { $Block['li']['text'] []= ''; - $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); - - if ($placeholder = $this->blockList($Line)) - { - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent']).'}/', '', $Line['body']); - } + $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); $Block['li']['text'] []= $text; From bdf537e9d57035a12160ab4344e9a7361e327bbf Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:30:50 +0200 Subject: [PATCH 052/238] Fix ordered list start argument See CommonMark spec examples [#226](http://spec.commonmark.org/0.26/#example-226) to #229 --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index bb2bd11..ac83e50 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -502,7 +502,7 @@ class Parsedown protected function blockList($Line) { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.\)]'); + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}[.\)]'); if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) { @@ -521,7 +521,7 @@ class Parsedown if($name === 'ol') { - $listStart = stristr($matches[1], $Block['data']['marker'], true); + $listStart = ltrim(strstr($matches[1], $Block['data']['marker'], true), '0') ?: '0'; if($listStart !== '1') { From 30ff5c6e7564aa3d0e30dd9476335c0a444f8dcd Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:31:35 +0200 Subject: [PATCH 053/238] Remove unused $placeholder variable --- Parsedown.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index ac83e50..5003775 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -584,13 +584,11 @@ class Parsedown return $Block; } - elseif ($Block['indent'] === $Line['indent'] and $placeholder = $this->blockList($Line)) + elseif ($Block['indent'] === $Line['indent'] and $this->blockList($Line)) { return null; } - unset($placeholder); - if ($Line['text'][0] === '[' and $this->blockReference($Line)) { return $Block; From 4b3b7df710e7a8ccd0a6fe66f84a03cddeb988e4 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:46:29 +0200 Subject: [PATCH 054/238] Support list items starting with a blank line According to the CommonMark specs ([list items](http://spec.commonmark.org/0.26/#list-items), rule 3), list items starting with a blank line basically behave like as if the \n doesn't exist. Also see example [#241](http://spec.commonmark.org/0.26/#example-241). --- Parsedown.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 5003775..df79c99 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -504,8 +504,12 @@ class Parsedown { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}[.\)]'); - if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + if (preg_match('/^('.$pattern.'([ ]+|$))(.*)/', $Line['text'], $matches)) { + if ($matches[2] === '') { + $matches[1] .= ' '; + } + $Block = array( 'indent' => $Line['indent'], 'pattern' => $pattern, @@ -532,9 +536,7 @@ class Parsedown $Block['li'] = array( 'name' => 'li', 'handler' => 'li', - 'text' => array( - $matches[2], - ), + 'text' => !empty($matches[3]) ? array($matches[3]) : array(), ); $Block['element']['text'] []= & $Block['li']; @@ -545,6 +547,10 @@ class Parsedown protected function blockListContinue($Line, array $Block) { + if (isset($Block['interrupted']) and empty($Block['li']['text'])) { + return null; + } + if ( $Block['indent'] === $Line['indent'] and From 1d61f90bf94a5a27736208275dae6d93c79107b8 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:47:06 +0200 Subject: [PATCH 055/238] Support list items starting with indented code --- Parsedown.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index df79c99..2f81b84 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -506,7 +506,12 @@ class Parsedown if (preg_match('/^('.$pattern.'([ ]+|$))(.*)/', $Line['text'], $matches)) { - if ($matches[2] === '') { + $contentIndent = strlen($matches[2]); + if ($contentIndent >= 5) { + $contentIndent -= 1; + $matches[1] = substr($matches[1], 0, -$contentIndent); + $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; + } elseif ($contentIndent === 0) { $matches[1] .= ' '; } From 7b1529fff036b9c4c2c301bc4939a8c7f6e7d245 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:51:32 +0200 Subject: [PATCH 056/238] Use the list marker width to determine whether a list item is continued This basically represents [list item parsing](http://spec.commonmark.org/0.26/#list-items), rule 1 of the CommonMark specs. --- Parsedown.php | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 2f81b84..f6e1bec 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -520,7 +520,8 @@ class Parsedown 'pattern' => $pattern, 'data' => array( 'type' => $name, - 'marker' => ($name === 'ul' ? stristr($matches[1], ' ', true) : substr(stristr($matches[1], ' ', true), -1)), + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? strstr($matches[1], ' ', true) : substr(strstr($matches[1], ' ', true), -1)), ), 'element' => array( 'name' => $name, @@ -530,7 +531,7 @@ class Parsedown if($name === 'ol') { - $listStart = ltrim(strstr($matches[1], $Block['data']['marker'], true), '0') ?: '0'; + $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; if($listStart !== '1') { @@ -562,12 +563,12 @@ class Parsedown ( ( $Block['data']['type'] === 'ol' - and preg_match('/^[0-9]+'.preg_quote($Block['data']['marker']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + and preg_match('/^[0-9]+'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) ) or ( $Block['data']['type'] === 'ul' - and preg_match('/^'.preg_quote($Block['data']['marker']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + and preg_match('/^'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) ) ) ) @@ -605,25 +606,31 @@ class Parsedown return $Block; } - if ( ! isset($Block['interrupted'])) + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + + if ($Line['indent'] >= $requiredIndent) { - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], $requiredIndent); $Block['li']['text'] []= $text; return $Block; } - if ($Line['indent'] > 0) + if ( ! isset($Block['interrupted'])) { - $Block['li']['text'] []= ''; - - $text = preg_replace('/^[ ]{0,'.min(4, $Block['indent'] + 1).'}/', '', $Line['body']); + // TODO: force multi-line paragraph, this must not parse any new block + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); $Block['li']['text'] []= $text; - unset($Block['interrupted']); - return $Block; } } From a9e1163c85bb23564458af84e8852650e209fbe9 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 19:52:38 +0200 Subject: [PATCH 057/238] Fix code formatting --- Parsedown.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f6e1bec..ececf78 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -507,11 +507,15 @@ class Parsedown if (preg_match('/^('.$pattern.'([ ]+|$))(.*)/', $Line['text'], $matches)) { $contentIndent = strlen($matches[2]); - if ($contentIndent >= 5) { + + if ($contentIndent >= 5) + { $contentIndent -= 1; $matches[1] = substr($matches[1], 0, -$contentIndent); $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; - } elseif ($contentIndent === 0) { + } + elseif ($contentIndent === 0) + { $matches[1] .= ' '; } @@ -553,7 +557,8 @@ class Parsedown protected function blockListContinue($Line, array $Block) { - if (isset($Block['interrupted']) and empty($Block['li']['text'])) { + if (isset($Block['interrupted']) and empty($Block['li']['text'])) + { return null; } From a3836b185368b92dad899e7a9567731eeed8297d Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 20:44:02 +0200 Subject: [PATCH 058/238] Handle subsequent list items which aren't indented sufficiently Subsequent list items which aren't indented sufficiently are treated as part of the original list, see CommonMark spec example [#256](http://spec.commonmark.org/0.26/#example-256). --- Parsedown.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index ececf78..de80f3f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -562,8 +562,10 @@ class Parsedown return null; } + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + if ( - $Block['indent'] === $Line['indent'] + $Line['indent'] < $requiredIndent and ( ( @@ -589,6 +591,8 @@ class Parsedown $text = isset($matches[1]) ? $matches[1] : ''; + $Block['indent'] = $Line['indent']; + $Block['li'] = array( 'name' => 'li', 'handler' => 'li', @@ -601,7 +605,7 @@ class Parsedown return $Block; } - elseif ($Block['indent'] === $Line['indent'] and $this->blockList($Line)) + elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) { return null; } @@ -611,8 +615,6 @@ class Parsedown return $Block; } - $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); - if ($Line['indent'] >= $requiredIndent) { if (isset($Block['interrupted'])) @@ -631,7 +633,6 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - // TODO: force multi-line paragraph, this must not parse any new block $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); $Block['li']['text'] []= $text; From a9f696f7bb413cbf7b14b744806fce504353e072 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 22:16:46 +0200 Subject: [PATCH 059/238] Improve CommonMark spec example regex CommonMark spec example [#170](http://spec.commonmark.org/0.26/#example-170) has a empty HTML result. --- test/CommonMarkTestStrict.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/CommonMarkTestStrict.php b/test/CommonMarkTestStrict.php index bd16ffa..3837738 100644 --- a/test/CommonMarkTestStrict.php +++ b/test/CommonMarkTestStrict.php @@ -7,7 +7,7 @@ */ class CommonMarkTestStrict extends PHPUnit_Framework_TestCase { - const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt'; + const SPEC_URL = 'https://raw.githubusercontent.com/jgm/CommonMark/master/spec.txt'; protected $parsedown; @@ -44,7 +44,7 @@ class CommonMarkTestStrict extends PHPUnit_Framework_TestCase $spec = strstr($spec, '', true); $matches = array(); - preg_match_all('/^`{32} example\n((?s).*?)\n\.\n((?s).*?)\n`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); + preg_match_all('/^`{32} example\n((?s).*?)\n\.\n(?:|((?s).*?)\n)`{32}$|^#{1,6} *(.*?)$/m', $spec, $matches, PREG_SET_ORDER); $data = array(); $currentId = 0; @@ -53,11 +53,15 @@ class CommonMarkTestStrict extends PHPUnit_Framework_TestCase if (isset($match[3])) { $currentSection = $match[3]; } else { - $data[] = array( - 'id' => ++$currentId, + $currentId++; + $markdown = str_replace('→', "\t", $match[1]); + $expectedHtml = isset($match[2]) ? str_replace('→', "\t", $match[2]) : ''; + + $data[$currentId] = array( + 'id' => $currentId, 'section' => $currentSection, - 'markdown' => str_replace('→', "\t", $match[1]), - 'expectedHtml' => str_replace('→', "\t", $match[2]) + 'markdown' => $markdown, + 'expectedHtml' => $expectedHtml ); } } From ae0211a84c92a1eab7892a7c1876b5a5f7402f28 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 13 Oct 2016 22:17:03 +0200 Subject: [PATCH 060/238] Travis: Add PHP nightly --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3320963..3259ca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,14 @@ php: - 5.5 - 5.4 - 5.3 + - nightly - hhvm - hhvm-nightly matrix: fast_finish: true allow_failures: + - php: nightly - php: hhvm-nightly install: From 8876c0984e64ace957f3e38abfc3b3961bc40b88 Mon Sep 17 00:00:00 2001 From: James Vickery Date: Tue, 25 Oct 2016 15:10:22 +0100 Subject: [PATCH 061/238] Tiny grammar correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ef230f..4ce37c5 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t **How does Parsedown work?** -It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belong to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). +It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages. From 7a92a31739be3401e6cd961d548398dcc67bc064 Mon Sep 17 00:00:00 2001 From: James Vickery Date: Tue, 25 Oct 2016 15:22:17 +0100 Subject: [PATCH 062/238] Grammar update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ce37c5..7907787 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t **How does Parsedown work?** -It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line start with a `-` then it perhaps belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). +It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line starts with a `-` then perhaps it belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages. From bc21988fe5e6cca8aae6928c0374779fd82710ed Mon Sep 17 00:00:00 2001 From: Yoan Blanc Date: Tue, 1 Nov 2016 18:27:37 +0100 Subject: [PATCH 063/238] Fix include from ParsedownTest I wasn't able to run all the tests from ParsedownExtra because of it. --- test/ParsedownTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index c922ab1..323dace 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -139,7 +139,7 @@ EXPECTED_HTML; public function testLateStaticBinding() { - include 'test/TestParsedown.php'; + include __DIR__ . '/TestParsedown.php'; $parsedown = Parsedown::instance(); $this->assertInstanceOf('Parsedown', $parsedown); From 48351504deb0336e265a9453cf9acab04fadae6d Mon Sep 17 00:00:00 2001 From: gene_sis Date: Fri, 6 Jan 2017 20:40:19 +0100 Subject: [PATCH 064/238] adjust two regex pattern within inlineLink() to reduce backtracking add test with base64 image --- Parsedown.php | 4 ++-- test/data/inline_link.html | 3 ++- test/data/inline_link.md | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 4737eee..d7b5537 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1204,7 +1204,7 @@ class Parsedown $remainder = $Excerpt['text']; - if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { $Element['text'] = $matches[1]; @@ -1217,7 +1217,7 @@ class Parsedown return; } - if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) + if (preg_match('/^[(]((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) { $Element['attributes']['href'] = $matches[1]; diff --git a/test/data/inline_link.html b/test/data/inline_link.html index 5ad564a..cef29cf 100644 --- a/test/data/inline_link.html +++ b/test/data/inline_link.html @@ -3,4 +3,5 @@

      (link) in parentheses

      link

      MD Logo

      -

      MD Logo and text

      \ No newline at end of file +

      MD Logo and text

      +

      MD Logo and text

      \ No newline at end of file diff --git a/test/data/inline_link.md b/test/data/inline_link.md index 6bac0b3..1ba24b7 100644 --- a/test/data/inline_link.md +++ b/test/data/inline_link.md @@ -8,4 +8,6 @@ [![MD Logo](http://parsedown.org/md.png)](http://example.com) -[![MD Logo](http://parsedown.org/md.png) and text](http://example.com) \ No newline at end of file +[![MD Logo](http://parsedown.org/md.png) and text](http://example.com) + +[![MD Logo](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAUCAYAAADskT9PAAAKQWlDQ1BJQ0MgUHJvZmlsZQAASA2dlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/syOll+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAEImlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MTwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+NTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjcyPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MzI8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjIwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxNS0wNi0xNFQxOTowNjo1OTwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjI8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ch7v5WoAAAGgSURBVEgNYywtLbVnYmLqYmBgMANieoJT//79K2MBWr4CaKsEPW2G2mUGspsFZnlnZycjPR1RXl7+H2Q3Ez0txWbXgDsAFAUYABo8YPH////HdXV1LUZWVFZWFsvIyLgIJoYt+pDNwCYP00swBIAWzaysrNSCaQCxgWLTYHxKaawhgGYoJzC7rC4sLDQBiYPYQIoHTQ3ZXGIcADJci42NDeZreGiQbSuSRmKiABb/CUB9IMwAjAKYGIhLESAYAj9//kwH+t4YaAvM59c4ODiyvn//HotuMzDh9QLFirCIg/I8CPQBE2QxhAkhCYZAf3//d2CJFQpU/h2EQeyGhoYvyIbA2FDDl8H4aPQydMtB8gQdAFLU3t5+DRjsWSAMYoPEcAFOTs5EoNw+NPl9UHE0YQYGglEA09HR0bEAxsZHA0PnFzAqgoBq9gIxKOrOAnEQSBxIYwCiQgBDFwEBYFB/BEaVJ7AQ2wGiQXxcWhhhJRZQ0UBURsSlAVyup4Y4TaKAFIeBouAJUIM0KZqoqPYpEzBrpQANfEFFQ4k16gXIbgCggnKoJ5DJdwAAAABJRU5ErkJggg==) and text](http://example.com) \ No newline at end of file From 0172d779d73d8d7ad94f9c486f8560a77d184389 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 21 Jan 2017 11:06:41 +0000 Subject: [PATCH 065/238] Trim surrounding whitespace from URL in inlineLink Fixes https://github.com/erusev/parsedown-extra/issues/103 --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index d7b5537..3e25b0d 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1217,7 +1217,7 @@ class Parsedown return; } - if (preg_match('/^[(]((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) { $Element['attributes']['href'] = $matches[1]; From 7081afe8cbf4081b49839c6789f45b2ef508ce5b Mon Sep 17 00:00:00 2001 From: Marek Skiba Date: Thu, 2 Mar 2017 12:43:51 +0100 Subject: [PATCH 066/238] Removed double semicolon --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 3e25b0d..20863a7 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -448,7 +448,7 @@ class Parsedown return $Block; } - $Block['element']['text']['text'] .= "\n".$Line['body'];; + $Block['element']['text']['text'] .= "\n".$Line['body']; return $Block; } From bd0e31a7ddfa3c45744599077b4c6b97ca2fbeb7 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Fri, 10 Mar 2017 01:04:53 +0200 Subject: [PATCH 067/238] Add Symfony demo to "Who uses it?" https://github.com/symfony/symfony-demo/blob/409a65b373cb47bc346742c452af5c7b1424f5cd/composer.json#L24 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7907787..4e5659a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w **Who uses it?** -[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://packagist.org/packages/erusev/parsedown/dependents). +[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). **How can I help?** From 0a09d5ad45b0f077f67493131e4e3f3ccde14389 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Thu, 23 Mar 2017 20:21:05 +0200 Subject: [PATCH 068/238] update tests to reflect changes in phpunit 6.0 --- test/CommonMarkTest.php | 2 +- test/ParsedownTest.php | 2 +- test/bootstrap.php | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index 9b8d116..37ed9fe 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -8,7 +8,7 @@ * @link http://commonmark.org/ CommonMark * @link http://git.io/8WtRvQ JavaScript test runner */ -class CommonMarkTest extends PHPUnit_Framework_TestCase +class CommonMarkTest extends \PHPUnit\Framework\TestCase { const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt'; diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 323dace..7e552e8 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -1,6 +1,6 @@ Date: Wed, 29 Mar 2017 19:04:15 +0300 Subject: [PATCH 069/238] add kbd to text-level elements --- Parsedown.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 20863a7..f5dd0fa 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1539,10 +1539,10 @@ class Parsedown 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 'i', 'rp', 'del', 'code', 'strike', 'marquee', 'q', 'rt', 'ins', 'font', 'strong', - 's', 'tt', 'sub', 'mark', - 'u', 'xm', 'sup', 'nobr', - 'var', 'ruby', - 'wbr', 'span', - 'time', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', ); } From 4367f89a74ecd7414d01f0411a8be6dba7071b08 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Wed, 29 Mar 2017 19:30:24 +0300 Subject: [PATCH 070/238] attempt to fix failing builds on 5.3 --- test/CommonMarkTest.php | 2 +- test/ParsedownTest.php | 2 +- test/bootstrap.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index 37ed9fe..9b8d116 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -8,7 +8,7 @@ * @link http://commonmark.org/ CommonMark * @link http://git.io/8WtRvQ JavaScript test runner */ -class CommonMarkTest extends \PHPUnit\Framework\TestCase +class CommonMarkTest extends PHPUnit_Framework_TestCase { const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt'; diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 7e552e8..323dace 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -1,6 +1,6 @@ Date: Sat, 25 Mar 2017 14:28:43 +0000 Subject: [PATCH 071/238] blockmarkup ends on interrupt by newline (CommonMark compliance) --- Parsedown.php | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f5dd0fa..e516f6b 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -683,7 +683,7 @@ class Parsedown return; } - if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) { $element = strtolower($matches[1]); @@ -694,7 +694,6 @@ class Parsedown $Block = array( 'name' => $matches[1], - 'depth' => 0, 'markup' => $Line['text'], ); @@ -730,35 +729,11 @@ class Parsedown protected function blockMarkupContinue($Line, array $Block) { - if (isset($Block['closed'])) + if (isset($Block['closed']) or isset($Block['interrupted'])) { return; } - if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open - { - $Block['depth'] ++; - } - - if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close - { - if ($Block['depth'] > 0) - { - $Block['depth'] --; - } - else - { - $Block['closed'] = true; - } - } - - if (isset($Block['interrupted'])) - { - $Block['markup'] .= "\n"; - - unset($Block['interrupted']); - } - $Block['markup'] .= "\n".$Line['body']; return $Block; From 1d0af35f10ed6b7e2a5841d032ac6a1de5b6f939 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 25 Mar 2017 14:47:36 +0000 Subject: [PATCH 072/238] update test to result generated by CommonMark reference parser --- test/data/sparse_html.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/data/sparse_html.html b/test/data/sparse_html.html index 9e89627..6e0213f 100644 --- a/test/data/sparse_html.html +++ b/test/data/sparse_html.html @@ -1,8 +1,6 @@
      line 1 - -line 2 -line 3 - -line 4 +

      line 2 +line 3

      +

      line 4

      \ No newline at end of file From 1140613fc7aa1393a6274812deab253b90027ae7 Mon Sep 17 00:00:00 2001 From: naNuke Date: Wed, 21 Jan 2015 03:50:36 +0100 Subject: [PATCH 073/238] Prevent various XSS attacks --- Parsedown.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index f5dd0fa..5d810de 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -75,6 +75,15 @@ class Parsedown protected $urlsLinked = true; + function setSafeLinksEnabled($safeLinksEnabled) + { + $this->safeLinksEnabled = $safeLinksEnabled; + + return $this; + } + + protected $safeLinksEnabled = true; + # # Lines # @@ -1253,7 +1262,13 @@ class Parsedown $Element['attributes']['title'] = $Definition['title']; } - $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']); + if ( $this->safeLinksEnabled && stripos($Element['attributes']['href'], 'javascript:') === 0 ) + { + return; + } + + $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href']); + $Element['text'] = htmlspecialchars($Element['text']); return array( 'extent' => $extent, From bf5105cb1a7a2656d134ab35456a727959574e7d Mon Sep 17 00:00:00 2001 From: naNuke Date: Sat, 24 Jan 2015 22:37:14 +0100 Subject: [PATCH 074/238] Improve safeLinks with whitelist. --- Parsedown.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 5d810de..94dbe20 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1262,13 +1262,18 @@ class Parsedown $Element['attributes']['title'] = $Definition['title']; } - if ( $this->safeLinksEnabled && stripos($Element['attributes']['href'], 'javascript:') === 0 ) + if ( $this->safeLinksEnabled && preg_match("/^(\/|https?:\/\/|ftps?:\/\/)/ui", $Element['attributes']['href']) === 0 ) { return; } - $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href']); - $Element['text'] = htmlspecialchars($Element['text']); + $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href'], ENT_QUOTES); + $Element['text'] = htmlspecialchars($Element['text'], ENT_QUOTES); + + if ( $Element['attributes']['title'] !== null ) + { + $Element['attributes']['title'] = htmlspecialchars($Element['attributes']['title'], ENT_QUOTES); + } return array( 'extent' => $extent, From 1d4296f34d938758ff88755e4ac44ae1d3fc6857 Mon Sep 17 00:00:00 2001 From: naNuke Date: Sun, 25 Jan 2015 19:47:32 +0100 Subject: [PATCH 075/238] Customizable whitelist of schemas for safeLinks --- Parsedown.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 94dbe20..9882810 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -84,6 +84,14 @@ class Parsedown protected $safeLinksEnabled = true; + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + '/', + 'ftp://', + 'ftps://' + ); + # # Lines # @@ -1262,9 +1270,22 @@ class Parsedown $Element['attributes']['title'] = $Definition['title']; } - if ( $this->safeLinksEnabled && preg_match("/^(\/|https?:\/\/|ftps?:\/\/)/ui", $Element['attributes']['href']) === 0 ) + if ( $this->safeLinksEnabled ) { - return; + $matched = false; + foreach ( $this->safeLinksWhitelist as $scheme ) + { + if ( stripos($Element['attributes']['href'], $scheme) === 0 ) + { + $matched = true; + break; + } + } + + if ( ! $matched ) + { + return; + } } $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href'], ENT_QUOTES); From b3d45c4bb9bc798e4b297da38c4b015ac8bb44c6 Mon Sep 17 00:00:00 2001 From: naNuke Date: Mon, 26 Jan 2015 18:49:17 +0100 Subject: [PATCH 076/238] Add html escaping to all attributes capable of holding user input. --- Parsedown.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 9882810..b162d8e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -422,7 +422,7 @@ class Parsedown if (isset($matches[1])) { - $class = 'language-'.$matches[1]; + $class = 'language-'.htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); $Element['attributes'] = array( 'class' => $class, @@ -532,10 +532,10 @@ class Parsedown ), ); - if($name === 'ol') + if($name === 'ol') { $listStart = stristr($matches[0], '.', true); - + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); @@ -1108,7 +1108,7 @@ class Parsedown { if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { - $url = $matches[1]; + $url = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); if ( ! isset($matches[2])) { @@ -1288,12 +1288,12 @@ class Parsedown } } - $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href'], ENT_QUOTES); - $Element['text'] = htmlspecialchars($Element['text'], ENT_QUOTES); + $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href'], ENT_QUOTES, 'UTF-8'); + $Element['text'] = htmlspecialchars($Element['text'], ENT_QUOTES, 'UTF-8'); if ( $Element['attributes']['title'] !== null ) { - $Element['attributes']['title'] = htmlspecialchars($Element['attributes']['title'], ENT_QUOTES); + $Element['attributes']['title'] = htmlspecialchars($Element['attributes']['title'], ENT_QUOTES, 'UTF-8'); } return array( @@ -1384,14 +1384,16 @@ class Parsedown if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { + $url = htmlspecialchars($matches[0][0], ENT_QUOTES, 'UTF-8'); + $Inline = array( 'extent' => strlen($matches[0][0]), 'position' => $matches[0][1], 'element' => array( 'name' => 'a', - 'text' => $matches[0][0], + 'text' => $url, 'attributes' => array( - 'href' => $matches[0][0], + 'href' => $url, ), ), ); @@ -1404,7 +1406,7 @@ class Parsedown { if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { - $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); + $url = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); return array( 'extent' => strlen($matches[0]), From 6bb66db00f7faf29932f96b47fc4ac60a4333e8e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 1 May 2017 03:24:40 +0100 Subject: [PATCH 077/238] anti-xss protect all attributes and content from xss via element method filter special attributes (a href, img src) expand url whitelist slightly to permit data images and mailto links --- Parsedown.php | 94 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index b162d8e..8571cd7 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -89,7 +89,9 @@ class Parsedown 'https://', '/', 'ftp://', - 'ftps://' + 'ftps://', + 'mailto:', + 'data:image/png;', ); # @@ -359,8 +361,6 @@ class Parsedown { $text = $Block['element']['text']['text']; - $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); - $Block['element']['text']['text'] = $text; return $Block; @@ -422,7 +422,7 @@ class Parsedown if (isset($matches[1])) { - $class = 'language-'.htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); + $class = 'language-'.$matches[1]; $Element['attributes'] = array( 'class' => $class, @@ -474,8 +474,6 @@ class Parsedown { $text = $Block['element']['text']['text']; - $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); - $Block['element']['text']['text'] = $text; return $Block; @@ -1091,7 +1089,6 @@ class Parsedown if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) { - $url = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); + $url = $matches[1]; if ( ! isset($matches[2])) { @@ -1270,32 +1267,6 @@ class Parsedown $Element['attributes']['title'] = $Definition['title']; } - if ( $this->safeLinksEnabled ) - { - $matched = false; - foreach ( $this->safeLinksWhitelist as $scheme ) - { - if ( stripos($Element['attributes']['href'], $scheme) === 0 ) - { - $matched = true; - break; - } - } - - if ( ! $matched ) - { - return; - } - } - - $Element['attributes']['href'] = htmlspecialchars($Element['attributes']['href'], ENT_QUOTES, 'UTF-8'); - $Element['text'] = htmlspecialchars($Element['text'], ENT_QUOTES, 'UTF-8'); - - if ( $Element['attributes']['title'] !== null ) - { - $Element['attributes']['title'] = htmlspecialchars($Element['attributes']['title'], ENT_QUOTES, 'UTF-8'); - } - return array( 'extent' => $extent, 'element' => $Element, @@ -1384,7 +1355,7 @@ class Parsedown if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { - $url = htmlspecialchars($matches[0][0], ENT_QUOTES, 'UTF-8'); + $url = $matches[0][0]; $Inline = array( 'extent' => strlen($matches[0][0]), @@ -1406,7 +1377,7 @@ class Parsedown { if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) { - $url = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8'); + $url = $matches[1]; return array( 'extent' => strlen($matches[0]), @@ -1444,6 +1415,8 @@ class Parsedown protected function element(array $Element) { + $Element = $this->sanitiseElement($Element); + $markup = '<'.$Element['name']; if (isset($Element['attributes'])) @@ -1455,7 +1428,7 @@ class Parsedown continue; } - $markup .= ' '.$name.'="'.$value.'"'; + $markup .= ' '.$name.'="'.self::escape($value).'"'; } } @@ -1469,7 +1442,7 @@ class Parsedown } else { - $markup .= $Element['text']; + $markup .= self::escape($Element['text'], true); } $markup .= ''; @@ -1528,10 +1501,55 @@ class Parsedown return $markup; } + protected function sanitiseElement(array $Element) + { + $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + if ($this->safeLinksEnabled) + { + $safe = false; + + foreach ($this->safeLinksWhitelist as $scheme) + { + if (stripos($Element['attributes'][$attribute], $scheme) === 0) + { + $safe = true; + + break; + } + } + + if ( ! $safe) + { + unset($Element['attributes'][$attribute]); + } + } + + return $Element; + } + # # Static Methods # + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + static function instance($name = 'default') { if (isset(self::$instances[$name])) From af04ac92e2ff852309891ebc767fa5a6bf179f39 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 1 May 2017 03:33:49 +0100 Subject: [PATCH 078/238] add xss tests --- test/ParsedownTest.php | 2 ++ test/data/xss_attribute_encoding.html | 6 ++++++ test/data/xss_attribute_encoding.md | 11 ++++++++++ test/data/xss_bad_url.html | 16 ++++++++++++++ test/data/xss_bad_url.md | 31 +++++++++++++++++++++++++++ test/data/xss_text_encoding.html | 7 ++++++ test/data/xss_text_encoding.md | 12 +++++++++++ 7 files changed, 85 insertions(+) create mode 100644 test/data/xss_attribute_encoding.html create mode 100644 test/data/xss_attribute_encoding.md create mode 100644 test/data/xss_bad_url.html create mode 100644 test/data/xss_bad_url.md create mode 100644 test/data/xss_text_encoding.html create mode 100644 test/data/xss_text_encoding.md diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 323dace..5fbf7f1 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -46,6 +46,8 @@ class ParsedownTest extends PHPUnit_Framework_TestCase $expectedMarkup = str_replace("\r\n", "\n", $expectedMarkup); $expectedMarkup = str_replace("\r", "\n", $expectedMarkup); + $this->Parsedown->setMarkupEscaped($test === 'xss_text_encoding'); + $actualMarkup = $this->Parsedown->text($markdown); $this->assertEquals($expectedMarkup, $actualMarkup); diff --git a/test/data/xss_attribute_encoding.html b/test/data/xss_attribute_encoding.html new file mode 100644 index 0000000..287ff51 --- /dev/null +++ b/test/data/xss_attribute_encoding.html @@ -0,0 +1,6 @@ +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss"

      +

      xss'

      \ No newline at end of file diff --git a/test/data/xss_attribute_encoding.md b/test/data/xss_attribute_encoding.md new file mode 100644 index 0000000..3d8e0c8 --- /dev/null +++ b/test/data/xss_attribute_encoding.md @@ -0,0 +1,11 @@ +[xss](https://www.example.com") + +![xss](https://www.example.com") + +[xss](https://www.example.com') + +![xss](https://www.example.com') + +![xss"](https://www.example.com) + +![xss'](https://www.example.com) \ No newline at end of file diff --git a/test/data/xss_bad_url.html b/test/data/xss_bad_url.html new file mode 100644 index 0000000..93dd0d8 --- /dev/null +++ b/test/data/xss_bad_url.html @@ -0,0 +1,16 @@ +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      \ No newline at end of file diff --git a/test/data/xss_bad_url.md b/test/data/xss_bad_url.md new file mode 100644 index 0000000..a730952 --- /dev/null +++ b/test/data/xss_bad_url.md @@ -0,0 +1,31 @@ +[xss](javascript:alert(1)) + +[xss]( javascript:alert(1)) + +[xss](javascript://alert(1)) + +[xss](javascript:alert(1)) + +![xss](javascript:alert(1)) + +![xss]( javascript:alert(1)) + +![xss](javascript://alert(1)) + +![xss](javascript:alert(1)) + +[xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +[xss]( data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +[xss](data://text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +[xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +![xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +![xss]( data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +![xss](data://text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) + +![xss](data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==) \ No newline at end of file diff --git a/test/data/xss_text_encoding.html b/test/data/xss_text_encoding.html new file mode 100644 index 0000000..e6b3fc5 --- /dev/null +++ b/test/data/xss_text_encoding.html @@ -0,0 +1,7 @@ +

      <script>alert(1)</script>

      +

      <script>

      +

      alert(1)

      +

      </script>

      +

      <script> +alert(1) +</script>

      \ No newline at end of file diff --git a/test/data/xss_text_encoding.md b/test/data/xss_text_encoding.md new file mode 100644 index 0000000..b1051a2 --- /dev/null +++ b/test/data/xss_text_encoding.md @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file From 924b26e16c3af33f152f95ecb7b0a19ebdc086cd Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 1 May 2017 03:53:29 +0100 Subject: [PATCH 079/238] replace hhvm nightly with nightly --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa5ca98..e5f1603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ php: - 5.4 - 5.3 - hhvm - - hhvm-nightly + - nightly matrix: fast_finish: true allow_failures: - - php: hhvm-nightly + - php: nightly From 131ba758514c5e905663bd82fc7c1c8271c7edc5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 1 May 2017 15:44:04 +0100 Subject: [PATCH 080/238] filter onevent attributes --- Parsedown.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index 8571cd7..2fadec0 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1513,6 +1513,22 @@ class Parsedown $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); } + if ( ! empty($Element['attributes'])) + { + # clear out nulls + $Element['attributes'] = array_filter( + $Element['attributes'], + function ($v) {return $v !== null;} + ); + + $onEventAttributes = preg_grep('/^\s*+on/i', array_flip($Element['attributes'])); + + foreach ($onEventAttributes as $att) + { + unset($Element['attributes'][$att]); + } + } + return $Element; } From 6d0156d70714fcd89c1b9c9eb573adade13bfb27 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 2 May 2017 00:30:04 +0100 Subject: [PATCH 081/238] dump attributes that contain characters that are impossible for validity, or very unlikely --- Parsedown.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 2fadec0..488af4b 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1503,7 +1503,8 @@ class Parsedown protected function sanitiseElement(array $Element) { - $safeUrlNameToAtt = array( + static $badAttributeChars = "\"'= \t\n\r\0\x0B"; + static $safeUrlNameToAtt = array( 'a' => 'href', 'img' => 'src', ); @@ -1515,13 +1516,21 @@ class Parsedown if ( ! empty($Element['attributes'])) { - # clear out nulls - $Element['attributes'] = array_filter( - $Element['attributes'], - function ($v) {return $v !== null;} - ); + foreach ($Element['attributes'] as $att => $val) + { + # clear out nulls + if ($val === null) + { + unset($Element['attributes'][$att]); + } + # filter out badly parsed attribute + elseif (strpbrk($att, $badAttributeChars) !== false) + { + unset($Element['attributes'][$att]); + } + } - $onEventAttributes = preg_grep('/^\s*+on/i', array_flip($Element['attributes'])); + $onEventAttributes = preg_grep('/^on/i', array_flip($Element['attributes'])); foreach ($onEventAttributes as $att) { From e4bb12329e8b895bfef4564296624cd47057d2be Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 2 May 2017 01:25:33 +0100 Subject: [PATCH 082/238] array_keys is probably faster --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 488af4b..d42e4f5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1530,9 +1530,9 @@ class Parsedown } } - $onEventAttributes = preg_grep('/^on/i', array_flip($Element['attributes'])); + $onEventAttributeKeys = preg_grep('/^on/i', array_keys($Element['attributes'])); - foreach ($onEventAttributes as $att) + foreach ($onEventAttributeKeys as $att) { unset($Element['attributes'][$att]); } From 4dc98b635d18527000e74a0eb4cd400db2bf5af3 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 2 May 2017 19:48:08 +0100 Subject: [PATCH 083/238] whitelist changes: * add gif and jpg as allowed data images * ensure that user controlled content fall only in the "data section" of the data URI (and does not intersect content-type definition in any way (best to be safe than sorry ;-))) "data section" as defined in: https://tools.ietf.org/html/rfc2397#section-3 --- Parsedown.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index d42e4f5..7e72d69 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -91,7 +91,9 @@ class Parsedown 'ftp://', 'ftps://', 'mailto:', - 'data:image/png;', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpg;base64,', ); # From aee3963e6b97186b1e5526c118bf5d2d872cd8ee Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 2 May 2017 19:55:03 +0100 Subject: [PATCH 084/238] jpeg, not jpg --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 7e72d69..c319a19 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -93,7 +93,7 @@ class Parsedown 'mailto:', 'data:image/png;base64,', 'data:image/gif;base64,', - 'data:image/jpg;base64,', + 'data:image/jpeg;base64,', ); # From 4bae1c9834382d3c7aa900a7cbf77771b3864c56 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 3 May 2017 00:39:01 +0100 Subject: [PATCH 085/238] whitelist regex for good attribute (no no chars that could form a delimiter allowed --- Parsedown.php | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index c319a19..0bd81e2 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1505,7 +1505,7 @@ class Parsedown protected function sanitiseElement(array $Element) { - static $badAttributeChars = "\"'= \t\n\r\0\x0B"; + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; static $safeUrlNameToAtt = array( 'a' => 'href', 'img' => 'src', @@ -1520,23 +1520,16 @@ class Parsedown { foreach ($Element['attributes'] as $att => $val) { - # clear out nulls - if ($val === null) - { - unset($Element['attributes'][$att]); - } # filter out badly parsed attribute - elseif (strpbrk($att, $badAttributeChars) !== false) + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (preg_match('/^on/i', $att)) { unset($Element['attributes'][$att]); } - } - - $onEventAttributeKeys = preg_grep('/^on/i', array_keys($Element['attributes'])); - - foreach ($onEventAttributeKeys as $att) - { - unset($Element['attributes'][$att]); } } From 054ba3c48729036fd8ab1166fb0b1d0b4dd1e465 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 3 May 2017 17:01:27 +0100 Subject: [PATCH 086/238] urlencode urls that are potentially unsafe: this should break urls that attempt to include a protocol, or port (these are absolute URLs and should have a whitelisted protocol for use) but URLs that are relative, or relative from the site root should be preserved (though characters non essential for the URL structure may be urlencoded) this approach has significant advantages over attempting to locate something like `javascript:alert(1)` or `javascript:alert(1)` (which are both valid) because browsers have been known to ignore ridiculous characters when encountered (meaning something like `jav\ta\0\0script:alert(1)` would be xss :( ). Instead of trying to chase down a way to interpret a URL to decide whether there is a protocol, this approach ensures that two essential characters needed to achieve a colon are encoded `:` (obviously) and `;` (from `:`). If these characters appear in a relative URL then they are equivalent to their URL encoded form and so this change will be non breaking for that case. --- Parsedown.php | 10 ++++++++-- test/data/inline_link.html | 2 +- test/data/xss_bad_url.html | 32 ++++++++++++++++---------------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0bd81e2..702b041 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -87,7 +87,6 @@ class Parsedown protected $safeLinksWhitelist = array( 'http://', 'https://', - '/', 'ftp://', 'ftps://', 'mailto:', @@ -1554,7 +1553,14 @@ class Parsedown if ( ! $safe) { - unset($Element['attributes'][$attribute]); + $Element['attributes'][$attribute] = preg_replace_callback( + '/[^\/#?&=%]++/', + function (array $match) + { + return urlencode($match[0]); + }, + $Element['attributes'][$attribute] + ); } } diff --git a/test/data/inline_link.html b/test/data/inline_link.html index cef29cf..7a3131b 100644 --- a/test/data/inline_link.html +++ b/test/data/inline_link.html @@ -1,5 +1,5 @@

      link

      -

      link with parentheses in URL

      +

      link with parentheses in URL

      (link) in parentheses

      link

      MD Logo

      diff --git a/test/data/xss_bad_url.html b/test/data/xss_bad_url.html index 93dd0d8..8e43877 100644 --- a/test/data/xss_bad_url.html +++ b/test/data/xss_bad_url.html @@ -1,16 +1,16 @@ -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      \ No newline at end of file +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      \ No newline at end of file From f76b10aaab9ff23a926323450885d0cd224fd147 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Thu, 4 May 2017 10:28:55 +0300 Subject: [PATCH 087/238] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e5659a..ffe3116 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> You might also like [Caret](http://caret.io?ref=parsedown) - our Markdown editor for Mac / Windows / Linux. +> You might also like [Caret](https://caret.io?ref=parsedown) - our Markdown editor for Mac / Windows / Linux. ## Parsedown From dc30cb441c0834357b7f145444f6e53cbe67154e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 5 May 2017 21:32:27 +0100 Subject: [PATCH 088/238] add more protocols to the whitelist --- Parsedown.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index 702b041..6ef12d3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -93,6 +93,14 @@ class Parsedown 'data:image/png;base64,', 'data:image/gif;base64,', 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'ftp:', + 'ftps:', + 'news:', + 'steam:', ); # From 2e4afde68dae7f410af8c9e3a0664fcf97ce8a3d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 5 May 2017 21:55:58 +0100 Subject: [PATCH 089/238] faster check substr at beginning of string --- Parsedown.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6ef12d3..096c399 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1533,7 +1533,7 @@ class Parsedown unset($Element['attributes'][$att]); } # dump onevent attribute - elseif (preg_match('/^on/i', $att)) + elseif (self::striAtStart($att, 'on')) { unset($Element['attributes'][$att]); } @@ -1551,7 +1551,7 @@ class Parsedown foreach ($this->safeLinksWhitelist as $scheme) { - if (stripos($Element['attributes'][$attribute], $scheme) === 0) + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { $safe = true; @@ -1584,6 +1584,20 @@ class Parsedown return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); } + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + static function instance($name = 'default') { if (isset(self::$instances[$name])) From 226f636360d29060f9d6d15ec47ce5de575a226c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 7 May 2017 13:45:59 +0100 Subject: [PATCH 090/238] remove $safe flag --- Parsedown.php | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 096c399..42b8298 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1547,29 +1547,23 @@ class Parsedown { if ($this->safeLinksEnabled) { - $safe = false; - foreach ($this->safeLinksWhitelist as $scheme) { if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { - $safe = true; - - break; + return $Element; } } - if ( ! $safe) - { - $Element['attributes'][$attribute] = preg_replace_callback( - '/[^\/#?&=%]++/', - function (array $match) - { - return urlencode($match[0]); - }, - $Element['attributes'][$attribute] - ); - } + $Element['attributes'][$attribute] = preg_replace_callback( + '/[^\/#?&=%]++/', + function (array $match) + { + return urlencode($match[0]); + }, + $Element['attributes'][$attribute] + ); + } return $Element; From c63b690a799cb6ae1641d345d33f67ed6efc6fb2 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 9 May 2017 14:50:15 +0100 Subject: [PATCH 091/238] remove duplicates --- Parsedown.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 42b8298..0695c6f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -97,8 +97,6 @@ class Parsedown 'ircs:', 'git:', 'ssh:', - 'ftp:', - 'ftps:', 'news:', 'steam:', ); From b1e5aebaf6c8162cda482e96a1370155fd2162b5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 9 May 2017 19:22:58 +0100 Subject: [PATCH 092/238] add single safeMode option that encompasses protection from link destination xss and plain markup based xss into a single on/off switch --- Parsedown.php | 14 +++++++------- test/ParsedownTest.php | 2 +- test/data/inline_link.html | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0695c6f..0dbf40c 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -75,14 +75,14 @@ class Parsedown protected $urlsLinked = true; - function setSafeLinksEnabled($safeLinksEnabled) + function setSafeMode($safeMode) { - $this->safeLinksEnabled = $safeLinksEnabled; + $this->safeMode = (bool) $safeMode; return $this; } - protected $safeLinksEnabled = true; + protected $safeMode; protected $safeLinksWhitelist = array( 'http://', @@ -378,7 +378,7 @@ class Parsedown protected function blockComment($Line) { - if ($this->markupEscaped) + if ($this->markupEscaped or $this->safeMode) { return; } @@ -700,7 +700,7 @@ class Parsedown protected function blockMarkup($Line) { - if ($this->markupEscaped) + if ($this->markupEscaped or $this->safeMode) { return; } @@ -1282,7 +1282,7 @@ class Parsedown protected function inlineMarkup($Excerpt) { - if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) { return; } @@ -1543,7 +1543,7 @@ class Parsedown protected function filterUnsafeUrlInAttribute(array $Element, $attribute) { - if ($this->safeLinksEnabled) + if ($this->safeMode) { foreach ($this->safeLinksWhitelist as $scheme) { diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 5fbf7f1..c7e3a82 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -46,7 +46,7 @@ class ParsedownTest extends PHPUnit_Framework_TestCase $expectedMarkup = str_replace("\r\n", "\n", $expectedMarkup); $expectedMarkup = str_replace("\r", "\n", $expectedMarkup); - $this->Parsedown->setMarkupEscaped($test === 'xss_text_encoding'); + $this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss'); $actualMarkup = $this->Parsedown->text($markdown); diff --git a/test/data/inline_link.html b/test/data/inline_link.html index 7a3131b..cef29cf 100644 --- a/test/data/inline_link.html +++ b/test/data/inline_link.html @@ -1,5 +1,5 @@

      link

      -

      link with parentheses in URL

      +

      link with parentheses in URL

      (link) in parentheses

      link

      MD Logo

      From bbb7687f31d6904f3a0e11e97bc61852a62cfe90 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 9 May 2017 19:31:36 +0100 Subject: [PATCH 093/238] safeMode will either apply all sanitisation techniques to an element or none (note that encoding HTML entities is done regardless because it speaks to character context, and that the only attributes/elements we should permit are the ones we actually mean to create) --- Parsedown.php | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0dbf40c..c540d12 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1422,7 +1422,10 @@ class Parsedown protected function element(array $Element) { - $Element = $this->sanitiseElement($Element); + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } $markup = '<'.$Element['name']; @@ -1543,27 +1546,23 @@ class Parsedown protected function filterUnsafeUrlInAttribute(array $Element, $attribute) { - if ($this->safeMode) + foreach ($this->safeLinksWhitelist as $scheme) { - foreach ($this->safeLinksWhitelist as $scheme) + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { - if (self::striAtStart($Element['attributes'][$attribute], $scheme)) - { - return $Element; - } + return $Element; } - - $Element['attributes'][$attribute] = preg_replace_callback( - '/[^\/#?&=%]++/', - function (array $match) - { - return urlencode($match[0]); - }, - $Element['attributes'][$attribute] - ); - } + $Element['attributes'][$attribute] = preg_replace_callback( + '/[^\/#?&=%]++/', + function (array $match) + { + return urlencode($match[0]); + }, + $Element['attributes'][$attribute] + ); + return $Element; } From 67c3efbea0d33c4433c6b18ed163b45c01395867 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 9 May 2017 19:37:13 +0100 Subject: [PATCH 094/238] according to https://tools.ietf.org/html/rfc3986#section-3 the colon is a required part of the syntax, other methods of achieving the colon character (as to browser interpretation) should be taken care of by htmlencoding that is done on all attribute content --- Parsedown.php | 9 +-------- test/data/xss_bad_url.html | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index c540d12..110d6e3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1554,14 +1554,7 @@ class Parsedown } } - $Element['attributes'][$attribute] = preg_replace_callback( - '/[^\/#?&=%]++/', - function (array $match) - { - return urlencode($match[0]); - }, - $Element['attributes'][$attribute] - ); + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); return $Element; } diff --git a/test/data/xss_bad_url.html b/test/data/xss_bad_url.html index 8e43877..0b216d1 100644 --- a/test/data/xss_bad_url.html +++ b/test/data/xss_bad_url.html @@ -1,16 +1,16 @@ -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      -

      xss

      \ No newline at end of file +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      +

      xss

      \ No newline at end of file From c82af01bd6e97fb5c39aaf2f6a41b6d55c9d66e4 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 14 May 2017 14:36:55 +0100 Subject: [PATCH 095/238] add sudo false --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index e5f1603..6799ce6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,3 +14,5 @@ matrix: fast_finish: true allow_failures: - php: nightly + +sudo: false \ No newline at end of file From be963a6531001b7d87083fb3f77b0df525ebc8db Mon Sep 17 00:00:00 2001 From: Daniel Berthereau Date: Mon, 19 Jun 2017 00:00:00 +0200 Subject: [PATCH 096/238] Added tests for consistency when a markdown follows a markup without blank line. --- test/ParsedownTest.php | 3 ++- test/data/markup_consecutive_one.html | 3 +++ test/data/markup_consecutive_one.md | 4 ++++ test/data/markup_consecutive_one_line.html | 4 ++++ test/data/markup_consecutive_one_line.md | 5 +++++ test/data/markup_consecutive_one_stripped.html | 3 +++ test/data/markup_consecutive_one_stripped.md | 4 ++++ test/data/markup_consecutive_two.html | 3 +++ test/data/markup_consecutive_two.md | 4 ++++ test/data/markup_consecutive_two_lines.html | 4 ++++ test/data/markup_consecutive_two_lines.md | 5 +++++ test/data/markup_consecutive_two_stripped.html | 4 ++++ test/data/markup_consecutive_two_stripped.md | 5 +++++ 13 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/data/markup_consecutive_one.html create mode 100644 test/data/markup_consecutive_one.md create mode 100644 test/data/markup_consecutive_one_line.html create mode 100644 test/data/markup_consecutive_one_line.md create mode 100644 test/data/markup_consecutive_one_stripped.html create mode 100644 test/data/markup_consecutive_one_stripped.md create mode 100644 test/data/markup_consecutive_two.html create mode 100644 test/data/markup_consecutive_two.md create mode 100644 test/data/markup_consecutive_two_lines.html create mode 100644 test/data/markup_consecutive_two_lines.md create mode 100644 test/data/markup_consecutive_two_stripped.html create mode 100644 test/data/markup_consecutive_two_stripped.md diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 323dace..bef612b 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -10,7 +10,8 @@ class ParsedownTest extends PHPUnit_Framework_TestCase parent::__construct($name, $data, $dataName); } - private $dirs, $Parsedown; + private $dirs; + protected $Parsedown; /** * @return array diff --git a/test/data/markup_consecutive_one.html b/test/data/markup_consecutive_one.html new file mode 100644 index 0000000..a0b1fca --- /dev/null +++ b/test/data/markup_consecutive_one.html @@ -0,0 +1,3 @@ +
      Markup
      +

      No markdown without blank line if strict compliance, but main processors convert it.

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one.md b/test/data/markup_consecutive_one.md new file mode 100644 index 0000000..7a00770 --- /dev/null +++ b/test/data/markup_consecutive_one.md @@ -0,0 +1,4 @@ +
      Markup
      +_No markdown_ without blank line if strict compliance, but **main** processors convert it. + +**Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_one_line.html b/test/data/markup_consecutive_one_line.html new file mode 100644 index 0000000..2a301aa --- /dev/null +++ b/test/data/markup_consecutive_one_line.html @@ -0,0 +1,4 @@ +
      One markup on +two lines
      +

      Markdown

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one_line.md b/test/data/markup_consecutive_one_line.md new file mode 100644 index 0000000..d40609a --- /dev/null +++ b/test/data/markup_consecutive_one_line.md @@ -0,0 +1,5 @@ +
      One markup on +two lines
      +_Markdown_ + +**Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_one_stripped.html b/test/data/markup_consecutive_one_stripped.html new file mode 100644 index 0000000..eb5136b --- /dev/null +++ b/test/data/markup_consecutive_one_stripped.html @@ -0,0 +1,3 @@ +

      Stripped markup

      +

      Markdown

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one_stripped.md b/test/data/markup_consecutive_one_stripped.md new file mode 100644 index 0000000..4189580 --- /dev/null +++ b/test/data/markup_consecutive_one_stripped.md @@ -0,0 +1,4 @@ +

      Stripped markup

      +_Markdown_ + +**Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two.html b/test/data/markup_consecutive_two.html new file mode 100644 index 0000000..efcebcd --- /dev/null +++ b/test/data/markup_consecutive_two.html @@ -0,0 +1,3 @@ +
      First markup

      and second markup on the same line.

      +

      Markdown

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two.md b/test/data/markup_consecutive_two.md new file mode 100644 index 0000000..bd663ac --- /dev/null +++ b/test/data/markup_consecutive_two.md @@ -0,0 +1,4 @@ +
      First markup

      and second markup on the same line.

      +_Markdown_ + +**Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two_lines.html b/test/data/markup_consecutive_two_lines.html new file mode 100644 index 0000000..cfa1463 --- /dev/null +++ b/test/data/markup_consecutive_two_lines.html @@ -0,0 +1,4 @@ +
      First markup

      and partial markup +on two lines.

      +

      Markdown

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two_lines.md b/test/data/markup_consecutive_two_lines.md new file mode 100644 index 0000000..66318e9 --- /dev/null +++ b/test/data/markup_consecutive_two_lines.md @@ -0,0 +1,5 @@ +
      First markup

      and partial markup +on two lines.

      +_Markdown_ + +**Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two_stripped.html b/test/data/markup_consecutive_two_stripped.html new file mode 100644 index 0000000..1633678 --- /dev/null +++ b/test/data/markup_consecutive_two_stripped.html @@ -0,0 +1,4 @@ +

      Stripped markup +on two lines

      +

      Markdown

      +

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two_stripped.md b/test/data/markup_consecutive_two_stripped.md new file mode 100644 index 0000000..8173380 --- /dev/null +++ b/test/data/markup_consecutive_two_stripped.md @@ -0,0 +1,5 @@ +

      Stripped markup +on two lines

      +_Markdown_ + +**Markdown** \ No newline at end of file From 129f807e32104a31134882c9ca32b279350f3db3 Mon Sep 17 00:00:00 2001 From: Daniel Berthereau Date: Thu, 22 Jun 2017 00:00:00 +0200 Subject: [PATCH 097/238] Inverted checks of consistency for markdown following markups. --- test/data/markup_consecutive_one.html | 2 +- test/data/markup_consecutive_one.md | 2 +- test/data/markup_consecutive_one_line.html | 2 +- test/data/markup_consecutive_one_line.md | 2 +- test/data/markup_consecutive_one_stripped.html | 2 +- test/data/markup_consecutive_one_stripped.md | 2 +- test/data/markup_consecutive_two.html | 2 +- test/data/markup_consecutive_two.md | 2 +- test/data/markup_consecutive_two_lines.html | 2 +- test/data/markup_consecutive_two_lines.md | 2 +- test/data/markup_consecutive_two_stripped.html | 2 +- test/data/markup_consecutive_two_stripped.md | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/data/markup_consecutive_one.html b/test/data/markup_consecutive_one.html index a0b1fca..d4c5005 100644 --- a/test/data/markup_consecutive_one.html +++ b/test/data/markup_consecutive_one.html @@ -1,3 +1,3 @@
      Markup
      -

      No markdown without blank line if strict compliance, but main processors convert it.

      +_No markdown_ without blank line for **strict** compliance with CommonMark.

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one.md b/test/data/markup_consecutive_one.md index 7a00770..18b4dcb 100644 --- a/test/data/markup_consecutive_one.md +++ b/test/data/markup_consecutive_one.md @@ -1,4 +1,4 @@
      Markup
      -_No markdown_ without blank line if strict compliance, but **main** processors convert it. +_No markdown_ without blank line for **strict** compliance with CommonMark. **Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_one_line.html b/test/data/markup_consecutive_one_line.html index 2a301aa..a89b4fd 100644 --- a/test/data/markup_consecutive_one_line.html +++ b/test/data/markup_consecutive_one_line.html @@ -1,4 +1,4 @@
      One markup on two lines
      -

      Markdown

      +_No markdown_

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one_line.md b/test/data/markup_consecutive_one_line.md index d40609a..daf945a 100644 --- a/test/data/markup_consecutive_one_line.md +++ b/test/data/markup_consecutive_one_line.md @@ -1,5 +1,5 @@
      One markup on two lines
      -_Markdown_ +_No markdown_ **Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_one_stripped.html b/test/data/markup_consecutive_one_stripped.html index eb5136b..ccc01f1 100644 --- a/test/data/markup_consecutive_one_stripped.html +++ b/test/data/markup_consecutive_one_stripped.html @@ -1,3 +1,3 @@

      Stripped markup

      -

      Markdown

      +_No markdown_

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_one_stripped.md b/test/data/markup_consecutive_one_stripped.md index 4189580..7f8df0c 100644 --- a/test/data/markup_consecutive_one_stripped.md +++ b/test/data/markup_consecutive_one_stripped.md @@ -1,4 +1,4 @@

      Stripped markup

      -_Markdown_ +_No markdown_ **Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two.html b/test/data/markup_consecutive_two.html index efcebcd..f7e71c7 100644 --- a/test/data/markup_consecutive_two.html +++ b/test/data/markup_consecutive_two.html @@ -1,3 +1,3 @@
      First markup

      and second markup on the same line.

      -

      Markdown

      +_No markdown_

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two.md b/test/data/markup_consecutive_two.md index bd663ac..83f3af7 100644 --- a/test/data/markup_consecutive_two.md +++ b/test/data/markup_consecutive_two.md @@ -1,4 +1,4 @@
      First markup

      and second markup on the same line.

      -_Markdown_ +_No markdown_ **Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two_lines.html b/test/data/markup_consecutive_two_lines.html index cfa1463..ffa4728 100644 --- a/test/data/markup_consecutive_two_lines.html +++ b/test/data/markup_consecutive_two_lines.html @@ -1,4 +1,4 @@
      First markup

      and partial markup on two lines.

      -

      Markdown

      +_No markdown_

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two_lines.md b/test/data/markup_consecutive_two_lines.md index 66318e9..dc70bb7 100644 --- a/test/data/markup_consecutive_two_lines.md +++ b/test/data/markup_consecutive_two_lines.md @@ -1,5 +1,5 @@
      First markup

      and partial markup on two lines.

      -_Markdown_ +_No markdown_ **Markdown** \ No newline at end of file diff --git a/test/data/markup_consecutive_two_stripped.html b/test/data/markup_consecutive_two_stripped.html index 1633678..707d6be 100644 --- a/test/data/markup_consecutive_two_stripped.html +++ b/test/data/markup_consecutive_two_stripped.html @@ -1,4 +1,4 @@

      Stripped markup on two lines

      -

      Markdown

      +_No markdown_

      Markdown

      \ No newline at end of file diff --git a/test/data/markup_consecutive_two_stripped.md b/test/data/markup_consecutive_two_stripped.md index 8173380..af5b781 100644 --- a/test/data/markup_consecutive_two_stripped.md +++ b/test/data/markup_consecutive_two_stripped.md @@ -1,5 +1,5 @@

      Stripped markup on two lines

      -_Markdown_ +_No markdown_ **Markdown** \ No newline at end of file From 6a4afac0d02218b134b922428e5c659ed9360d36 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 22 Jun 2017 00:02:03 +0100 Subject: [PATCH 098/238] remove ability for htmlblock to allow paragraph after if it closes on the same line --- Parsedown.php | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e516f6b..f6daad2 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -515,10 +515,10 @@ class Parsedown ), ); - if($name === 'ol') + if($name === 'ol') { $listStart = stristr($matches[0], '.', true); - + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); @@ -697,32 +697,6 @@ class Parsedown 'markup' => $Line['text'], ); - $length = strlen($matches[0]); - - $remainder = substr($Line['text'], $length); - - if (trim($remainder) === '') - { - if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) - { - $Block['closed'] = true; - - $Block['void'] = true; - } - } - else - { - if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) - { - return; - } - - if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) - { - $Block['closed'] = true; - } - } - return $Block; } } From c05bff047ad6703b1b4abd58bca9de78f32fa05a Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 22 Jun 2017 00:03:12 +0100 Subject: [PATCH 099/238] correct test to match CommonMark specified input for output --- test/data/self-closing_html.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/data/self-closing_html.md b/test/data/self-closing_html.md index acb2032..61d16a3 100644 --- a/test/data/self-closing_html.md +++ b/test/data/self-closing_html.md @@ -1,12 +1,18 @@
      + paragraph
      + paragraph
      + paragraph
      + paragraph
      + paragraph
      + paragraph \ No newline at end of file From 44042011755697dd1b8972b6a79c2b87c759660e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 20 Aug 2017 10:28:46 +0100 Subject: [PATCH 100/238] Properly support fenced code block infostring Reference: http://spec.commonmark.org/0.28/#info-string --- Parsedown.php | 6 +++--- test/data/fenced_code_block.html | 3 ++- test/data/fenced_code_block.md | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f5dd0fa..00f61f3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -396,7 +396,7 @@ class Parsedown protected function blockFencedCode($Line) { - if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) { $Element = array( 'name' => 'code', @@ -515,10 +515,10 @@ class Parsedown ), ); - if($name === 'ol') + if($name === 'ol') { $listStart = stristr($matches[0], '.', true); - + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); diff --git a/test/data/fenced_code_block.html b/test/data/fenced_code_block.html index 8bdabba..ce8dfd0 100644 --- a/test/data/fenced_code_block.html +++ b/test/data/fenced_code_block.html @@ -3,4 +3,5 @@ $message = 'fenced code block'; echo $message;
    tilde
    -
    echo 'language identifier';
    \ No newline at end of file +
    echo 'language identifier';
    +
    echo 'language identifier with non words';
    \ No newline at end of file diff --git a/test/data/fenced_code_block.md b/test/data/fenced_code_block.md index cbed8eb..9176ef4 100644 --- a/test/data/fenced_code_block.md +++ b/test/data/fenced_code_block.md @@ -11,4 +11,8 @@ tilde ```php echo 'language identifier'; +``` + +```c# +echo 'language identifier with non words'; ``` \ No newline at end of file From 07c937583d56776b7cfd0564b542fa95a7765012 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Sun, 22 Oct 2017 15:57:58 +0300 Subject: [PATCH 101/238] improve readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ffe3116..0459e3c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Better Markdown Parser in PHP * Tested in 5.3 to 7.1 and in HHVM * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) +Note that when you deal with untrusted content (ex: user commnets) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). + ### Installation Include `Parsedown.php` or install [the composer package](https://packagist.org/packages/erusev/parsedown). From 16aadff2ed7f03e3ea19bff15cac980e339a84b9 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Sun, 22 Oct 2017 16:00:43 +0300 Subject: [PATCH 102/238] improve readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0459e3c..9a2d56e 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ Better Markdown Parser in PHP * Tested in 5.3 to 7.1 and in HHVM * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) -Note that when you deal with untrusted content (ex: user commnets) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). - ### Installation Include `Parsedown.php` or install [the composer package](https://packagist.org/packages/erusev/parsedown). @@ -37,6 +35,10 @@ echo $Parsedown->text('Hello _Parsedown_!'); # prints:

    Hello Parsedown Date: Sun, 22 Oct 2017 16:01:34 +0300 Subject: [PATCH 103/238] improve readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a2d56e..7073a95 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t ### Security -Parsedownw does not sanitize the outut HTML. When you deal with untrusted content (ex: user commnets) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). +Parsedown does not sanitize the HTML that it generates. When you deal with untrusted content (ex: user commnets) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). ### Questions From af6affdc2c7dcdfb1c14bc642c374005193b10ca Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Mon, 6 Nov 2017 16:54:00 +0200 Subject: [PATCH 104/238] improve readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7073a95..5f5c38e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Better Markdown Parser in PHP ### Features * One File +* No Dependencies * Super Fast * Extensible * [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) From 691e36b1f241e7000fa36d176a2b13de4820b64c Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sat, 11 Nov 2017 00:56:03 -0200 Subject: [PATCH 105/238] Use PHPUnit\Framework\TestCase instead of PHPUnit_Framework_TestCase --- composer.json | 3 +++ phpunit.xml.dist | 2 +- test/CommonMarkTest.php | 5 ++++- test/ParsedownTest.php | 4 +++- test/bootstrap.php | 4 ---- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 28145af..b7f8aea 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,9 @@ "require": { "php": ">=5.3.0" }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, "autoload": { "psr-0": {"Parsedown": ""} } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b2d5e9d..dd9da22 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,4 +5,4 @@ test/ParsedownTest.php - \ No newline at end of file + diff --git a/test/CommonMarkTest.php b/test/CommonMarkTest.php index 9b8d116..7111f0b 100644 --- a/test/CommonMarkTest.php +++ b/test/CommonMarkTest.php @@ -8,7 +8,10 @@ * @link http://commonmark.org/ CommonMark * @link http://git.io/8WtRvQ JavaScript test runner */ -class CommonMarkTest extends PHPUnit_Framework_TestCase + +use PHPUnit\Framework\TestCase; + +class CommonMarkTest extends TestCase { const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt'; diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 323dace..3b4c7d9 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -1,6 +1,8 @@ Date: Sat, 11 Nov 2017 01:02:11 -0200 Subject: [PATCH 106/238] Make Travis CI use installed PHPUnit version, not global one --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6799ce6..58831c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,5 +14,10 @@ matrix: fast_finish: true allow_failures: - php: nightly + - php: 5.3 -sudo: false \ No newline at end of file +before_script: + - composer install --prefer-dist --no-interaction --no-progress + +script: + - vendor/bin/phpunit From 09827f542c09ef729e34a01ff9eafa1c832f7559 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Tue, 14 Nov 2017 15:19:24 -0200 Subject: [PATCH 107/238] Rewrite Travis CI --- .travis.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58831c9..cfb4d8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,24 @@ language: php -php: - - 7.1 - - 7.0 - - 5.6 - - 5.5 - - 5.4 - - 5.3 - - hhvm - - nightly +dist: trusty +sudo: false matrix: + include: + - php: 5.3 + dist: precise + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: nightly + - php: hhvm + - php: hhvm-nightly fast_finish: true allow_failures: - php: nightly - - php: 5.3 + - php: hhvm-nightly before_script: - composer install --prefer-dist --no-interaction --no-progress From 089789dfff34a552e96998933803d75b230f465b Mon Sep 17 00:00:00 2001 From: John Bafford Date: Tue, 14 Nov 2017 17:13:31 -0500 Subject: [PATCH 108/238] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f5c38e..e1f2b53 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t ### Security -Parsedown does not sanitize the HTML that it generates. When you deal with untrusted content (ex: user commnets) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). +Parsedown does not sanitize the HTML that it generates. When you deal with untrusted content (ex: user comments) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). ### Questions From d98d60aaf336ebdfbd612a51cce4dc748409d659 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Sun, 31 Dec 2017 22:10:48 +0100 Subject: [PATCH 109/238] Update license year --- LICENSE.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index baca86f..4552494 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Emanuil Rusev, erusev.com +Copyright (c) 2018 Emanuil Rusev, erusev.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 1244122b841f3930052a16c9f4ddd6858dc1060c Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Mon, 1 Jan 2018 14:09:31 +0100 Subject: [PATCH 110/238] Update LICENSE.txt --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 4552494..8e7c764 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Emanuil Rusev, erusev.com +Copyright (c) 2013-2018 Emanuil Rusev, erusev.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 0e1043a8d6bb5bae93bfc65c99bd109e7f486b7b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 19 Feb 2017 16:12:04 +0000 Subject: [PATCH 111/238] consistent li items for loose list --- Parsedown.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 757666e..877f5b8 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -515,10 +515,10 @@ class Parsedown ), ); - if($name === 'ol') + if($name === 'ol') { $listStart = stristr($matches[0], '.', true); - + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); @@ -547,6 +547,8 @@ class Parsedown { $Block['li']['text'] []= ''; + $Block['loose'] = true; + unset($Block['interrupted']); } @@ -595,6 +597,22 @@ class Parsedown } } + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['text'] as &$li) + { + if (end($li['text']) !== '') + { + $li['text'] []= ''; + } + } + } + + return $Block; + } + # # Quote From 7fd92a8fbd76dc9312dc7fb47fe05c8fb02fe3b7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 19 Feb 2017 16:19:55 +0000 Subject: [PATCH 112/238] update tests --- test/data/paragraph_list.html | 4 +++- test/data/sparse_dense_list.html | 8 ++++++-- test/data/sparse_list.html | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/data/paragraph_list.html b/test/data/paragraph_list.html index ced1c43..00a612c 100644 --- a/test/data/paragraph_list.html +++ b/test/data/paragraph_list.html @@ -8,5 +8,7 @@

  • li

  • -
  • li
  • +
  • +

    li

    +
  • \ No newline at end of file diff --git a/test/data/sparse_dense_list.html b/test/data/sparse_dense_list.html index 095bc73..58923f8 100644 --- a/test/data/sparse_dense_list.html +++ b/test/data/sparse_dense_list.html @@ -2,6 +2,10 @@
  • li

  • -
  • li
  • -
  • li
  • +
  • +

    li

    +
  • +
  • +

    li

    +
  • \ No newline at end of file diff --git a/test/data/sparse_list.html b/test/data/sparse_list.html index 452b2b8..9803d27 100644 --- a/test/data/sparse_list.html +++ b/test/data/sparse_list.html @@ -2,7 +2,9 @@
  • li

  • -
  • li
  • +
  • +

    li

    +

    • From 722b776684eb10a0cb4e59c45b51156670cf6465 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 29 Jan 2018 14:25:00 +0100 Subject: [PATCH 113/238] Test multiple multiline lists --- test/data/multiline_lists.html | 10 ++++++++++ test/data/multiline_lists.md | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 test/data/multiline_lists.html create mode 100644 test/data/multiline_lists.md diff --git a/test/data/multiline_lists.html b/test/data/multiline_lists.html new file mode 100644 index 0000000..a223792 --- /dev/null +++ b/test/data/multiline_lists.html @@ -0,0 +1,10 @@ +
        +
      1. +

        One +First body copy

        +
      2. +
      3. +

        Two +Last body copy

        +
      4. +
      \ No newline at end of file diff --git a/test/data/multiline_lists.md b/test/data/multiline_lists.md new file mode 100644 index 0000000..6251115 --- /dev/null +++ b/test/data/multiline_lists.md @@ -0,0 +1,5 @@ +1. One + First body copy + +2. Two + Last body copy From e69374af0d3bc849272c0a6eec09910f220d6136 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Mon, 29 Jan 2018 20:52:27 +0200 Subject: [PATCH 114/238] improve readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1f2b53..92d7202 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> You might also like [Caret](https://caret.io?ref=parsedown) - our Markdown editor for Mac / Windows / Linux. +> I also make [Caret](https://caret.io?ref=parsedown), a Markdown editor for Mac, Windows and Linux. ## Parsedown From e938ab4ffe74ebf84afa43d8875ab418fcfc8e4d Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Mon, 29 Jan 2018 20:54:40 +0200 Subject: [PATCH 115/238] improve readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92d7202..7bc3be1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> I also make [Caret](https://caret.io?ref=parsedown), a Markdown editor for Mac, Windows and Linux. +> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac, Windows and Linux. ## Parsedown From c999a4b61bfc15d3eed74a40ce814afe6faa21ea Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Mon, 29 Jan 2018 20:55:30 +0200 Subject: [PATCH 116/238] improve readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7bc3be1..76b6905 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac, Windows and Linux. +> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac and PC. ## Parsedown From ad62bf5a6fc25052949f4ebba29375363045a009 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Feb 2018 17:01:31 +0000 Subject: [PATCH 117/238] Talk about safe mode in the README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 76b6905..68f216e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,20 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t ### Security -Parsedown does not sanitize the HTML that it generates. When you deal with untrusted content (ex: user comments) you should also use a HTML sanitizer like [HTML Purifier](http://htmlpurifier.org/). +Parsedown is capable of escaping user-input within the HTML that it generates. +Additionally Parsedown can attempt to sanitize additional scriping vectors (such +as scripting link destinations). To tell Parsedown that it is processing untrusted +user input, use the following: +```php +$parsedown = new Parsedown; +$parsedown->setSafeMode(true); +``` + +It is recommended that when you deal with untrusted content (ex: user comments) +you should employ defense-in-depth measures, like making use of a HTML sanitizer +that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). +Additionally, you should strongly consider +[deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/). ### Questions From e2f3961f8092730980b0e09a8e78b66f81b62045 Mon Sep 17 00:00:00 2001 From: Hari KT Date: Wed, 28 Feb 2018 23:25:38 +0530 Subject: [PATCH 118/238] Add test case to make sure issue 232 no longer exists --- test/data/fenced_code_block.html | 6 +++++- test/data/fenced_code_block.md | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/data/fenced_code_block.html b/test/data/fenced_code_block.html index ce8dfd0..565a541 100644 --- a/test/data/fenced_code_block.html +++ b/test/data/fenced_code_block.html @@ -4,4 +4,8 @@ $message = 'fenced code block'; echo $message;
    tilde
    echo 'language identifier';
    -
    echo 'language identifier with non words';
    \ No newline at end of file +
    echo 'language identifier with non words';
    +
    <?php
    +echo "Hello World";
    +?>
    +<a href="http://auraphp.com" >Aura Project</a>
    \ No newline at end of file diff --git a/test/data/fenced_code_block.md b/test/data/fenced_code_block.md index 9176ef4..62db24a 100644 --- a/test/data/fenced_code_block.md +++ b/test/data/fenced_code_block.md @@ -15,4 +15,11 @@ echo 'language identifier'; ```c# echo 'language identifier with non words'; +``` + +```html+php + +Aura Project ``` \ No newline at end of file From 096e16475639a3e934777f21715ac8152a03d136 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Feb 2018 18:59:34 +0100 Subject: [PATCH 119/238] Update README.md Sort "Who uses it" alphabetically, add Laravel + Pico --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76b6905..2f65e56 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w **Who uses it?** -[phpDocumentor](http://www.phpdoc.org/), [October CMS](http://octobercms.com/), [Bolt CMS](http://bolt.cm/), [Kirby CMS](http://getkirby.com/), [Grav CMS](http://getgrav.org/), [Statamic CMS](http://www.statamic.com/), [Herbie CMS](http://www.getherbie.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). +[Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [Laravel](https://laravel.com/), [October CMS](http://octobercms.com/), [phpDocumentor](http://www.phpdoc.org/), [Pico](http://picocms.org), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). **How can I help?** From cc53d5ae2959b3ea946a7b4f79b82cd55c7225c8 Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Feb 2018 19:12:19 +0100 Subject: [PATCH 120/238] Travis: Issue build error when Parsedown::version isn't up-to-date --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index db5d725..b9361a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,3 +26,4 @@ install: script: - vendor/bin/phpunit - vendor/bin/phpunit test/CommonMarkTestWeak.php || true + - '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"Parsedown.php\"); echo Parsedown::version;")" ]' From fa89f0d743792dac266780d24f5ff7fe77c2ca6d Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Wed, 28 Feb 2018 20:42:25 +0100 Subject: [PATCH 121/238] Add mbstring dependency to composer.json --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index dc349d4..f8b40f8 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ } ], "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "ext-mbstring": "*" }, "require-dev": { "phpunit/phpunit": "^4.8.35" From 72d30d33bc126676bff7c13c27155bd3e45c3a9e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 13 Jun 2017 20:28:32 +0100 Subject: [PATCH 122/238] allow element to have no name --- Parsedown.php | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e0ce3ac..cd93e9c 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1460,26 +1460,33 @@ class Parsedown $Element = $this->sanitiseElement($Element); } - $markup = '<'.$Element['name']; + $hasName = isset($Element['name']); - if (isset($Element['attributes'])) + $markup = ''; + + if ($hasName) { - foreach ($Element['attributes'] as $name => $value) - { - if ($value === null) - { - continue; - } + $markup .= '<'.$Element['name']; - $markup .= ' '.$name.'="'.self::escape($value).'"'; + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.self::escape($value).'"'; + } } } if (isset($Element['text'])) { - $markup .= '>'; + $markup .= $hasName ? '>' : ''; - if (!isset($Element['nonNestables'])) + if (!isset($Element['nonNestables'])) { $Element['nonNestables'] = array(); } @@ -1493,9 +1500,9 @@ class Parsedown $markup .= self::escape($Element['text'], true); } - $markup .= ''; + $markup .= $hasName ? '' : ''; } - else + elseif ($hasName) { $markup .= ' />'; } From 90439ef882500d98f581e725833bec370ece6bb2 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 1 Mar 2018 18:44:11 +0000 Subject: [PATCH 123/238] Rewrite section --- README.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 68f216e..9bc3657 100644 --- a/README.md +++ b/README.md @@ -39,19 +39,37 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t ### Security Parsedown is capable of escaping user-input within the HTML that it generates. -Additionally Parsedown can attempt to sanitize additional scriping vectors (such -as scripting link destinations). To tell Parsedown that it is processing untrusted -user input, use the following: +Additionally Parsedown will apply sanitisation to additional scripting vectors (such +as scripting link destinations) that are introduced by the markdown syntax itself. +To tell Parsedown that it is processing untrusted user-input, use the following: ```php $parsedown = new Parsedown; $parsedown->setSafeMode(true); ``` -It is recommended that when you deal with untrusted content (ex: user comments) -you should employ defense-in-depth measures, like making use of a HTML sanitizer +If instead, you wish to allow HTML within untrusted user input, but still want +output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). -Additionally, you should strongly consider -[deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/). + +In both cases you should strongly consider employing defence-in-depth measures, +like [deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) +(making use of browser security feature) so that your page is likely to be safe even if an +attacker finds a vulnerability in one of the first lines of defence above. + +#### Security of Parsedown Extensions + +Safe mode does not necessarily yield safe results when using extensions to Parsedown. Extensions should be evaluated on their own to determine their specific safety against XSS. + +### Escaping HTML +> ⚠️  **WARNING:** This method isn't safe from XSS! + +If you wish to escape HTML **in trusted input**, you can use the following: +```php +$parsedown = new Parsedown; +$parsedown->setMarkupEscaped(true); +``` + +Beware that this still allows users to insert unsafe scripting vectors, such as links like `[xss](javascript:alert%281%29)`. ### Questions From 9b1f54b9d3bbe45b5a204a3a3c731fb5dccca695 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 1 Mar 2018 18:45:38 +0000 Subject: [PATCH 124/238] Lets be consistent with hyphenation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9bc3657..b67a886 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ $parsedown = new Parsedown; $parsedown->setSafeMode(true); ``` -If instead, you wish to allow HTML within untrusted user input, but still want +If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). From f3068df45a80f98e96666682423b025c51cf301d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 1 Mar 2018 19:54:58 +0000 Subject: [PATCH 125/238] Remove extra line breaks --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b67a886..e950bd2 100644 --- a/README.md +++ b/README.md @@ -38,23 +38,17 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t ### Security -Parsedown is capable of escaping user-input within the HTML that it generates. -Additionally Parsedown will apply sanitisation to additional scripting vectors (such -as scripting link destinations) that are introduced by the markdown syntax itself. +Parsedown is capable of escaping user-input within the HTML that it generates. Additionally Parsedown will apply sanitisation to additional scripting vectors (such as scripting link destinations) that are introduced by the markdown syntax itself. + To tell Parsedown that it is processing untrusted user-input, use the following: ```php $parsedown = new Parsedown; $parsedown->setSafeMode(true); ``` -If instead, you wish to allow HTML within untrusted user-input, but still want -output to be free from XSS it is recommended that you make use of a HTML sanitiser -that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). +If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). -In both cases you should strongly consider employing defence-in-depth measures, -like [deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) -(making use of browser security feature) so that your page is likely to be safe even if an -attacker finds a vulnerability in one of the first lines of defence above. +In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (making use of browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above. #### Security of Parsedown Extensions From 33b51eaefa8c0ef4d87b089493f5ed0df78f5108 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 2 Mar 2018 01:13:58 +0000 Subject: [PATCH 126/238] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b629e6..fc0419c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ $parsedown->setSafeMode(true); If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). -In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Secuity-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (making use of browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above. +In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Security-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (a browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above. #### Security of Parsedown Extensions From e5bf9560d72cd38fcc5ad73d3f1a76faa94f1c1e Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Fri, 2 Mar 2018 17:37:16 +0200 Subject: [PATCH 127/238] add Laravel to who uses it --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc0419c..ed47c11 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w **Who uses it?** -[Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [Laravel](https://laravel.com/), [October CMS](http://octobercms.com/), [phpDocumentor](http://www.phpdoc.org/), [Pico](http://picocms.org), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). +[Laravel](https://github.com/laravel/framework), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [Laravel](https://laravel.com/), [October CMS](http://octobercms.com/), [phpDocumentor](http://www.phpdoc.org/), [Pico](http://picocms.org), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). **How can I help?** From a18bf495edd81d68e30e6c5e7f9c602d4e503cc5 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Fri, 2 Mar 2018 17:40:21 +0200 Subject: [PATCH 128/238] refactor who uses it section in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed47c11..c6006d4 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w **Who uses it?** -[Laravel](https://github.com/laravel/framework), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [Laravel](https://laravel.com/), [October CMS](http://octobercms.com/), [phpDocumentor](http://www.phpdoc.org/), [Pico](http://picocms.org), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). +[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). **How can I help?** From 253822057aae085f7ae8c405905743b17d7b04c9 Mon Sep 17 00:00:00 2001 From: Emanuil Rusev Date: Fri, 2 Mar 2018 17:46:45 +0200 Subject: [PATCH 129/238] refactor who uses it section in readme a bit more --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6006d4..b5d9ed2 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w **Who uses it?** -[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Statamic CMS](http://www.statamic.com/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). +[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). **How can I help?** From ae7e8e50672dc5f16cf59097f7b147a0e26b2b55 Mon Sep 17 00:00:00 2001 From: "Luiz Paulo \"Bills" Date: Wed, 7 Mar 2018 21:51:35 -0300 Subject: [PATCH 130/238] bump version --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index e0ce3ac..e3f045f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.6.0'; + const version = '1.7.0'; # ~ From 98573341861280a80cca9b6223e1540de3f1d6fb Mon Sep 17 00:00:00 2001 From: "Luiz Paulo \"Bills" Date: Wed, 7 Mar 2018 22:04:55 -0300 Subject: [PATCH 131/238] bump version --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index e3f045f..87d612a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.7.0'; + const version = '1.7.1'; # ~ From f70d96479aa9ebca173c0e2829e8ee051f16c200 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 9 Mar 2018 16:48:32 +0000 Subject: [PATCH 132/238] Add test case for email surrounded by tags --- test/data/email.html | 3 ++- test/data/email.md | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/data/email.html b/test/data/email.html index c40759c..93e0705 100644 --- a/test/data/email.html +++ b/test/data/email.html @@ -1 +1,2 @@ -

    my email is me@example.com

    \ No newline at end of file +

    my email is me@example.com

    +

    html tags shouldn't start an email autolink first.last@example.com

    \ No newline at end of file diff --git a/test/data/email.md b/test/data/email.md index 26b7b6c..00b6969 100644 --- a/test/data/email.md +++ b/test/data/email.md @@ -1 +1,3 @@ -my email is \ No newline at end of file +my email is + +html tags shouldn't start an email autolink first.last@example.com \ No newline at end of file From 721b885dd3d569b64377bed548efeb743ce5cbf7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 9 Mar 2018 16:49:04 +0000 Subject: [PATCH 133/238] Fix #565 by validating email as defined in commonmark spec --- Parsedown.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 87d612a..685d6df 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1142,8 +1142,14 @@ class Parsedown protected function inlineEmailTag($Excerpt) { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) - { + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]' + .'(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?' + .'(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*' + ; + + if (strpos($Excerpt['text'], '>') !== false + and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ){ $url = $matches[1]; if ( ! isset($matches[2])) @@ -1479,7 +1485,7 @@ class Parsedown { $markup .= '>'; - if (!isset($Element['nonNestables'])) + if (!isset($Element['nonNestables'])) { $Element['nonNestables'] = array(); } From 19f1bb9353506f9c25c8b803333606cd6568cfc3 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 9 Mar 2018 16:54:21 +0000 Subject: [PATCH 134/238] Disable backtracking where the regex doesn't need it --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 685d6df..27a35bf 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1142,7 +1142,7 @@ class Parsedown protected function inlineEmailTag($Excerpt) { - $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]' + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@[a-zA-Z0-9]' .'(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?' .'(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*' ; From 6830c3339f70046183280ceaba83bcbc5c60634b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 9 Mar 2018 17:38:41 +0000 Subject: [PATCH 135/238] Readability Thanks @PhrozenByte for the suggestion :) --- Parsedown.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 27a35bf..c0dbf08 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1142,10 +1142,10 @@ class Parsedown protected function inlineEmailTag($Excerpt) { - $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@[a-zA-Z0-9]' - .'(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?' - .'(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*' - ; + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; if (strpos($Excerpt['text'], '>') !== false and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) From e6444bb57e60d56648ea60cf6b30f737c052e2c1 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 15 Mar 2018 10:42:29 +0000 Subject: [PATCH 136/238] Add unsafeHtml option for extensions to use on trusted input --- Parsedown.php | 21 +++++++++++++++++++-- test/ParsedownTest.php | 12 ++++++++++++ test/UnsafeExtension.php | 14 ++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/UnsafeExtension.php diff --git a/Parsedown.php b/Parsedown.php index 2725170..b274f52 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1488,7 +1488,20 @@ class Parsedown } } + $unsafeHtml = false; if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['unsafeHtml']) and !$this->safeMode) + { + $text = $Element['unsafeHtml']; + $unsafeHtml = true; + } + + if (isset($text)) { $markup .= $hasName ? '>' : ''; @@ -1499,11 +1512,15 @@ class Parsedown if (isset($Element['handler'])) { - $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']); + $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); + } + elseif ($unsafeHtml !== true or $this->safeMode) + { + $markup .= self::escape($text, true); } else { - $markup .= self::escape($Element['text'], true); + $markup .= $text; } $markup .= $hasName ? '' : ''; diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index c28cedf..3cd796e 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -1,4 +1,5 @@ assertEquals($expectedMarkup, $actualMarkup); } + function testUnsafeHtml() + { + $markdown = "```php\nfoobar\n```"; + $expectedMarkup = '

    foobar

    '; + + $unsafeExtension = new UnsafeExtension; + $actualMarkup = $unsafeExtension->text($markdown); + + $this->assertEquals($expectedMarkup, $actualMarkup); + } + function data() { $data = array(); diff --git a/test/UnsafeExtension.php b/test/UnsafeExtension.php new file mode 100644 index 0000000..f2343c4 --- /dev/null +++ b/test/UnsafeExtension.php @@ -0,0 +1,14 @@ +$text

    "; + + return $Block; + } +} From e4c5be026d4c776261edf5e8ef802e258bfaee9f Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 15 Mar 2018 11:00:03 +0000 Subject: [PATCH 137/238] Further attempt to dissuade this feature's use --- test/UnsafeExtension.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/UnsafeExtension.php b/test/UnsafeExtension.php index f2343c4..9a8dcc7 100644 --- a/test/UnsafeExtension.php +++ b/test/UnsafeExtension.php @@ -7,6 +7,11 @@ class UnsafeExtension extends Parsedown $text = $Block['element']['text']['text']; unset($Block['element']['text']['text']); + // WARNING: There is almost always a better way of doing things! + // + // This example is one of them, unsafe behaviour is NOT needed here. + // Only use this if you trust the input and have no idea what + // the output HTML will look like (e.g. using an external parser). $Block['element']['text']['unsafeHtml'] = "

    $text

    "; return $Block; From ef7ed7b66cf22b268c459a98e4fe3f7f809d40b5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 15 Mar 2018 11:09:55 +0000 Subject: [PATCH 138/238] Still grab the text if safe mode enabled, but output it escaped --- Parsedown.php | 3 ++- test/ParsedownTest.php | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index b274f52..9558525 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1495,9 +1495,10 @@ class Parsedown } // very strongly consider an alternative if you're writing an // extension - elseif (isset($Element['unsafeHtml']) and !$this->safeMode) + elseif (isset($Element['unsafeHtml'])) { $text = $Element['unsafeHtml']; + $unsafeHtml = true; } diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 3cd796e..8f3e6c8 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -60,11 +60,17 @@ class ParsedownTest extends TestCase { $markdown = "```php\nfoobar\n```"; $expectedMarkup = '

    foobar

    '; + $expectedSafeMarkup = '
    <p>foobar</p>
    '; $unsafeExtension = new UnsafeExtension; $actualMarkup = $unsafeExtension->text($markdown); $this->assertEquals($expectedMarkup, $actualMarkup); + + $unsafeExtension->setSafeMode(true); + $actualSafeMarkup = $unsafeExtension->text($markdown); + + $this->assertEquals($expectedSafeMarkup, $actualSafeMarkup); } function data() From 3fc54bc966caea29a633dba41ebb6728a917ee67 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 15 Mar 2018 19:46:03 +0000 Subject: [PATCH 139/238] Allow extension to "vouch" for raw HTML they produce Rename "unsafeHtml" to "rawHtml" --- Parsedown.php | 25 ++++++++++++++++++++----- test/ParsedownTest.php | 21 +++++++++++++++++++-- test/SampleExtensions.php | 39 +++++++++++++++++++++++++++++++++++++++ test/UnsafeExtension.php | 19 ------------------- 4 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 test/SampleExtensions.php delete mode 100644 test/UnsafeExtension.php diff --git a/Parsedown.php b/Parsedown.php index 9558525..160594e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1488,18 +1488,33 @@ class Parsedown } } - $unsafeHtml = false; + $permitRawHtml = false; + if (isset($Element['text'])) { $text = $Element['text']; } // very strongly consider an alternative if you're writing an // extension - elseif (isset($Element['unsafeHtml'])) + elseif (isset($Element['rawHtml'])) { - $text = $Element['unsafeHtml']; + $text = $Element['rawHtml']; - $unsafeHtml = true; + $allowRawHtmlInSafeMode = false; + + if (isset($Element['allowRawHtmlInSafeMode'])) + { + $allowRawHtmlInSafeMode = (true === $Element['allowRawHtmlInSafeMode']); + } + + if ($this->safeMode !== true) + { + $permitRawHtml = true; + } + elseif ($this->safeMode and $allowRawHtmlInSafeMode) + { + $permitRawHtml = true; + } } if (isset($text)) @@ -1515,7 +1530,7 @@ class Parsedown { $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); } - elseif ($unsafeHtml !== true or $this->safeMode) + elseif ($permitRawHtml !== true) { $markup .= self::escape($text, true); } diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 8f3e6c8..cc0cc1d 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -1,5 +1,5 @@ assertEquals($expectedMarkup, $actualMarkup); } - function testUnsafeHtml() + function testRawHtml() { $markdown = "```php\nfoobar\n```"; $expectedMarkup = '

    foobar

    '; @@ -73,6 +73,23 @@ class ParsedownTest extends TestCase $this->assertEquals($expectedSafeMarkup, $actualSafeMarkup); } + function testTrustDelegatedRawHtml() + { + $markdown = "```php\nfoobar\n```"; + $expectedMarkup = '

    foobar

    '; + $expectedSafeMarkup = $expectedMarkup; + + $unsafeExtension = new TrustDelegatedExtension; + $actualMarkup = $unsafeExtension->text($markdown); + + $this->assertEquals($expectedMarkup, $actualMarkup); + + $unsafeExtension->setSafeMode(true); + $actualSafeMarkup = $unsafeExtension->text($markdown); + + $this->assertEquals($expectedSafeMarkup, $actualSafeMarkup); + } + function data() { $data = array(); diff --git a/test/SampleExtensions.php b/test/SampleExtensions.php new file mode 100644 index 0000000..6d7ec9f --- /dev/null +++ b/test/SampleExtensions.php @@ -0,0 +1,39 @@ +$text

    "; + + return $Block; + } +} + + +class TrustDelegatedExtension extends Parsedown +{ + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + unset($Block['element']['text']['text']); + + // WARNING: There is almost always a better way of doing things! + // + // This example is one of them, unsafe behaviour is NOT needed here. + // Only use this if you trust the input and have no idea what + // the output HTML will look like (e.g. using an external parser). + $Block['element']['text']['rawHtml'] = "

    $text

    "; + $Block['element']['text']['allowRawHtmlInSafeMode'] = true; + + return $Block; + } +} diff --git a/test/UnsafeExtension.php b/test/UnsafeExtension.php deleted file mode 100644 index 9a8dcc7..0000000 --- a/test/UnsafeExtension.php +++ /dev/null @@ -1,19 +0,0 @@ -$text

    "; - - return $Block; - } -} From 624a08b7eb8c3a4b0b99e7175251b76483e387cf Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 15 Mar 2018 19:55:33 +0000 Subject: [PATCH 140/238] Update commment --- test/SampleExtensions.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/SampleExtensions.php b/test/SampleExtensions.php index 6d7ec9f..c66190c 100644 --- a/test/SampleExtensions.php +++ b/test/SampleExtensions.php @@ -28,9 +28,10 @@ class TrustDelegatedExtension extends Parsedown // WARNING: There is almost always a better way of doing things! // - // This example is one of them, unsafe behaviour is NOT needed here. - // Only use this if you trust the input and have no idea what - // the output HTML will look like (e.g. using an external parser). + // This behaviour is NOT needed in the demonstrated case. + // Only use this if you are sure that the result being added into + // rawHtml is safe. + // (e.g. using an external parser with escaping capabilities). $Block['element']['text']['rawHtml'] = "

    $text

    "; $Block['element']['text']['allowRawHtmlInSafeMode'] = true; From 88dc94989039d37e09f186e94ec3a3964af82b36 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 19:42:14 +0000 Subject: [PATCH 141/238] Refactor based on suggestion by @PhrozenByte --- Parsedown.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 160594e..bf08700 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1500,21 +1500,8 @@ class Parsedown { $text = $Element['rawHtml']; - $allowRawHtmlInSafeMode = false; - - if (isset($Element['allowRawHtmlInSafeMode'])) - { - $allowRawHtmlInSafeMode = (true === $Element['allowRawHtmlInSafeMode']); - } - - if ($this->safeMode !== true) - { - $permitRawHtml = true; - } - elseif ($this->safeMode and $allowRawHtmlInSafeMode) - { - $permitRawHtml = true; - } + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; } if (isset($text)) @@ -1530,7 +1517,7 @@ class Parsedown { $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); } - elseif ($permitRawHtml !== true) + elseif (!$permitRawHtml) { $markup .= self::escape($text, true); } From 972648ff64228ee4c775febef962e863d16c853e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 20 Mar 2018 16:56:40 +0100 Subject: [PATCH 142/238] Added inline example to README see https://github.com/erusev/parsedown/issues/562 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b5d9ed2..9a4bb36 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Include `Parsedown.php` or install [the composer package](https://packagist.org/ $Parsedown = new Parsedown(); echo $Parsedown->text('Hello _Parsedown_!'); # prints:

    Hello Parsedown!

    +// you can also parse inline markdown only +echo $Parsedown->line('Hello _Parsedown_!'); # prints: Hello Parsedown! ``` More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI). From eb55e426b9da828237603633bb251ecd573bb879 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 21 Mar 2018 02:18:34 +0000 Subject: [PATCH 143/238] Initial refactor to use AST --- Parsedown.php | 60 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 11e50d5..0cdfb13 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -142,6 +142,11 @@ class Parsedown # protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) { $CurrentBlock = null; @@ -284,7 +289,7 @@ class Parsedown # ~ - $markup = ''; + $Elements = array(); foreach ($Blocks as $Block) { @@ -293,15 +298,12 @@ class Parsedown continue; } - $markup .= "\n"; - $markup .= $this->element($Block['element']); + $Elements[] = $Block['element']; } - $markup .= "\n"; - # ~ - return $markup; + return $Elements; } protected function isBlockContinuable($Type) @@ -386,7 +388,10 @@ class Parsedown if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') { $Block = array( - 'element' => array('rawHtml' => $Line['body']), + 'element' => array( + 'rawHtml' => $Line['body'], + 'autobreak' => true, + ), ); if (preg_match('/-->$/', $Line['text'])) @@ -776,7 +781,10 @@ class Parsedown $Block = array( 'name' => $matches[1], - 'element' => array('rawHtml' => $Line['text']), + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), ); return $Block; @@ -1045,7 +1053,12 @@ class Parsedown public function line($text, $nonNestables=array()) { - $markup = ''; + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = array()) + { + $Elements = array(); # $excerpt is based on the first occurrence of a marker @@ -1098,10 +1111,11 @@ class Parsedown $unmarkedText = substr($text, 0, $Inline['position']); # compile the unmarked text - $markup .= $this->unmarkedText($unmarkedText); + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; # compile the inline - $markup .= $this->element($Inline['element']); + $Elements[] = $Inline['element']; # remove the examined text $text = substr($text, $Inline['position'] + $Inline['extent']); @@ -1113,14 +1127,25 @@ class Parsedown $unmarkedText = substr($text, 0, $markerPosition + 1); - $markup .= $this->unmarkedText($unmarkedText); + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; $text = substr($text, $markerPosition + 1); } - $markup .= $this->unmarkedText($text); + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; - return $markup; + $Elements = array_map( + function ($Element) { + $Element['autobreak'] = isset($Element['autobreak']) + ? $Element['autobreak'] : false; + return $Element; + }, + $Elements + ); + + return $Elements; } # @@ -1568,11 +1593,14 @@ class Parsedown foreach ($Elements as $Element) { + $autoBreakNext = (isset($Element['name']) + || isset($Element['autobreak']) && $Element['autobreak'] + ); // (autobreak === false) covers both sides of an element - $autoBreak = !$autoBreak ? $autoBreak : isset($Element['name']); + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); - $autoBreak = isset($Element['name']); + $autoBreak = $autoBreakNext; } $markup .= $autoBreak ? "\n" : ''; From 098f24d189d17f6cadfdb135de6aa241576f3e35 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 21 Mar 2018 02:32:01 +0000 Subject: [PATCH 144/238] Seperate handler delegation from AST This also splits 'text' into 'text', 'elements', and 'element' to hopefully better communicate structure --- Parsedown.php | 221 +++++++++++++++++------------- test/SampleExtensions.php | 14 +- test/data/deeply_nested_list.html | 36 ++--- 3 files changed, 153 insertions(+), 118 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0cdfb13..4feeb38 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -260,9 +260,13 @@ class Parsedown # ~ - if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) - { - $CurrentBlock['element']['text'] .= "\n".$text; + if ( + isset($CurrentBlock) + and isset($CurrentBlock['element']['name']) + and $CurrentBlock['element']['name'] === 'p' + and ! isset($CurrentBlock['interrupted']) + ) { + $CurrentBlock['element']['elements'] .= "\n".$text; } else { @@ -333,8 +337,7 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'pre', - 'handler' => 'element', - 'text' => array( + 'element' => array( 'name' => 'code', 'text' => $text, ), @@ -351,16 +354,16 @@ class Parsedown { if (isset($Block['interrupted'])) { - $Block['element']['text']['text'] .= "\n"; + $Block['element']['element']['text'] .= "\n"; unset($Block['interrupted']); } - $Block['element']['text']['text'] .= "\n"; + $Block['element']['element']['text'] .= "\n"; $text = substr($Line['body'], 4); - $Block['element']['text']['text'] .= $text; + $Block['element']['element']['text'] .= $text; return $Block; } @@ -368,9 +371,9 @@ class Parsedown protected function blockCodeComplete($Block) { - $text = $Block['element']['text']['text']; + $text = $Block['element']['element']['text']; - $Block['element']['text']['text'] = $text; + $Block['element']['element']['text'] = $text; return $Block; } @@ -445,8 +448,7 @@ class Parsedown 'char' => $Line['text'][0], 'element' => array( 'name' => 'pre', - 'handler' => 'element', - 'text' => $Element, + 'element' => $Element, ), ); @@ -463,30 +465,30 @@ class Parsedown if (isset($Block['interrupted'])) { - $Block['element']['text']['text'] .= "\n"; + $Block['element']['element']['text'] .= "\n"; unset($Block['interrupted']); } if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) { - $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); $Block['complete'] = true; return $Block; } - $Block['element']['text']['text'] .= "\n".$Line['body']; + $Block['element']['element']['text'] .= "\n".$Line['body']; return $Block; } protected function blockFencedCodeComplete($Block) { - $text = $Block['element']['text']['text']; + $text = $Block['element']['element']['text']; - $Block['element']['text']['text'] = $text; + $Block['element']['element']['text'] = $text; return $Block; } @@ -516,8 +518,8 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'h' . min(6, $level), - 'text' => $text, - 'handler' => 'line', + 'elements' => $text, + 'handler' => 'lineElements' ), ); @@ -557,7 +559,6 @@ class Parsedown ), 'element' => array( 'name' => $name, - 'handler' => 'elements', ), ); @@ -574,10 +575,10 @@ class Parsedown $Block['li'] = array( 'name' => 'li', 'handler' => 'li', - 'text' => !empty($matches[3]) ? array($matches[3]) : array(), + 'elements' => !empty($matches[3]) ? array($matches[3]) : array(), ); - $Block['element']['text'] []= & $Block['li']; + $Block['element']['elements'] []= & $Block['li']; return $Block; } @@ -585,7 +586,7 @@ class Parsedown protected function blockListContinue($Line, array $Block) { - if (isset($Block['interrupted']) and empty($Block['li']['text'])) + if (isset($Block['interrupted']) and empty($Block['li']['elements'])) { return null; } @@ -605,7 +606,7 @@ class Parsedown ) { if (isset($Block['interrupted'])) { - $Block['li']['text'] []= ''; + $Block['li']['elements'] []= ''; $Block['loose'] = true; @@ -621,12 +622,12 @@ class Parsedown $Block['li'] = array( 'name' => 'li', 'handler' => 'li', - 'text' => array( + 'elements' => array( $text, ), ); - $Block['element']['text'] []= & $Block['li']; + $Block['element']['elements'] []= & $Block['li']; return $Block; } @@ -644,14 +645,16 @@ class Parsedown { if (isset($Block['interrupted'])) { - $Block['li']['text'] []= ''; + $Block['li']['elements'] []= ''; + + $Block['loose'] = true; unset($Block['interrupted']); } $text = substr($Line['body'], $requiredIndent); - $Block['li']['text'] []= $text; + $Block['li']['elements'] []= $text; return $Block; } @@ -660,7 +663,7 @@ class Parsedown { $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); - $Block['li']['text'] []= $text; + $Block['li']['elements'] []= $text; return $Block; } @@ -670,11 +673,11 @@ class Parsedown { if (isset($Block['loose'])) { - foreach ($Block['element']['text'] as &$li) + foreach ($Block['element']['elements'] as &$li) { - if (end($li['text']) !== '') + if (end($li['elements']) !== '') { - $li['text'] []= ''; + $li['elements'] []= ''; } } } @@ -692,8 +695,8 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'blockquote', - 'handler' => 'lines', - 'text' => (array) $matches[1], + 'handler' => 'linesElements', + 'elements' => (array) $matches[1], ), ); @@ -710,14 +713,14 @@ class Parsedown if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { - $Block['element']['text'] []= $matches[1]; + $Block['element']['elements'] []= $matches[1]; return $Block; } if ( ! isset($Block['interrupted'])) { - $Block['element']['text'] []= $Line['text']; + $Block['element']['elements'] []= $Line['text']; return $Block; } @@ -732,7 +735,7 @@ class Parsedown { $Block = array( 'element' => array( - 'name' => 'hr' + 'name' => 'hr', ), ); @@ -843,7 +846,7 @@ class Parsedown } if ( - strpos($Block['element']['text'], '|') === false + strpos($Block['element']['elements'], '|') === false and strpos($Line['text'], '|') === false and strpos($Line['text'], ':') === false ) { @@ -892,7 +895,7 @@ class Parsedown $HeaderElements = array(); - $header = $Block['element']['text']; + $header = $Block['element']['elements']; $header = trim($header); $header = trim($header, '|'); @@ -910,8 +913,8 @@ class Parsedown $HeaderElement = array( 'name' => 'th', - 'text' => $headerCell, - 'handler' => 'line', + 'elements' => $headerCell, + 'handler' => 'lineElements', ); if (isset($alignments[$index])) @@ -933,25 +936,21 @@ class Parsedown 'identified' => true, 'element' => array( 'name' => 'table', - 'handler' => 'elements', ), ); - $Block['element']['text'] []= array( + $Block['element']['elements'] []= array( 'name' => 'thead', - 'handler' => 'elements', ); - $Block['element']['text'] []= array( + $Block['element']['elements'] []= array( 'name' => 'tbody', - 'handler' => 'elements', - 'text' => array(), + 'elements' => array(), ); - $Block['element']['text'][0]['text'] []= array( + $Block['element']['elements'][0]['elements'] []= array( 'name' => 'tr', - 'handler' => 'elements', - 'text' => $HeaderElements, + 'elements' => $HeaderElements, ); return $Block; @@ -983,8 +982,8 @@ class Parsedown $Element = array( 'name' => 'td', - 'handler' => 'line', - 'text' => $cell, + 'handler' => 'lineElements', + 'elements' => $cell, ); if (isset($Block['alignments'][$index])) @@ -999,11 +998,10 @@ class Parsedown $Element = array( 'name' => 'tr', - 'handler' => 'elements', - 'text' => $Elements, + 'elements' => $Elements, ); - $Block['element']['text'][1]['text'] []= $Element; + $Block['element']['elements'][1]['elements'] []= $Element; return $Block; } @@ -1018,8 +1016,8 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'p', - 'text' => $Line['text'], - 'handler' => 'line', + 'elements' => $Line['text'], + 'handler' => 'lineElements', ), ); @@ -1157,13 +1155,13 @@ class Parsedown $Inline = array( 'extent' => strlen($text), 'element' => array( - 'handler' => 'elements', + 'elements' => array(), ), ); if ($this->breaksEnabled) { - $Inline['element']['text'] = self::pregReplaceElements( + $Inline['element']['elements'] = self::pregReplaceElements( '/[ ]*\n/', array( array('name' => 'br'), @@ -1174,7 +1172,7 @@ class Parsedown } else { - $Inline['element']['text'] = self::pregReplaceElements( + $Inline['element']['elements'] = self::pregReplaceElements( '/(?:[ ][ ]+|[ ]*\\\\)\n/', array( array('name' => 'br'), @@ -1262,8 +1260,8 @@ class Parsedown 'extent' => strlen($matches[0]), 'element' => array( 'name' => $emphasis, - 'handler' => 'line', - 'text' => $matches[1], + 'handler' => 'lineElements', + 'elements' => $matches[1], ), ); } @@ -1301,8 +1299,9 @@ class Parsedown 'name' => 'img', 'attributes' => array( 'src' => $Link['element']['attributes']['href'], - 'alt' => $Link['element']['text'], + 'alt' => $Link['element']['elements'], ), + 'autobreak' => true, ), ); @@ -1317,9 +1316,9 @@ class Parsedown { $Element = array( 'name' => 'a', - 'handler' => 'line', + 'handler' => 'lineElements', 'nonNestables' => array('Url', 'Link'), - 'text' => null, + 'elements' => null, 'attributes' => array( 'href' => null, 'title' => null, @@ -1332,7 +1331,7 @@ class Parsedown if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { - $Element['text'] = $matches[1]; + $Element['elements'] = $matches[1]; $extent += strlen($matches[0]); @@ -1358,14 +1357,14 @@ class Parsedown { if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { - $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strlen($matches[1]) ? $matches[1] : $Element['elements']; $definition = strtolower($definition); $extent += strlen($matches[0]); } else { - $definition = strtolower($Element['text']); + $definition = strtolower($Element['elements']); } if ( ! isset($this->DefinitionData['Reference'][$definition])) @@ -1443,8 +1442,8 @@ class Parsedown 'extent' => strlen($matches[0]), 'element' => array( 'name' => 'del', - 'text' => $matches[1], - 'handler' => 'line', + 'elements' => $matches[1], + 'handler' => 'lineElements', ), ); } @@ -1508,6 +1507,41 @@ class Parsedown # Handlers # + protected function handle(array $Element) + { + $hasContent = isset($Element['text']) || isset($Element['element']) || isset($Element['elements']); + + if (isset($Element['handler']) and $hasContent) + { + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->{$Element['handler']}($Element['elements'], $Element['nonNestables']); + + $Element['elements'] = array_map(array($this, 'handle'), $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->{$Element['handler']}($Element['element'], $Element['nonNestables']); + + $Element['element'] = $this->handle($Element['element']); + } + else + { + $Element['text'] = $this->{$Element['handler']}($Element['text'], $Element['nonNestables']); + } + } + + unset($Element['handler']); + + return $Element; + } + + protected function element(array $Element) { if ($this->safeMode) @@ -1515,6 +1549,8 @@ class Parsedown $Element = $this->sanitiseElement($Element); } + $Element = $this->handle($Element); + $hasName = isset($Element['name']); $markup = ''; @@ -1553,26 +1589,30 @@ class Parsedown $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; } - if (isset($text)) + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) { $markup .= $hasName ? '>' : ''; - if (!isset($Element['nonNestables'])) + if (isset($Element['elements'])) { - $Element['nonNestables'] = array(); + $markup .= $this->elements($Element['elements']); } - - if (isset($Element['handler'])) + elseif (isset($Element['element'])) { - $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); - } - elseif (!$permitRawHtml) - { - $markup .= self::escape($text, true); + $markup .= $this->element($Element['element']); } else { - $markup .= $text; + if (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } } $markup .= $hasName ? '' : ''; @@ -1612,21 +1652,16 @@ class Parsedown protected function li($lines) { - $markup = $this->lines($lines); + $Elements = $this->linesElements($lines); - $trimmedMarkup = trim($markup); - - if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

    ') - { - $markup = $trimmedMarkup; - $markup = substr($markup, 3); - - $position = strpos($markup, "

    "); - - $markup = substr_replace($markup, '', $position, 4); + if ( ! in_array('', $lines) + and isset($Elements[0]) and isset($Elements[0]['name']) + and $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); } - return $markup; + return $Elements; } # diff --git a/test/SampleExtensions.php b/test/SampleExtensions.php index c66190c..e855c71 100644 --- a/test/SampleExtensions.php +++ b/test/SampleExtensions.php @@ -4,15 +4,15 @@ class UnsafeExtension extends Parsedown { protected function blockFencedCodeComplete($Block) { - $text = $Block['element']['text']['text']; - unset($Block['element']['text']['text']); + $text = $Block['element']['element']['text']; + unset($Block['element']['element']['text']); // WARNING: There is almost always a better way of doing things! // // This example is one of them, unsafe behaviour is NOT needed here. // Only use this if you trust the input and have no idea what // the output HTML will look like (e.g. using an external parser). - $Block['element']['text']['rawHtml'] = "

    $text

    "; + $Block['element']['element']['rawHtml'] = "

    $text

    "; return $Block; } @@ -23,8 +23,8 @@ class TrustDelegatedExtension extends Parsedown { protected function blockFencedCodeComplete($Block) { - $text = $Block['element']['text']['text']; - unset($Block['element']['text']['text']); + $text = $Block['element']['element']['text']; + unset($Block['element']['element']['text']); // WARNING: There is almost always a better way of doing things! // @@ -32,8 +32,8 @@ class TrustDelegatedExtension extends Parsedown // Only use this if you are sure that the result being added into // rawHtml is safe. // (e.g. using an external parser with escaping capabilities). - $Block['element']['text']['rawHtml'] = "

    $text

    "; - $Block['element']['text']['allowRawHtmlInSafeMode'] = true; + $Block['element']['element']['rawHtml'] = "

    $text

    "; + $Block['element']['element']['allowRawHtmlInSafeMode'] = true; return $Block; } diff --git a/test/data/deeply_nested_list.html b/test/data/deeply_nested_list.html index bd2eb7f..96efd22 100644 --- a/test/data/deeply_nested_list.html +++ b/test/data/deeply_nested_list.html @@ -1,30 +1,30 @@
      -
    • li -
        -
      • li -
          +
        • li
            +
          • li
            • li
            • li
            • -
          • +
          +
        • li
        • -
      • +
      +
    • li

      -
    • level 1 -
        -
      • level 2 -
          -
        • level 3 -
            -
          • level 4 -
              +
            • level 1
                +
              • level 2
                  +
                • level 3
                    +
                  • level 4
                    • level 5
                    • -
                  • -
                • -
              • -
            • +
            +
          • +
          +
        • +
        +
      • +
      +

      From d5ded2b935ae62873594ca9f9b0acaa8ab3bf6cd Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 21 Mar 2018 16:02:57 +0000 Subject: [PATCH 145/238] Decouple handler argument from structure keys --- Parsedown.php | 129 ++++++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 4feeb38..60aff75 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -266,7 +266,7 @@ class Parsedown and $CurrentBlock['element']['name'] === 'p' and ! isset($CurrentBlock['interrupted']) ) { - $CurrentBlock['element']['elements'] .= "\n".$text; + $CurrentBlock['element']['handler']['argument'] .= "\n".$text; } else { @@ -518,8 +518,11 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'h' . min(6, $level), - 'elements' => $text, - 'handler' => 'lineElements' + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) ), ); @@ -559,6 +562,7 @@ class Parsedown ), 'element' => array( 'name' => $name, + 'elements' => array(), ), ); @@ -574,8 +578,11 @@ class Parsedown $Block['li'] = array( 'name' => 'li', - 'handler' => 'li', - 'elements' => !empty($matches[3]) ? array($matches[3]) : array(), + 'handler' => array( + 'function' => 'li', + 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), + 'destination' => 'elements' + ) ); $Block['element']['elements'] []= & $Block['li']; @@ -586,7 +593,7 @@ class Parsedown protected function blockListContinue($Line, array $Block) { - if (isset($Block['interrupted']) and empty($Block['li']['elements'])) + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) { return null; } @@ -606,7 +613,7 @@ class Parsedown ) { if (isset($Block['interrupted'])) { - $Block['li']['elements'] []= ''; + $Block['li']['handler']['argument'] []= ''; $Block['loose'] = true; @@ -621,10 +628,11 @@ class Parsedown $Block['li'] = array( 'name' => 'li', - 'handler' => 'li', - 'elements' => array( - $text, - ), + 'handler' => array( + 'function' => 'li', + 'argument' => array($text), + 'destination' => 'elements' + ) ); $Block['element']['elements'] []= & $Block['li']; @@ -645,7 +653,7 @@ class Parsedown { if (isset($Block['interrupted'])) { - $Block['li']['elements'] []= ''; + $Block['li']['handler']['argument'] []= ''; $Block['loose'] = true; @@ -654,7 +662,7 @@ class Parsedown $text = substr($Line['body'], $requiredIndent); - $Block['li']['elements'] []= $text; + $Block['li']['handler']['argument'] []= $text; return $Block; } @@ -663,7 +671,7 @@ class Parsedown { $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); - $Block['li']['elements'] []= $text; + $Block['li']['handler']['argument'] []= $text; return $Block; } @@ -675,9 +683,9 @@ class Parsedown { foreach ($Block['element']['elements'] as &$li) { - if (end($li['elements']) !== '') + if (end($li['handler']['argument']) !== '') { - $li['elements'] []= ''; + $li['handler']['argument'] []= ''; } } } @@ -695,8 +703,11 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'blockquote', - 'handler' => 'linesElements', - 'elements' => (array) $matches[1], + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ) ), ); @@ -713,14 +724,14 @@ class Parsedown if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { - $Block['element']['elements'] []= $matches[1]; + $Block['element']['handler']['argument'] []= $matches[1]; return $Block; } if ( ! isset($Block['interrupted'])) { - $Block['element']['elements'] []= $Line['text']; + $Block['element']['handler']['argument'] []= $Line['text']; return $Block; } @@ -846,7 +857,7 @@ class Parsedown } if ( - strpos($Block['element']['elements'], '|') === false + strpos($Block['element']['handler']['argument'], '|') === false and strpos($Line['text'], '|') === false and strpos($Line['text'], ':') === false ) { @@ -895,7 +906,7 @@ class Parsedown $HeaderElements = array(); - $header = $Block['element']['elements']; + $header = $Block['element']['handler']['argument']; $header = trim($header); $header = trim($header, '|'); @@ -913,8 +924,11 @@ class Parsedown $HeaderElement = array( 'name' => 'th', - 'elements' => $headerCell, - 'handler' => 'lineElements', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ) ); if (isset($alignments[$index])) @@ -936,6 +950,7 @@ class Parsedown 'identified' => true, 'element' => array( 'name' => 'table', + 'elements' => array(), ), ); @@ -982,8 +997,11 @@ class Parsedown $Element = array( 'name' => 'td', - 'handler' => 'lineElements', - 'elements' => $cell, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) ); if (isset($Block['alignments'][$index])) @@ -1016,8 +1034,11 @@ class Parsedown $Block = array( 'element' => array( 'name' => 'p', - 'elements' => $Line['text'], - 'handler' => 'lineElements', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ) ), ); @@ -1260,8 +1281,11 @@ class Parsedown 'extent' => strlen($matches[0]), 'element' => array( 'name' => $emphasis, - 'handler' => 'lineElements', - 'elements' => $matches[1], + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) ), ); } @@ -1299,7 +1323,7 @@ class Parsedown 'name' => 'img', 'attributes' => array( 'src' => $Link['element']['attributes']['href'], - 'alt' => $Link['element']['elements'], + 'alt' => $Link['element']['handler']['argument'], ), 'autobreak' => true, ), @@ -1316,9 +1340,12 @@ class Parsedown { $Element = array( 'name' => 'a', - 'handler' => 'lineElements', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), 'nonNestables' => array('Url', 'Link'), - 'elements' => null, 'attributes' => array( 'href' => null, 'title' => null, @@ -1331,7 +1358,7 @@ class Parsedown if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { - $Element['elements'] = $matches[1]; + $Element['handler']['argument'] = $matches[1]; $extent += strlen($matches[0]); @@ -1357,14 +1384,14 @@ class Parsedown { if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { - $definition = strlen($matches[1]) ? $matches[1] : $Element['elements']; + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; $definition = strtolower($definition); $extent += strlen($matches[0]); } else { - $definition = strtolower($Element['elements']); + $definition = strtolower($Element['handler']['argument']); } if ( ! isset($this->DefinitionData['Reference'][$definition])) @@ -1442,8 +1469,11 @@ class Parsedown 'extent' => strlen($matches[0]), 'element' => array( 'name' => 'del', - 'elements' => $matches[1], - 'handler' => 'lineElements', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) ), ); } @@ -1509,31 +1539,18 @@ class Parsedown protected function handle(array $Element) { - $hasContent = isset($Element['text']) || isset($Element['element']) || isset($Element['elements']); - - if (isset($Element['handler']) and $hasContent) + if (isset($Element['handler'])) { if (!isset($Element['nonNestables'])) { $Element['nonNestables'] = array(); } - if (isset($Element['elements'])) - { - $Element['elements'] = $this->{$Element['handler']}($Element['elements'], $Element['nonNestables']); + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; - $Element['elements'] = array_map(array($this, 'handle'), $Element['elements']); - } - elseif (isset($Element['element'])) - { - $Element['element'] = $this->{$Element['handler']}($Element['element'], $Element['nonNestables']); - - $Element['element'] = $this->handle($Element['element']); - } - else - { - $Element['text'] = $this->{$Element['handler']}($Element['text'], $Element['nonNestables']); - } + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); } unset($Element['handler']); From 1a47e74be1c31dca2781a77c9a93c6adf7312fd9 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:33:26 +0000 Subject: [PATCH 146/238] Quotes are permitted in escaped body --- test/ParsedownTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index 2f58418..01b4e25 100644 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -159,12 +159,12 @@ MARKDOWN_WITH_MARKUP;

      <div>content</div>

      sparse:

      <div> -<div class="inner"> +<div class="inner"> content </div> </div>

      paragraph

      -

      <style type="text/css"> +

      <style type="text/css"> p { color: red; } From 65d7bc5013d4f39d3fb8b4dfbe87f9627ced2d74 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:36:30 +0000 Subject: [PATCH 147/238] Special casing for elements with no name --- Parsedown.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index d7337a5..715deee 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1489,12 +1489,18 @@ class Parsedown { $markup = ''; + $autoBreak = true; + foreach ($Elements as $Element) { - $markup .= "\n" . $this->element($Element); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : isset($Element['name']); + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = isset($Element['name']); } - $markup .= "\n"; + $markup .= $autoBreak ? "\n" : ''; return $markup; } @@ -1539,6 +1545,12 @@ class Parsedown 'img' => 'src', ); + if ( ! isset($Element['name'])) + { + unset($Element['attributes']); + return $Element; + } + if (isset($safeUrlNameToAtt[$Element['name']])) { $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); From adcba805022d380e99636b66ae882f40eb9baaef Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:37:40 +0000 Subject: [PATCH 148/238] Implement unmarked text via AST --- Parsedown.php | 80 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 715deee..79e3ba6 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1070,6 +1070,41 @@ class Parsedown # ~ # + protected function inlineText($text) + { + $Inline = array( + 'extent' => strlen($text), + 'element' => array( + 'handler' => 'elements', + ), + ); + + if ($this->breaksEnabled) + { + $Inline['element']['text'] = self::pregReplaceElements( + '/[ ]*\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + } + else + { + $Inline['element']['text'] = self::pregReplaceElements( + '/(?:[ ][ ]+|[ ]*\\\\)\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + } + + return $Inline; + } + protected function inlineCode($Excerpt) { $marker = $Excerpt['text'][0]; @@ -1391,17 +1426,7 @@ class Parsedown protected function unmarkedText($text) { - if ($this->breaksEnabled) - { - $text = preg_replace('/[ ]*\n/', "
      \n", $text); - } - else - { - $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
      \n", $text); - $text = str_replace(" \n", "\n", $text); - } - - return $text; + return $this->element($this->inlineText($text)['element']); } # @@ -1526,6 +1551,39 @@ class Parsedown return $markup; } + # + # AST Convenience + # + + /** + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. + */ + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = array(); + + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) + { + $offset = $matches[0][1]; + $before = substr($text, 0, $offset); + $after = substr($text, $offset + strlen($matches[0][0])); + + $newElements[] = array('text' => $before); + + foreach ($Elements as $Element) + { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = array('text' => $text); + + return $newElements; + } + # # Deprecated Methods # From 011465bca6a830400e357c919441c7fd03670a0e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:44:07 +0000 Subject: [PATCH 149/238] Use rawHtml to provide conditional escaping for specialChars --- Parsedown.php | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 79e3ba6..ec0f0a9 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -964,13 +964,11 @@ class Parsedown # protected $InlineTypes = array( - '"' => array('SpecialCharacter'), '!' => array('Image'), '&' => array('SpecialCharacter'), '*' => array('Emphasis'), ':' => array('Url'), - '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), - '>' => array('SpecialCharacter'), + '<' => array('UrlTag', 'EmailTag', 'Markup'), '[' => array('Link'), '_' => array('Emphasis'), '`' => array('Code'), @@ -980,7 +978,7 @@ class Parsedown # ~ - protected $inlineMarkerList = '!"*_&[:<>`~\\'; + protected $inlineMarkerList = '!*_&[:<`~\\'; # # ~ @@ -1337,23 +1335,15 @@ class Parsedown protected function inlineSpecialCharacter($Excerpt) { - if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + if (preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)) { return array( - 'markup' => '&', - 'extent' => 1, + 'element' => array('rawHtml' => '&'.$matches[1].';'), + 'extent' => strlen($matches[0]), ); } - $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); - - if (isset($SpecialCharacter[$Excerpt['text'][0]])) - { - return array( - 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', - 'extent' => 1, - ); - } + return; } protected function inlineStrikethrough($Excerpt) From 0205a4cbe640f0f5052974500d93c7b0404b7a15 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:46:08 +0000 Subject: [PATCH 150/238] Use rawHtml to provide conditional escaping on special chars --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index ec0f0a9..546beae 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1189,7 +1189,7 @@ class Parsedown if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { return array( - 'markup' => $Excerpt['text'][1], + 'element' => array('rawHtml' => $Excerpt['text'][1]), 'extent' => 2, ); } From 8c14c5c239d700c70832e84f744c0e469257acbc Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:46:55 +0000 Subject: [PATCH 151/238] Use rawHtml to provide conditional escaping for markup --- Parsedown.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 546beae..e261c2f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -386,7 +386,7 @@ class Parsedown if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') { $Block = array( - 'markup' => $Line['body'], + 'element' => array('rawHtml' => $Line['body']), ); if (preg_match('/-->$/', $Line['text'])) @@ -405,7 +405,7 @@ class Parsedown return; } - $Block['markup'] .= "\n" . $Line['body']; + $Block['element']['rawHtml'] .= "\n" . $Line['body']; if (preg_match('/-->$/', $Line['text'])) { @@ -734,7 +734,7 @@ class Parsedown $Block = array( 'name' => $matches[1], - 'markup' => $Line['text'], + 'element' => array('rawHtml' => $Line['text']), ); return $Block; @@ -748,7 +748,7 @@ class Parsedown return; } - $Block['markup'] .= "\n".$Line['body']; + $Block['element']['rawHtml'] .= "\n".$Line['body']; return $Block; } @@ -1311,7 +1311,7 @@ class Parsedown if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) { return array( - 'markup' => $matches[0], + 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), ); } @@ -1319,7 +1319,7 @@ class Parsedown if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { return array( - 'markup' => $matches[0], + 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), ); } @@ -1327,7 +1327,7 @@ class Parsedown if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) { return array( - 'markup' => $matches[0], + 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), ); } From e59fbd736d98db6031824c9b3402f184d6d46c3d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 22:48:52 +0000 Subject: [PATCH 152/238] Remove 'markup' key exception for outputting via AST --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e261c2f..0e82ed5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -294,7 +294,7 @@ class Parsedown } $markup .= "\n"; - $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + $markup .= $this->element($Block['element']); } $markup .= "\n"; @@ -1042,7 +1042,7 @@ class Parsedown $markup .= $this->unmarkedText($unmarkedText); # compile the inline - $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + $markup .= $this->element($Inline['element']); # remove the examined text $text = substr($text, $Inline['position'] + $Inline['extent']); From 1fa6b038af2eb7cc34f7989cab39c5c62ba201e1 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 18 Mar 2018 23:06:26 +0000 Subject: [PATCH 153/238] PHP 5.3 compat --- Parsedown.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 0e82ed5..e015be8 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1416,7 +1416,8 @@ class Parsedown protected function unmarkedText($text) { - return $this->element($this->inlineText($text)['element']); + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); } # From 913e04782f970bb8ff1fa1441aa127dfa2155867 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 25 Mar 2018 22:50:16 +0100 Subject: [PATCH 154/238] Add failing test cases to be fixed --- test/data/setext_header_spaces.html | 12 ++++++++++++ test/data/setext_header_spaces.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/data/setext_header_spaces.html create mode 100644 test/data/setext_header_spaces.md diff --git a/test/data/setext_header_spaces.html b/test/data/setext_header_spaces.html new file mode 100644 index 0000000..daf97c0 --- /dev/null +++ b/test/data/setext_header_spaces.html @@ -0,0 +1,12 @@ +

      trailing space

      +

      trailing space

      +

      leading and trailing space

      +

      leading and trailing space

      +

      1 leading space

      +

      1 leading space

      +

      3 leading spaces

      +

      3 leading spaces

      +

      too many leading spaces +==

      +

      too many leading spaces +--

      \ No newline at end of file diff --git a/test/data/setext_header_spaces.md b/test/data/setext_header_spaces.md new file mode 100644 index 0000000..4ac35bb --- /dev/null +++ b/test/data/setext_header_spaces.md @@ -0,0 +1,29 @@ +trailing space +== + +trailing space +-- + +leading and trailing space + == + +leading and trailing space + -- + +1 leading space + == + +1 leading space + -- + +3 leading spaces + == + +3 leading spaces + -- + +too many leading spaces + == + +too many leading spaces + -- \ No newline at end of file From f71bec00f4053854ebaf2b842724e35aec6cd5b6 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 25 Mar 2018 22:50:42 +0100 Subject: [PATCH 155/238] Fix space handling in setext headings --- Parsedown.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index bf08700..844a504 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -705,8 +705,10 @@ class Parsedown return; } - if (chop($Line['text'], $Line['text'][0]) === '') - { + if ( + chop(chop($Line['text'], ' '), $Line['text'][0]) === '' + and $Line['indent'] < 4 + ) { $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; return $Block; From e7fbbf537b36d2748bef4dddbff0f976d3e7cafe Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 26 Mar 2018 18:45:34 +0100 Subject: [PATCH 156/238] Add repo specific paths to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8a7996 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor/ From dd9f4036ee92e2d281b8f5b0501c3e9e3f34d11c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 26 Mar 2018 18:47:33 +0100 Subject: [PATCH 157/238] Add .gitignore to export ignore in .gitattribtutes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 001c45c..c1a5d9a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ # Ignore all tests for archive /test export-ignore /.gitattributes export-ignore +/.gitignore export-ignore /.travis.yml export-ignore /phpunit.xml.dist export-ignore From f594d4c18b686294ccc3e162ba0eb84c4b45fb3b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Mar 2018 11:20:04 +0100 Subject: [PATCH 158/238] Add more tests for CommonMark compliance --- test/data/deeply_nested_list.html | 28 ++++++++++++++++++++++++++++ test/data/deeply_nested_list.md | 22 +++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/test/data/deeply_nested_list.html b/test/data/deeply_nested_list.html index d2c7e5a..bd2eb7f 100644 --- a/test/data/deeply_nested_list.html +++ b/test/data/deeply_nested_list.html @@ -9,4 +9,32 @@
    • li
  • li
  • + +
    +
      +
    • level 1 +
        +
      • level 2 +
          +
        • level 3 +
            +
          • level 4 +
              +
            • level 5
            • +
          • +
        • +
      • +
    • +
    +
    +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d
    • +
    • e
    • +
    • f
    • +
    • g
    • +
    • h
    • +
    • i
    \ No newline at end of file diff --git a/test/data/deeply_nested_list.md b/test/data/deeply_nested_list.md index 76b7552..82f73b8 100644 --- a/test/data/deeply_nested_list.md +++ b/test/data/deeply_nested_list.md @@ -3,4 +3,24 @@ - li - li - li -- li \ No newline at end of file +- li + +--- + +- level 1 + - level 2 + - level 3 + - level 4 + - level 5 + +--- + +- a + - b + - c + - d + - e + - f + - g + - h +- i \ No newline at end of file From d2a73f917938e816fde895f417d5a1539d6b310e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Mar 2018 11:23:04 +0100 Subject: [PATCH 159/238] Trim whitespace --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 0d44669..b5fb584 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -558,7 +558,7 @@ class Parsedown if($name === 'ol') { $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; - + if($listStart !== '1') { $Block['element']['attributes'] = array('start' => $listStart); From 244ea0aaa692761dc89798d8c98b3b39bc19e22e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Mar 2018 12:11:00 +0100 Subject: [PATCH 160/238] Remove some whitespace --- Parsedown.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index b5fb584..fdc2aa6 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -586,22 +586,17 @@ class Parsedown $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); - if ( - $Line['indent'] < $requiredIndent - and - ( + if ($Line['indent'] < $requiredIndent + and ( ( $Block['data']['type'] === 'ol' and preg_match('/^[0-9]+'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) - ) - or - ( + ) or ( $Block['data']['type'] === 'ul' and preg_match('/^'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) ) ) - ) - { + ) { if (isset($Block['interrupted'])) { $Block['li']['text'] []= ''; From 1c52cb6b5e775ce51c1c17ee211a2a484ff77766 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Mar 2018 22:01:32 +0100 Subject: [PATCH 161/238] Add failing test cases --- test/data/atx_heading.html | 4 +++- test/data/atx_heading.md | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/data/atx_heading.html b/test/data/atx_heading.html index 751f873..94c3825 100644 --- a/test/data/atx_heading.html +++ b/test/data/atx_heading.html @@ -6,4 +6,6 @@
    h6

    ####### not a heading

    closed h1

    -

    #

    \ No newline at end of file +

    #

    +

    # of levels

    +

    # of levels #

    \ No newline at end of file diff --git a/test/data/atx_heading.md b/test/data/atx_heading.md index ad97b44..62fdd6c 100644 --- a/test/data/atx_heading.md +++ b/test/data/atx_heading.md @@ -14,4 +14,8 @@ # closed h1 # -# \ No newline at end of file +# + +# # of levels + +# # of levels # # \ No newline at end of file From 790aed42ab4997d6c5889be6aa42820de0f0f980 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 27 Mar 2018 22:04:11 +0100 Subject: [PATCH 162/238] Fix trimming of internal #'s --- Parsedown.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 1d9839b..5e4a6e2 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -505,7 +505,8 @@ class Parsedown return; } - $text = trim($Line['text'], '# '); + $text = trim($Line['text'], '#'); + $text = trim($text, ' '); $Block = array( 'element' => array( From 00e51ee4245edd5b7121ae3a6ea461db42ab3bf6 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 26 Mar 2018 20:37:21 +0100 Subject: [PATCH 163/238] Permit 1 column tables with less delimiters --- Parsedown.php | 215 +++++++++++++++++++----------------- test/data/simple_table.html | 32 ++++++ test/data/simple_table.md | 16 ++- 3 files changed, 163 insertions(+), 100 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 1d9839b..965a844 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -835,104 +835,119 @@ class Parsedown return; } - if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') - { - $alignments = array(); - - $divider = $Line['text']; - - $divider = trim($divider); - $divider = trim($divider, '|'); - - $dividerCells = explode('|', $divider); - - foreach ($dividerCells as $dividerCell) - { - $dividerCell = trim($dividerCell); - - if ($dividerCell === '') - { - continue; - } - - $alignment = null; - - if ($dividerCell[0] === ':') - { - $alignment = 'left'; - } - - if (substr($dividerCell, - 1) === ':') - { - $alignment = $alignment === 'left' ? 'center' : 'right'; - } - - $alignments []= $alignment; - } - - # ~ - - $HeaderElements = array(); - - $header = $Block['element']['text']; - - $header = trim($header); - $header = trim($header, '|'); - - $headerCells = explode('|', $header); - - foreach ($headerCells as $index => $headerCell) - { - $headerCell = trim($headerCell); - - $HeaderElement = array( - 'name' => 'th', - 'text' => $headerCell, - 'handler' => 'line', - ); - - if (isset($alignments[$index])) - { - $alignment = $alignments[$index]; - - $HeaderElement['attributes'] = array( - 'style' => 'text-align: '.$alignment.';', - ); - } - - $HeaderElements []= $HeaderElement; - } - - # ~ - - $Block = array( - 'alignments' => $alignments, - 'identified' => true, - 'element' => array( - 'name' => 'table', - 'handler' => 'elements', - ), - ); - - $Block['element']['text'] []= array( - 'name' => 'thead', - 'handler' => 'elements', - ); - - $Block['element']['text'] []= array( - 'name' => 'tbody', - 'handler' => 'elements', - 'text' => array(), - ); - - $Block['element']['text'][0]['text'] []= array( - 'name' => 'tr', - 'handler' => 'elements', - 'text' => $HeaderElements, - ); - - return $Block; + if ( + strpos($Block['element']['text'], '|') === false + and strpos($Line['text'], '|') === false + and strpos($Line['text'], ':') === false + ) { + return; } + + if (chop($Line['text'], ' -:|') !== '') + { + return; + } + + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + if (count($headerCells) !== count($alignments)) + { + return; + } + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; } protected function blockTableContinue($Line, array $Block) @@ -942,7 +957,7 @@ class Parsedown return; } - if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) { $Elements = array(); @@ -953,7 +968,9 @@ class Parsedown preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); - foreach ($matches[0] as $index => $cell) + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) { $cell = trim($cell); diff --git a/test/data/simple_table.html b/test/data/simple_table.html index 237d7ef..f6f7aab 100644 --- a/test/data/simple_table.html +++ b/test/data/simple_table.html @@ -34,4 +34,36 @@ cell 2.2 + +
    + + + + + + + + + + + + + + +
    header 1
    cell 1.1
    cell 2.1
    +
    + + + + + + + + + + + + + +
    header 1
    cell 1.1
    cell 2.1
    \ No newline at end of file diff --git a/test/data/simple_table.md b/test/data/simple_table.md index 466d140..f767257 100644 --- a/test/data/simple_table.md +++ b/test/data/simple_table.md @@ -8,4 +8,18 @@ cell 2.1 | cell 2.2 header 1 | header 2 :------- | -------- cell 1.1 | cell 1.2 -cell 2.1 | cell 2.2 \ No newline at end of file +cell 2.1 | cell 2.2 + +--- + +header 1 +:------- +cell 1.1 +cell 2.1 + +--- + +header 1 +-------| +cell 1.1 +cell 2.1 \ No newline at end of file From caea78300609ecc335379fc88e4bd54f1a6928a4 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 03:24:01 +0100 Subject: [PATCH 164/238] Add failing test case --- test/data/simple_blockquote.html | 15 +++++++++++++++ test/data/simple_blockquote.md | 8 +++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/test/data/simple_blockquote.html b/test/data/simple_blockquote.html index 8225d57..9f9bfab 100644 --- a/test/data/simple_blockquote.html +++ b/test/data/simple_blockquote.html @@ -8,4 +8,19 @@

    no space after >:

    quote

    +
    +
    +
    +
    +
    +

    Info 1 text

    +
    +
    +
    +
    +
    +
    +

    Info 2 text

    +
    +
    \ No newline at end of file diff --git a/test/data/simple_blockquote.md b/test/data/simple_blockquote.md index 22b6b11..d7c3e12 100644 --- a/test/data/simple_blockquote.md +++ b/test/data/simple_blockquote.md @@ -4,4 +4,10 @@ indented: > quote no space after `>`: ->quote \ No newline at end of file +>quote + +--- + +>>> Info 1 text + +>>> Info 2 text \ No newline at end of file From 07216480dba97140aa38333c3be84421bfbbabb4 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 03:26:45 +0100 Subject: [PATCH 165/238] Change test to comply with CommonMark --- test/data/lazy_blockquote.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/data/lazy_blockquote.html b/test/data/lazy_blockquote.html index 0a2a2aa..dea3dca 100644 --- a/test/data/lazy_blockquote.html +++ b/test/data/lazy_blockquote.html @@ -1,6 +1,8 @@

    quote the rest of it

    +
    +

    another paragraph the rest of it

    \ No newline at end of file From 92e426e0e893bf87f04aa586737f0dc394f073f3 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 03:27:09 +0100 Subject: [PATCH 166/238] Fix merging of adjacent blockquotes --- Parsedown.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0b00886..11e50d5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -698,15 +698,13 @@ class Parsedown protected function blockQuoteContinue($Line, array $Block) { + if (isset($Block['interrupted'])) + { + return; + } + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) { - if (isset($Block['interrupted'])) - { - $Block['element']['text'] []= ''; - - unset($Block['interrupted']); - } - $Block['element']['text'] []= $matches[1]; return $Block; From 40e797031e03ebbd00400af22c9eab3721728693 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 21 Mar 2018 17:31:40 +0000 Subject: [PATCH 167/238] Old handler compatability layer --- Parsedown.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 60aff75..b843a6a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1546,9 +1546,18 @@ class Parsedown $Element['nonNestables'] = array(); } - $function = $Element['handler']['function']; - $argument = $Element['handler']['argument']; - $destination = $Element['handler']['destination']; + if (is_string($Element['handler'])) + { + $function = $Element['handler']; + $argument = $Element['text']; + $destination = 'rawHtml'; + } + else + { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); } From a3e02c1d0e7f7da041754328641a859e7a7be48a Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 15:37:47 +0100 Subject: [PATCH 168/238] Add failing test case --- test/data/ordered_list.html | 5 ++++- test/data/ordered_list.md | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test/data/ordered_list.html b/test/data/ordered_list.html index c4a69db..b748fc1 100644 --- a/test/data/ordered_list.html +++ b/test/data/ordered_list.html @@ -10,4 +10,7 @@

    large numbers:

    1. one
    2. -
    \ No newline at end of file + +

    foo 1. the following should not start a list +100.
    +200.

    \ No newline at end of file diff --git a/test/data/ordered_list.md b/test/data/ordered_list.md index b307032..d7e7fc1 100644 --- a/test/data/ordered_list.md +++ b/test/data/ordered_list.md @@ -8,4 +8,8 @@ repeating numbers: large numbers: -123. one \ No newline at end of file +123. one + +foo 1. the following should not start a list +100. +200. \ No newline at end of file From a9c21447cee7393bd20a4b26609ddbcb2ebf1332 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 15:38:11 +0100 Subject: [PATCH 169/238] Only interrupt paragraph if starting with 1 --- Parsedown.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 11e50d5..5ca40d8 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -523,7 +523,7 @@ class Parsedown # # List - protected function blockList($Line) + protected function blockList($Line, array $CurrentBlock = null) { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}[.\)]'); @@ -556,12 +556,20 @@ class Parsedown ), ); - if($name === 'ol') + if ($name === 'ol') { $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; - if($listStart !== '1') + if ($listStart !== '1') { + if ( + isset($CurrentBlock) + and ! isset($CurrentBlock['type']) + and ! isset($CurrentBlock['interrupted']) + ) { + return; + } + $Block['element']['attributes'] = array('start' => $listStart); } } From 9f1f5de38763d3235830c07acb431ef871f71f7c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Wed, 28 Mar 2018 20:59:56 +0100 Subject: [PATCH 170/238] Add recursive handle methods to generate entire AST for traversal --- Parsedown.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index b843a6a..6023808 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1567,6 +1567,27 @@ class Parsedown return $Element; } + protected function handleElementRecursive(array $Element) + { + $Element = $this->handle($Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->handleElementsRecursive($Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->handleElementRecursive($Element['element']); + } + + return $Element; + } + + protected function handleElementsRecursive(array $Elements) + { + return array_map(array($this, 'handleElementRecursive'), $Elements); + } + protected function element(array $Element) { @@ -1575,6 +1596,7 @@ class Parsedown $Element = $this->sanitiseElement($Element); } + # identity map if element has no handler $Element = $this->handle($Element); $hasName = isset($Element['name']); From 20e592359f5fb1c902160a1dc79b48b8155c2f10 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 30 Mar 2018 19:22:13 +0100 Subject: [PATCH 171/238] Add failing test case --- test/data/html_comment.html | 8 +++++++- test/data/html_comment.md | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/data/html_comment.html b/test/data/html_comment.html index 566dc3a..009c309 100644 --- a/test/data/html_comment.html +++ b/test/data/html_comment.html @@ -2,4 +2,10 @@

    paragraph

    -

    paragraph

    \ No newline at end of file +

    paragraph

    +abc +
      +
    • abcd
    • +
    • bbbb
    • +
    • cccc
    • +
    \ No newline at end of file diff --git a/test/data/html_comment.md b/test/data/html_comment.md index 6ddfdb4..27aeb29 100644 --- a/test/data/html_comment.md +++ b/test/data/html_comment.md @@ -5,4 +5,10 @@ paragraph -paragraph \ No newline at end of file +paragraph + +abc + +* abcd +* bbbb +* cccc \ No newline at end of file From aa90dd481ae5f65e461078fe18e77b1d964caaf1 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 30 Mar 2018 19:22:50 +0100 Subject: [PATCH 172/238] Match CommonMark spec on HTML comments: Start condition: line begins with the string ``. --- Parsedown.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 5ca40d8..0313408 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -383,13 +383,13 @@ class Parsedown return; } - if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + if (strpos($Line['text'], '$/', $Line['text'])) + if (strpos($Line['text'], '-->') !== false) { $Block['closed'] = true; } @@ -407,7 +407,7 @@ class Parsedown $Block['element']['rawHtml'] .= "\n" . $Line['body']; - if (preg_match('/-->$/', $Line['text'])) + if (strpos($Line['text'], '-->') !== false) { $Block['closed'] = true; } From e4d6c8f91138e50b9471cc67c87935ec6bbfe839 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 31 Mar 2018 12:07:53 +0100 Subject: [PATCH 173/238] Add support for recursive handlers --- Parsedown.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index 6023808..fd68ce5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1560,6 +1560,11 @@ class Parsedown } $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') + { + $Element = $this->handle($Element); + } } unset($Element['handler']); From 535110c57eae6227a80074ed60378260cb966276 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 31 Mar 2018 23:23:12 +0100 Subject: [PATCH 174/238] Add `process` method so extensions may process final AST without copying implementation of `text` --- Parsedown.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 8b6ebc9..382023c 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -36,7 +36,13 @@ class Parsedown $lines = explode("\n", $text); # iterate through lines to identify blocks - $markup = $this->lines($lines); + $Elements = $this->linesElements($lines); + + # process elements + $Elements = $this->process($Elements); + + # convert to markup + $markup = $this->elements($Elements); # trim line breaks $markup = trim($markup, "\n"); @@ -1709,6 +1715,11 @@ class Parsedown return $markup; } + protected function process(array $Elements) + { + return $Elements; + } + # ~ protected function li($lines) From 9026b1abdb2ceaff39d275e65712c2bb79f54e2b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 1 Apr 2018 16:55:10 +0100 Subject: [PATCH 175/238] Add recursive helper for AST, use this for implementation of calling handler recursively --- Parsedown.php | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 382023c..e0783c9 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1588,26 +1588,39 @@ class Parsedown protected function handleElementRecursive(array $Element) { - $Element = $this->handle($Element); + return $this->elementApplyRecursive(array($this, 'handle'), $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); if (isset($Element['elements'])) { - $Element['elements'] = $this->handleElementsRecursive($Element['elements']); + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); } elseif (isset($Element['element'])) { - $Element['element'] = $this->handleElementRecursive($Element['element']); + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); } return $Element; } - protected function handleElementsRecursive(array $Elements) + protected function elementsApplyRecursive($closure, array $Elements) { - return array_map(array($this, 'handleElementRecursive'), $Elements); + return array_map( + array($this, 'elementApplyRecursive'), + array_fill(0, count($Elements), $closure), + $Elements + ); } - protected function element(array $Element) { if ($this->safeMode) From 390fa0da1b2da708ed040455de24fbc085e61a74 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 1 Apr 2018 17:55:32 +0100 Subject: [PATCH 176/238] This is probably faster than duplicating the closure --- Parsedown.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e0783c9..d0cf8bc 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1614,10 +1614,13 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { - return array_map( - array($this, 'elementApplyRecursive'), - array_fill(0, count($Elements), $closure), - $Elements + return array_reduce( + $Elements, + function (array $Elements, array $Element) use ($closure) { + $Elements[] = $this->elementApplyRecursive($closure, $Element); + return $Elements; + }, + array() ); } From 8a9058621824ff0a675a735fbbf1c1161d596208 Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Thu, 29 Mar 2018 22:10:30 +1100 Subject: [PATCH 177/238] Support #hashtag per CommonMark and GFM specs --- Parsedown.php | 5 +++++ test/data/atx_heading.html | 4 +++- test/data/atx_heading.md | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 382023c..1578815 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -519,6 +519,11 @@ class Parsedown } $text = trim($Line['text'], '#'); + + if (!isset($text[0]) or $text[0] !== ' ') { + return; + } + $text = trim($text, ' '); $Block = array( diff --git a/test/data/atx_heading.html b/test/data/atx_heading.html index 94c3825..545d889 100644 --- a/test/data/atx_heading.html +++ b/test/data/atx_heading.html @@ -7,5 +7,7 @@

    ####### not a heading

    closed h1

    #

    +

    ##

    # of levels

    -

    # of levels #

    \ No newline at end of file +

    # of levels #

    +

    #hashtag

    \ No newline at end of file diff --git a/test/data/atx_heading.md b/test/data/atx_heading.md index 62fdd6c..7748aa9 100644 --- a/test/data/atx_heading.md +++ b/test/data/atx_heading.md @@ -16,6 +16,10 @@ # +## + # # of levels -# # of levels # # \ No newline at end of file +# # of levels # # + +#hashtag \ No newline at end of file From d0279cdd3b54caa8f7620b10c4397c20ba8cab9e Mon Sep 17 00:00:00 2001 From: Nathan Baulch Date: Fri, 30 Mar 2018 03:44:47 +1100 Subject: [PATCH 178/238] Enable #hashtag support via setting --- Parsedown.php | 11 ++++++++++- test/ParsedownTest.php | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) mode change 100644 => 100755 Parsedown.php mode change 100644 => 100755 test/ParsedownTest.php diff --git a/Parsedown.php b/Parsedown.php old mode 100644 new mode 100755 index 1578815..ff1ae0e --- a/Parsedown.php +++ b/Parsedown.php @@ -90,6 +90,15 @@ class Parsedown protected $safeMode; + function setHastagsEnabled($hashtagsEnabled) + { + $this->hashtagsEnabled = (bool) $hashtagsEnabled; + + return $this; + } + + protected $hashtagsEnabled; + protected $safeLinksWhitelist = array( 'http://', 'https://', @@ -520,7 +529,7 @@ class Parsedown $text = trim($Line['text'], '#'); - if (!isset($text[0]) or $text[0] !== ' ') { + if ($this->hashtagsEnabled and (!isset($text[0]) or $text[0] !== ' ')) { return; } diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php old mode 100644 new mode 100755 index 01b4e25..f4c5f7c --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -32,6 +32,7 @@ class ParsedownTest extends TestCase protected function initParsedown() { $Parsedown = new TestParsedown(); + $Parsedown->setHastagsEnabled(true); return $Parsedown; } From cf6d23de550baa24ed9f44ff4e58c888f885248e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 2 Apr 2018 17:06:31 +0100 Subject: [PATCH 179/238] Rename hashtags enabled to strict mode We can use this to seperate any intentional spec deviations from spec behaviour so users can pick between compatability and spec implementations --- Parsedown.php | 9 +++++---- test/ParsedownTest.php | 2 +- test/data/atx_heading.html | 3 +-- test/data/atx_heading.md | 4 +--- test/data/strict_atx_heading.html | 13 +++++++++++++ test/data/strict_atx_heading.md | 25 +++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 test/data/strict_atx_heading.html create mode 100644 test/data/strict_atx_heading.md diff --git a/Parsedown.php b/Parsedown.php index ff1ae0e..a3efd7a 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -90,14 +90,14 @@ class Parsedown protected $safeMode; - function setHastagsEnabled($hashtagsEnabled) + function setStrictMode($strictMode) { - $this->hashtagsEnabled = (bool) $hashtagsEnabled; + $this->strictMode = (bool) $strictMode; return $this; } - protected $hashtagsEnabled; + protected $strictMode; protected $safeLinksWhitelist = array( 'http://', @@ -529,7 +529,8 @@ class Parsedown $text = trim($Line['text'], '#'); - if ($this->hashtagsEnabled and (!isset($text[0]) or $text[0] !== ' ')) { + if ($this->strictMode and ( ! isset($text[0]) or $text[0] !== ' ')) + { return; } diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index f4c5f7c..bf40317 100755 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -32,7 +32,6 @@ class ParsedownTest extends TestCase protected function initParsedown() { $Parsedown = new TestParsedown(); - $Parsedown->setHastagsEnabled(true); return $Parsedown; } @@ -52,6 +51,7 @@ class ParsedownTest extends TestCase $expectedMarkup = str_replace("\r", "\n", $expectedMarkup); $this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss'); + $this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict'); $actualMarkup = $this->Parsedown->text($markdown); diff --git a/test/data/atx_heading.html b/test/data/atx_heading.html index 545d889..387e2ec 100644 --- a/test/data/atx_heading.html +++ b/test/data/atx_heading.html @@ -7,7 +7,6 @@

    ####### not a heading

    closed h1

    #

    -

    ##

    # of levels

    # of levels #

    -

    #hashtag

    \ No newline at end of file +

    heading

    \ No newline at end of file diff --git a/test/data/atx_heading.md b/test/data/atx_heading.md index 7748aa9..3724c1f 100644 --- a/test/data/atx_heading.md +++ b/test/data/atx_heading.md @@ -16,10 +16,8 @@ # -## - # # of levels # # of levels # # -#hashtag \ No newline at end of file +#heading \ No newline at end of file diff --git a/test/data/strict_atx_heading.html b/test/data/strict_atx_heading.html new file mode 100644 index 0000000..11cf4df --- /dev/null +++ b/test/data/strict_atx_heading.html @@ -0,0 +1,13 @@ +

    h1

    +

    h2

    +

    h3

    +

    h4

    +
    h5
    +
    h6
    +

    ####### not a heading

    +

    #not a heading

    +

    closed h1

    +

    +

    +

    # of levels

    +

    # of levels #

    \ No newline at end of file diff --git a/test/data/strict_atx_heading.md b/test/data/strict_atx_heading.md new file mode 100644 index 0000000..5358731 --- /dev/null +++ b/test/data/strict_atx_heading.md @@ -0,0 +1,25 @@ +# h1 + +## h2 + +### h3 + +#### h4 + +##### h5 + +###### h6 + +####### not a heading + +#not a heading + +# closed h1 # + +# + +## + +# # of levels + +# # of levels # # \ No newline at end of file From 772c919b058f68d2fdd551a22128d3a65d8cc6d2 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 2 Apr 2018 17:09:54 +0100 Subject: [PATCH 180/238] Fix bug where empty atx headings would not be recognised (CommonMark) Fixes #595 --- Parsedown.php | 65 ++++++++++++++++++-------------------- test/data/atx_heading.html | 3 +- test/data/atx_heading.md | 2 ++ 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index a3efd7a..1dde17d 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -513,42 +513,39 @@ class Parsedown protected function blockHeader($Line) { - if (isset($Line['text'][1])) + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') { - $level = 1; - - while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') - { - $level ++; - } - - if ($level > 6) - { - return; - } - - $text = trim($Line['text'], '#'); - - if ($this->strictMode and ( ! isset($text[0]) or $text[0] !== ' ')) - { - return; - } - - $text = trim($text, ' '); - - $Block = array( - 'element' => array( - 'name' => 'h' . min(6, $level), - 'handler' => array( - 'function' => 'lineElements', - 'argument' => $text, - 'destination' => 'elements', - ) - ), - ); - - return $Block; + $level ++; } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) + ), + ); + + return $Block; } # diff --git a/test/data/atx_heading.html b/test/data/atx_heading.html index 387e2ec..3b09c38 100644 --- a/test/data/atx_heading.html +++ b/test/data/atx_heading.html @@ -6,7 +6,8 @@
    h6

    ####### not a heading

    closed h1

    -

    #

    +

    +

    # of levels

    # of levels #

    heading

    \ No newline at end of file diff --git a/test/data/atx_heading.md b/test/data/atx_heading.md index 3724c1f..50f991c 100644 --- a/test/data/atx_heading.md +++ b/test/data/atx_heading.md @@ -16,6 +16,8 @@ # +## + # # of levels # # of levels # # From 3b3d13489be45011d2e6720ff9793507b16a2285 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 2 Apr 2018 19:51:07 +0100 Subject: [PATCH 181/238] Test on PHP 7.2 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b9361a6..a5382fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ matrix: - php: 5.6 - php: 7.0 - php: 7.1 + - php: 7.2 - php: nightly - php: hhvm - php: hhvm-nightly From ed3e967fb64d04bd90e00e5246963e383daf7e0f Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 2 Apr 2018 19:52:26 +0100 Subject: [PATCH 182/238] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a4bb36..c987de4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Better Markdown Parser in PHP * Super Fast * Extensible * [GitHub flavored](https://help.github.com/articles/github-flavored-markdown) -* Tested in 5.3 to 7.1 and in HHVM +* Tested in 5.3 to 7.2 and in HHVM * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) ### Installation From 24e48e91c83bb572d28cdf153885914a4029de23 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 5 Apr 2018 01:01:52 +0100 Subject: [PATCH 183/238] Add literalBreaks support Line breaks will be converted to
    --- Parsedown.php | 21 +++++++++++++++++++-- test/ParsedownTest.php | 1 + test/data/literal_breaks.html | 6 ++++++ test/data/literal_breaks.md | 6 ++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/data/literal_breaks.html create mode 100644 test/data/literal_breaks.md diff --git a/Parsedown.php b/Parsedown.php index 186896c..f6662d6 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -63,6 +63,15 @@ class Parsedown protected $breaksEnabled; + function setLiteralBreaks($literalBreaks) + { + $this->literalBreaks = $literalBreaks; + + return $this; + } + + protected $literalBreaks; + function setMarkupEscaped($markupEscaped) { $this->markupEscaped = $markupEscaped; @@ -167,7 +176,7 @@ class Parsedown foreach ($lines as $line) { - if (chop($line) === '') + if ( ! $this->literalBreaks and chop($line) === '') { if (isset($CurrentBlock)) { @@ -230,7 +239,15 @@ class Parsedown # ~ - $marker = $text[0]; + if (isset($text[0])) + { + $marker = $text[0]; + } + elseif ($this->literalBreaks) + { + $marker = '\n'; + $text = ' '; + } # ~ diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index bf40317..d92e4fc 100755 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -52,6 +52,7 @@ class ParsedownTest extends TestCase $this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss'); $this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict'); + $this->Parsedown->setLiteralBreaks(substr($test, 0, 14) === 'literal_breaks'); $actualMarkup = $this->Parsedown->text($markdown); diff --git a/test/data/literal_breaks.html b/test/data/literal_breaks.html new file mode 100644 index 0000000..bf9e273 --- /dev/null +++ b/test/data/literal_breaks.html @@ -0,0 +1,6 @@ +

    first line +
    +
    +
    +
    +sixth line

    \ No newline at end of file diff --git a/test/data/literal_breaks.md b/test/data/literal_breaks.md new file mode 100644 index 0000000..2b95a92 --- /dev/null +++ b/test/data/literal_breaks.md @@ -0,0 +1,6 @@ +first line + + + + +sixth line \ No newline at end of file From 38ea813b0ea950cf34d5bfde04c23191c70a2c13 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 5 Apr 2018 16:54:35 +0100 Subject: [PATCH 184/238] Add failing test case --- test/data/fenced_code_block.html | 5 ++++- test/data/fenced_code_block.md | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/data/fenced_code_block.html b/test/data/fenced_code_block.html index 565a541..78480fd 100644 --- a/test/data/fenced_code_block.html +++ b/test/data/fenced_code_block.html @@ -8,4 +8,7 @@ echo $message;
    <?php
     echo "Hello World";
     ?>
    -<a href="http://auraphp.com" >Aura Project</a>
    \ No newline at end of file +<a href="http://auraphp.com" >Aura Project</a> +
    the following isn't quite enough to close
    +```
    +still a fenced code block
    \ No newline at end of file diff --git a/test/data/fenced_code_block.md b/test/data/fenced_code_block.md index 62db24a..35acb75 100644 --- a/test/data/fenced_code_block.md +++ b/test/data/fenced_code_block.md @@ -22,4 +22,10 @@ echo 'language identifier with non words'; echo "Hello World"; ?> Aura Project -``` \ No newline at end of file +``` + +```` +the following isn't quite enough to close +``` +still a fenced code block +```` \ No newline at end of file From 06b810cd4a4e139d7367c70dc1470d6a7f1c06bb Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 5 Apr 2018 16:55:14 +0100 Subject: [PATCH 185/238] Fix fenced code block closer to match CommonMark rules --- Parsedown.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 186896c..ee38601 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -443,16 +443,16 @@ class Parsedown protected function blockFencedCode($Line) { - if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) + if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) { $Element = array( 'name' => 'code', 'text' => '', ); - if (isset($matches[1])) + if (isset($matches[2])) { - $class = 'language-'.$matches[1]; + $class = 'language-'.$matches[2]; $Element['attributes'] = array( 'class' => $class, @@ -461,6 +461,7 @@ class Parsedown $Block = array( 'char' => $Line['text'][0], + 'openerLength' => mb_strlen($matches[1]), 'element' => array( 'name' => 'pre', 'element' => $Element, @@ -485,8 +486,10 @@ class Parsedown unset($Block['interrupted']); } - if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) - { + if ( + preg_match('/^(['.preg_quote($Block['char']).']{3,})[ ]*$/', $Line['text'], $matches) + and mb_strlen($matches[1]) >= $Block['openerLength'] + ) { $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); $Block['complete'] = true; From 798bda682e8b16c2114e467cd39146e2c1e47bad Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 5 Nov 2014 09:19:58 +0100 Subject: [PATCH 186/238] Update README.md updated installation instructions. fixes #242 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c987de4..196854e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,12 @@ Better Markdown Parser in PHP ### Installation -Include `Parsedown.php` or install [the composer package](https://packagist.org/packages/erusev/parsedown). +Download and include [Parsedown.php] or install the [composer package] by running the following command: + + composer require erusev/parsedown + +[Parsedown.php]: https://raw.githubusercontent.com/erusev/parsedown/master/Parsedown.php "The Parsedown class file" +[composer package]: https://packagist.org/packages/erusev/parsedown "The Parsedown package on packagist.org" ### Example From 0c0ed38290ca282486d7f0bbbd547b80d5e26b71 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 16:13:29 +0100 Subject: [PATCH 187/238] Don't encourage downloading from master --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 196854e..cdf0167 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,17 @@ Better Markdown Parser in PHP * [Markdown Extra extension](https://github.com/erusev/parsedown-extra) ### Installation - -Download and include [Parsedown.php] or install the [composer package] by running the following command: +#### Composer +Install the [composer package] by running the following command: composer require erusev/parsedown -[Parsedown.php]: https://raw.githubusercontent.com/erusev/parsedown/master/Parsedown.php "The Parsedown class file" +#### Manual +1. Download the "Source code" from the [latest release] +2. Include `Parsedown.php` + [composer package]: https://packagist.org/packages/erusev/parsedown "The Parsedown package on packagist.org" +[latest release]: https://github.com/erusev/parsedown/releases/latest "The latest release of Parsedown" ### Example From 557db7c1799c1955e442d9a6c18fb8bc259a8501 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 18:10:41 +0100 Subject: [PATCH 188/238] Split some of `text` into `textElements` `process` is no longer needed --- Parsedown.php | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 186896c..a6297dc 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -22,6 +22,19 @@ class Parsedown # ~ function text($text) + { + $Elements = $this->textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + protected function textElements($text) { # make sure no definitions are set $this->DefinitionData = array(); @@ -36,18 +49,7 @@ class Parsedown $lines = explode("\n", $text); # iterate through lines to identify blocks - $Elements = $this->linesElements($lines); - - # process elements - $Elements = $this->process($Elements); - - # convert to markup - $markup = $this->elements($Elements); - - # trim line breaks - $markup = trim($markup, "\n"); - - return $markup; + return $this->linesElements($lines); } # @@ -1743,11 +1745,6 @@ class Parsedown return $markup; } - protected function process(array $Elements) - { - return $Elements; - } - # ~ protected function li($lines) From 32e69de014c6248198b4a59f2e81a5aa795bf868 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 19:51:27 +0100 Subject: [PATCH 189/238] Ensure autobreak false is honoured over empty name --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 186896c..93faf43 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -1728,8 +1728,8 @@ class Parsedown foreach ($Elements as $Element) { - $autoBreakNext = (isset($Element['name']) - || isset($Element['autobreak']) && $Element['autobreak'] + $autoBreakNext = (isset($Element['autobreak']) && $Element['autobreak'] + || ! isset($Element['autobreak']) && isset($Element['name']) ); // (autobreak === false) covers both sides of an element $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; From 4c9ea94d0c1f1bd67fe48622bc71f081df2aea1d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 19:52:25 +0100 Subject: [PATCH 190/238] Apply depth first to avoid risk of segfault if closure creates subelements --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 186896c..d62c6c8 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -1610,8 +1610,6 @@ class Parsedown protected function elementApplyRecursive($closure, array $Element) { - $Element = call_user_func($closure, $Element); - if (isset($Element['elements'])) { $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); @@ -1621,6 +1619,8 @@ class Parsedown $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); } + $Element = call_user_func($closure, $Element); + return $Element; } From 0039cd00f8bed5bf2794353d29f4ff35bf914cf7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 20:40:25 +0100 Subject: [PATCH 191/238] Explicitly capture $this for PHP 5.3 --- Parsedown.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index fca5512..bec8e03 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -1648,10 +1648,13 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { + # PHP 5.3 compat + $instance = $this; + return array_reduce( $Elements, - function (array $Elements, array $Element) use ($closure) { - $Elements[] = $this->elementApplyRecursive($closure, $Element); + function (array $Elements, array $Element) use ($instance, $closure) { + $Elements[] = $instance->elementApplyRecursive($closure, $Element); return $Elements; }, array() From 68be90348cbdbfb4ae14ebe8ba18ad949b40dfc6 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 20:50:34 +0100 Subject: [PATCH 192/238] Revert "Explicitly capture $this for PHP 5.3" --- Parsedown.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index bec8e03..fca5512 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -1648,13 +1648,10 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { - # PHP 5.3 compat - $instance = $this; - return array_reduce( $Elements, - function (array $Elements, array $Element) use ($instance, $closure) { - $Elements[] = $instance->elementApplyRecursive($closure, $Element); + function (array $Elements, array $Element) use ($closure) { + $Elements[] = $this->elementApplyRecursive($closure, $Element); return $Elements; }, array() From 387ef63888dd957e01f4362221cfc3552ff87f9c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Fri, 6 Apr 2018 20:55:27 +0100 Subject: [PATCH 193/238] Replace array reduce with foreach loop for PHP 5.3 compat --- Parsedown.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index fca5512..9dde3f4 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -1648,14 +1648,14 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { - return array_reduce( - $Elements, - function (array $Elements, array $Element) use ($closure) { - $Elements[] = $this->elementApplyRecursive($closure, $Element); - return $Elements; - }, - array() - ); + $newElements = array(); + + foreach ($Elements as $Element) + { + $newElements[] = $this->elementApplyRecursive($closure, $Element); + } + + return $newElements; } protected function element(array $Element) From 600db7e4de545f9b2915ade9e0684a07a1f00a3e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 02:46:30 +0100 Subject: [PATCH 194/238] Bump version const to 1.8.0-beta-1 --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 9dde3f4..0ac3e3d 100755 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.7.1'; + const version = '1.8.0-beta-1'; # ~ From 1d65fb858a9e6a1ce7b2230b21dc76eef89b277c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 14:30:23 +0100 Subject: [PATCH 195/238] Restore file permission to that of 1.7.1 --- Parsedown.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Parsedown.php diff --git a/Parsedown.php b/Parsedown.php old mode 100755 new mode 100644 From cdaf86b0392c88af87c9d946b732498529e07b3b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 17:39:24 +0100 Subject: [PATCH 196/238] Add seperate depth-first function instead of replacing recursive method --- Parsedown.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index 0ac3e3d..eebd2f9 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1631,6 +1631,22 @@ class Parsedown } protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) { if (isset($Element['elements'])) { @@ -1658,6 +1674,18 @@ class Parsedown return $newElements; } + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + $newElements = array(); + + foreach ($Elements as $Element) + { + $newElements[] = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $newElements; + } + protected function element(array $Element) { if ($this->safeMode) From 86940be22421f709107d7be8a890d67ea65c25e5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 17:49:36 +0100 Subject: [PATCH 197/238] Use mutating loop instead of creating new array --- Parsedown.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index eebd2f9..9b7a3e9 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1664,26 +1664,22 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { - $newElements = array(); - - foreach ($Elements as $Element) + foreach ($Elements as &$Element) { - $newElements[] = $this->elementApplyRecursive($closure, $Element); + $Element = $this->elementApplyRecursive($closure, $Element); } - return $newElements; + return $Elements; } protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) { - $newElements = array(); - - foreach ($Elements as $Element) + foreach ($Elements as &$Element) { - $newElements[] = $this->elementApplyRecursiveDepthFirst($closure, $Element); + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); } - return $newElements; + return $Elements; } protected function element(array $Element) From 43d25a74fe882af127eccf1a01bd64e0489969e1 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 18:40:50 +0100 Subject: [PATCH 198/238] Fix function name --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 9b7a3e9..b5a0382 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1650,11 +1650,11 @@ class Parsedown { if (isset($Element['elements'])) { - $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); } elseif (isset($Element['element'])) { - $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); } $Element = call_user_func($closure, $Element); From 9a021b2130868db90dfe77c83820d68269e3787e Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 14:11:49 +0100 Subject: [PATCH 199/238] Add failing test cases --- test/data/code_block.html | 7 ++++++- test/data/code_block.md | 9 ++++++++- test/data/fenced_code_block.html | 6 +++++- test/data/fenced_code_block.md | 9 ++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/test/data/code_block.html b/test/data/code_block.html index 889b02d..a186e9a 100644 --- a/test/data/code_block.html +++ b/test/data/code_block.html @@ -5,4 +5,9 @@ echo $message;
    > not a quote
     - not a list item
    -[not a reference]: http://foo.com
    \ No newline at end of file +[not a reference]: http://foo.com +
    +
    foo
    +
    +
    +bar
    \ No newline at end of file diff --git a/test/data/code_block.md b/test/data/code_block.md index 2cfc953..badf873 100644 --- a/test/data/code_block.md +++ b/test/data/code_block.md @@ -7,4 +7,11 @@ > not a quote - not a list item - [not a reference]: http://foo.com \ No newline at end of file + [not a reference]: http://foo.com + +--- + + foo + + + bar \ No newline at end of file diff --git a/test/data/fenced_code_block.html b/test/data/fenced_code_block.html index 78480fd..50d39df 100644 --- a/test/data/fenced_code_block.html +++ b/test/data/fenced_code_block.html @@ -11,4 +11,8 @@ echo "Hello World"; <a href="http://auraphp.com" >Aura Project</a>
    the following isn't quite enough to close
     ```
    -still a fenced code block
    \ No newline at end of file +still a fenced code block +
    foo
    +
    +
    +bar
    \ No newline at end of file diff --git a/test/data/fenced_code_block.md b/test/data/fenced_code_block.md index 35acb75..3e4155a 100644 --- a/test/data/fenced_code_block.md +++ b/test/data/fenced_code_block.md @@ -28,4 +28,11 @@ echo "Hello World"; the following isn't quite enough to close ``` still a fenced code block -```` \ No newline at end of file +```` + +``` +foo + + +bar +``` \ No newline at end of file From 3f5b0ee7819a5c29f991c637de8c49e8edfa559c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 14:13:10 +0100 Subject: [PATCH 200/238] Count number of interrupts --- Parsedown.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index b5a0382..0a1bee5 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -182,7 +182,9 @@ class Parsedown { if (isset($CurrentBlock)) { - $CurrentBlock['interrupted'] = true; + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); } continue; @@ -388,7 +390,7 @@ class Parsedown { if (isset($Block['interrupted'])) { - $Block['element']['element']['text'] .= "\n"; + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); unset($Block['interrupted']); } @@ -500,7 +502,7 @@ class Parsedown if (isset($Block['interrupted'])) { - $Block['element']['element']['text'] .= "\n"; + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); unset($Block['interrupted']); } From 39df7d4f8ea22f67c03ab1776f4b8f3efb4db365 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:27:44 +0100 Subject: [PATCH 201/238] Swap 'hidden' blocks for empty elements --- Parsedown.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0a1bee5..f9f269e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -878,18 +878,13 @@ class Parsedown $Data = array( 'url' => $matches[2], - 'title' => null, + 'title' => isset($matches[3]) ? $matches[3] : null, ); - if (isset($matches[3])) - { - $Data['title'] = $matches[3]; - } - $this->DefinitionData['Reference'][$id] = $Data; $Block = array( - 'hidden' => true, + 'element' => array(), ); return $Block; @@ -1776,6 +1771,11 @@ class Parsedown foreach ($Elements as $Element) { + if (empty($Element)) + { + continue; + } + $autoBreakNext = (isset($Element['autobreak']) && $Element['autobreak'] || ! isset($Element['autobreak']) && isset($Element['name']) ); From 5353ebb524005cf570e04e791a7659aefeb8cc33 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:29:09 +0100 Subject: [PATCH 202/238] Avoid needing two arrays We only need to collect elements, we can discard finished blocks --- Parsedown.php | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index f9f269e..090bd7f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -174,6 +174,7 @@ class Parsedown protected function linesElements(array $lines) { + $Elements = array(); $CurrentBlock = null; foreach ($lines as $line) @@ -278,7 +279,10 @@ class Parsedown if ( ! isset($Block['identified'])) { - $Blocks []= $CurrentBlock; + if (isset($CurrentBlock)) + { + $Elements[] = $CurrentBlock['element']; + } $Block['identified'] = true; } @@ -306,7 +310,10 @@ class Parsedown } else { - $Blocks []= $CurrentBlock; + if (isset($CurrentBlock)) + { + $Elements[] = $CurrentBlock['element']; + } $CurrentBlock = $this->paragraph($Line); @@ -323,22 +330,9 @@ class Parsedown # ~ - $Blocks []= $CurrentBlock; - - unset($Blocks[0]); - - # ~ - - $Elements = array(); - - foreach ($Blocks as $Block) + if (isset($CurrentBlock)) { - if (isset($Block['hidden'])) - { - continue; - } - - $Elements[] = $Block['element']; + $Elements[] = $CurrentBlock['element']; } # ~ From ae8067e862dda3070aab204bc6f826ac1296b34d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:34:57 +0100 Subject: [PATCH 203/238] Swap undefined type for type === 'Paragraph' for ease of reading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The way in which we use this assumes that it is a paragraph, for example appending text into the handler argument — so there is no loss of generality here, we're simply being explicit. --- Parsedown.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 090bd7f..ffdc83d 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -302,8 +302,7 @@ class Parsedown if ( isset($CurrentBlock) - and isset($CurrentBlock['element']['name']) - and $CurrentBlock['element']['name'] === 'p' + and $CurrentBlock['type'] === 'Paragraph' and ! isset($CurrentBlock['interrupted']) ) { $CurrentBlock['element']['handler']['argument'] .= "\n".$text; @@ -355,7 +354,7 @@ class Parsedown protected function blockCode($Line, $Block = null) { - if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) { return; } @@ -610,7 +609,7 @@ class Parsedown { if ( isset($CurrentBlock) - and ! isset($CurrentBlock['type']) + and $CurrentBlock['type'] === 'Paragraph' and ! isset($CurrentBlock['interrupted']) ) { return; @@ -803,7 +802,7 @@ class Parsedown protected function blockSetextHeader($Line, array $Block = null) { - if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { return; } @@ -890,7 +889,7 @@ class Parsedown protected function blockTable($Line, array $Block = null) { - if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { return; } @@ -1070,18 +1069,17 @@ class Parsedown protected function paragraph($Line) { - $Block = array( + return array( + 'type' => 'Paragraph', 'element' => array( 'name' => 'p', 'handler' => array( 'function' => 'lineElements', 'argument' => $Line['text'], 'destination' => 'elements', - ) + ), ), ); - - return $Block; } # From e4cd13350b11f676afa0fd1e270078f65d0a0cff Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 15:11:45 +0100 Subject: [PATCH 204/238] Remove setLiteralBreaks --- Parsedown.php | 21 ++------------------- test/ParsedownTest.php | 1 - test/data/literal_breaks.html | 6 ------ test/data/literal_breaks.md | 6 ------ 4 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 test/data/literal_breaks.html delete mode 100644 test/data/literal_breaks.md diff --git a/Parsedown.php b/Parsedown.php index ffdc83d..e62ddd3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -65,15 +65,6 @@ class Parsedown protected $breaksEnabled; - function setLiteralBreaks($literalBreaks) - { - $this->literalBreaks = $literalBreaks; - - return $this; - } - - protected $literalBreaks; - function setMarkupEscaped($markupEscaped) { $this->markupEscaped = $markupEscaped; @@ -179,7 +170,7 @@ class Parsedown foreach ($lines as $line) { - if ( ! $this->literalBreaks and chop($line) === '') + if (chop($line) === '') { if (isset($CurrentBlock)) { @@ -244,15 +235,7 @@ class Parsedown # ~ - if (isset($text[0])) - { - $marker = $text[0]; - } - elseif ($this->literalBreaks) - { - $marker = '\n'; - $text = ' '; - } + $marker = $text[0]; # ~ diff --git a/test/ParsedownTest.php b/test/ParsedownTest.php index d92e4fc..bf40317 100755 --- a/test/ParsedownTest.php +++ b/test/ParsedownTest.php @@ -52,7 +52,6 @@ class ParsedownTest extends TestCase $this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss'); $this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict'); - $this->Parsedown->setLiteralBreaks(substr($test, 0, 14) === 'literal_breaks'); $actualMarkup = $this->Parsedown->text($markdown); diff --git a/test/data/literal_breaks.html b/test/data/literal_breaks.html deleted file mode 100644 index bf9e273..0000000 --- a/test/data/literal_breaks.html +++ /dev/null @@ -1,6 +0,0 @@ -

    first line -
    -
    -
    -
    -sixth line

    \ No newline at end of file diff --git a/test/data/literal_breaks.md b/test/data/literal_breaks.md deleted file mode 100644 index 2b95a92..0000000 --- a/test/data/literal_breaks.md +++ /dev/null @@ -1,6 +0,0 @@ -first line - - - - -sixth line \ No newline at end of file From 043c55e4c6f6cb2bfb6b9399e2f73b56f83ddb8b Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 15:12:17 +0100 Subject: [PATCH 205/238] Give paragraph block semantics for overloading --- Parsedown.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index e62ddd3..9e02e16 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -283,12 +283,14 @@ class Parsedown # ~ - if ( - isset($CurrentBlock) - and $CurrentBlock['type'] === 'Paragraph' - and ! isset($CurrentBlock['interrupted']) - ) { - $CurrentBlock['element']['handler']['argument'] .= "\n".$text; + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) + { + $CurrentBlock = $Block; } else { @@ -1065,6 +1067,18 @@ class Parsedown ); } + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + # # Inline Elements # From c440c91af5388b6c213103a44c61ca4b7ff80e88 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 16:28:25 +0100 Subject: [PATCH 206/238] Add failing test case --- test/data/simple_table.html | 8 +++++++- test/data/simple_table.md | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/test/data/simple_table.html b/test/data/simple_table.html index f6f7aab..b74a2ec 100644 --- a/test/data/simple_table.html +++ b/test/data/simple_table.html @@ -66,4 +66,10 @@ cell 2.1 - \ No newline at end of file + +
    +

    Not a table, we haven't ended the paragraph: +header 1 | header 2 +-------- | -------- +cell 1.1 | cell 1.2 +cell 2.1 | cell 2.2

    \ No newline at end of file diff --git a/test/data/simple_table.md b/test/data/simple_table.md index f767257..42eff5a 100644 --- a/test/data/simple_table.md +++ b/test/data/simple_table.md @@ -22,4 +22,12 @@ cell 2.1 header 1 -------| cell 1.1 -cell 2.1 \ No newline at end of file +cell 2.1 + +--- + +Not a table, we haven't ended the paragraph: +header 1 | header 2 +-------- | -------- +cell 1.1 | cell 1.2 +cell 2.1 | cell 2.2 \ No newline at end of file From cb33daf0e6f694076a3718b36c06c570b4b58ba7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 16:37:32 +0100 Subject: [PATCH 207/238] Assert table header does not contain new lines --- Parsedown.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Parsedown.php b/Parsedown.php index 9e02e16..5baf6ab 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -883,6 +883,7 @@ class Parsedown strpos($Block['element']['handler']['argument'], '|') === false and strpos($Line['text'], '|') === false and strpos($Line['text'], ':') === false + or strpos($Block['element']['handler']['argument'], "\n") !== false ) { return; } From 6f13f976745c56a4cfc4c3797ec59aa594368af1 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 17:33:01 +0100 Subject: [PATCH 208/238] Use mutating loop instead of array_map --- Parsedown.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 5baf6ab..153fb85 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1190,14 +1190,11 @@ class Parsedown $InlineText = $this->inlineText($text); $Elements[] = $InlineText['element']; - $Elements = array_map( - function ($Element) { - $Element['autobreak'] = isset($Element['autobreak']) - ? $Element['autobreak'] : false; - return $Element; - }, - $Elements - ); + foreach ($Elements as &$Element) + { + $Element['autobreak'] = isset($Element['autobreak']) + ? $Element['autobreak'] : false; + } return $Elements; } From f2327023c14170047217f2bad9f7fcdf91426f61 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 17:38:09 +0100 Subject: [PATCH 209/238] No need to unset if not set --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 153fb85..6dbca0e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1597,9 +1597,9 @@ class Parsedown { $Element = $this->handle($Element); } - } - unset($Element['handler']); + unset($Element['handler']); + } return $Element; } From 90ad738933e3468374545da0fe331620943800af Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:37:36 +0100 Subject: [PATCH 210/238] General readability --- Parsedown.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6dbca0e..f7d0a27 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1105,7 +1105,7 @@ class Parsedown # ~ # - public function line($text, $nonNestables=array()) + public function line($text, $nonNestables = array()) { return $this->elements($this->lineElements($text, $nonNestables)); } @@ -1156,9 +1156,9 @@ class Parsedown # cause the new element to 'inherit' our non nestables - foreach ($nonNestables as $non_nestable) + foreach ($nonNestables as $nonNestable) { - $Inline['element']['nonNestables'][] = $non_nestable; + $Inline['element']['nonNestables'][] = $nonNestable; } # the text that comes before the inline @@ -1192,8 +1192,10 @@ class Parsedown foreach ($Elements as &$Element) { - $Element['autobreak'] = isset($Element['autobreak']) - ? $Element['autobreak'] : false; + if ( ! isset($Element['autobreak'])) + { + $Element['autobreak'] = false; + } } return $Elements; @@ -1763,8 +1765,8 @@ class Parsedown continue; } - $autoBreakNext = (isset($Element['autobreak']) && $Element['autobreak'] - || ! isset($Element['autobreak']) && isset($Element['name']) + $autoBreakNext = (isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) ); // (autobreak === false) covers both sides of an element $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; From 70f5c02d47818c8ea033984b39d26988771dca45 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:38:21 +0100 Subject: [PATCH 211/238] Use non-nestable values as keys for O(1) lookup --- Parsedown.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index f7d0a27..62cbf5a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1114,6 +1114,8 @@ class Parsedown { $Elements = array(); + $nonNestables = array_combine($nonNestables, $nonNestables); + # $excerpt is based on the first occurrence of a marker while ($excerpt = strpbrk($text, $this->inlineMarkerList)) @@ -1128,7 +1130,7 @@ class Parsedown { # check to see if the current inline type is nestable in the current context - if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) + if (isset($nonNestables[$inlineType])) { continue; } From dc5cf8770b536e2d92747c558666c51569fe2b2a Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:39:20 +0100 Subject: [PATCH 212/238] The AST has high complexity here (and so traversal is hard anyway) We gain quite a bit of a speed boost by working with text here since this is a very common function --- Parsedown.php | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 62cbf5a..438e39e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1211,32 +1211,20 @@ class Parsedown { $Inline = array( 'extent' => strlen($text), - 'element' => array( - 'elements' => array(), - ), + 'element' => array(), ); + $safeText = self::escape($text, true); + if ($this->breaksEnabled) { - $Inline['element']['elements'] = self::pregReplaceElements( - '/[ ]*\n/', - array( - array('name' => 'br'), - array('text' => "\n"), - ), - $text - ); + $Inline['element']['rawHtml'] = preg_replace('/[ ]*\n/', "
    \n", $safeText); + $Inline['element']['allowRawHtmlInSafeMode'] = true; } else { - $Inline['element']['elements'] = self::pregReplaceElements( - '/(?:[ ][ ]+|[ ]*\\\\)\n/', - array( - array('name' => 'br'), - array('text' => "\n"), - ), - $text - ); + $Inline['element']['rawHtml'] = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
    \n", $safeText); + $Inline['element']['allowRawHtmlInSafeMode'] = true; } return $Inline; From d6e306d62044c7731317ec2670a30842d13358d9 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 20:43:14 +0100 Subject: [PATCH 213/238] Optimise commonly used regexes to fail fast --- Parsedown.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 438e39e..2e61f0f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1218,12 +1218,12 @@ class Parsedown if ($this->breaksEnabled) { - $Inline['element']['rawHtml'] = preg_replace('/[ ]*\n/', "
    \n", $safeText); + $Inline['element']['rawHtml'] = preg_replace('/[ ]*+\n/', "
    \n", $safeText); $Inline['element']['allowRawHtmlInSafeMode'] = true; } else { - $Inline['element']['rawHtml'] = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
    \n", $safeText); + $Inline['element']['rawHtml'] = preg_replace('/(?:[ ]*+\\\\|[ ]{2,}+)\n/', "
    \n", $safeText); $Inline['element']['allowRawHtmlInSafeMode'] = true; } From d4f1ac465c2f0abc371fd8d200ceae245a181969 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 21:24:45 +0100 Subject: [PATCH 214/238] String interpolation is slightly faster than concat --- Parsedown.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 2e61f0f..8fac5cd 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -216,7 +216,7 @@ class Parsedown if (isset($CurrentBlock['continuable'])) { - $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + $Block = $this->{"block{$CurrentBlock['type']}Continue"}($Line, $CurrentBlock); if (isset($Block)) { @@ -228,7 +228,7 @@ class Parsedown { if ($this->isBlockCompletable($CurrentBlock['type'])) { - $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + $CurrentBlock = $this->{"block{$CurrentBlock['type']}Complete"}($CurrentBlock); } } } @@ -254,7 +254,7 @@ class Parsedown foreach ($blockTypes as $blockType) { - $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); if (isset($Block)) { @@ -309,7 +309,7 @@ class Parsedown if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { - $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + $CurrentBlock = $this->{"block{$CurrentBlock['type']}Complete"}($CurrentBlock); } # ~ @@ -326,12 +326,12 @@ class Parsedown protected function isBlockContinuable($Type) { - return method_exists($this, 'block'.$Type.'Continue'); + return method_exists($this, "block${Type}Continue"); } protected function isBlockCompletable($Type) { - return method_exists($this, 'block'.$Type.'Complete'); + return method_exists($this, "block${Type}Complete"); } # @@ -427,7 +427,7 @@ class Parsedown return; } - $Block['element']['rawHtml'] .= "\n" . $Line['body']; + $Block['element']['rawHtml'] .= "\n{$Line['body']}"; if (strpos($Line['text'], '-->') !== false) { @@ -451,7 +451,7 @@ class Parsedown if (isset($matches[2])) { - $class = 'language-'.$matches[2]; + $class = "language-{$matches[2]}"; $Element['attributes'] = array( 'class' => $class, @@ -496,7 +496,7 @@ class Parsedown return $Block; } - $Block['element']['element']['text'] .= "\n".$Line['body']; + $Block['element']['element']['text'] .= "\n{$Line['body']}"; return $Block; } @@ -840,7 +840,7 @@ class Parsedown return; } - $Block['element']['rawHtml'] .= "\n".$Line['body']; + $Block['element']['rawHtml'] .= "\n{$Line['body']}"; return $Block; } @@ -960,7 +960,7 @@ class Parsedown $alignment = $alignments[$index]; $HeaderElement['attributes'] = array( - 'style' => 'text-align: '.$alignment.';', + 'style' => "text-align: $alignment;", ); } @@ -1031,7 +1031,7 @@ class Parsedown if (isset($Block['alignments'][$index])) { $Element['attributes'] = array( - 'style' => 'text-align: '.$Block['alignments'][$index].';', + 'style' => "text-align: {$Block['alignments'][$index]};", ); } @@ -1135,7 +1135,7 @@ class Parsedown continue; } - $Inline = $this->{'inline'.$inlineType}($Excerpt); + $Inline = $this->{"inline$inlineType"}($Excerpt); if ( ! isset($Inline)) { @@ -1263,7 +1263,7 @@ class Parsedown if ( ! isset($matches[2])) { - $url = 'mailto:' . $url; + $url = "mailto:$url"; } return array( @@ -1472,7 +1472,7 @@ class Parsedown if (preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)) { return array( - 'element' => array('rawHtml' => '&'.$matches[1].';'), + 'element' => array('rawHtml' => "&{$matches[1]};"), 'extent' => strlen($matches[0]), ); } @@ -1674,7 +1674,7 @@ class Parsedown if ($hasName) { - $markup .= '<'.$Element['name']; + $markup .= "<{$Element['name']}"; if (isset($Element['attributes'])) { @@ -1685,7 +1685,7 @@ class Parsedown continue; } - $markup .= ' '.$name.'="'.self::escape($value).'"'; + $markup .= " $name=\"".self::escape($value).'"'; } } } @@ -1732,7 +1732,7 @@ class Parsedown } } - $markup .= $hasName ? '' : ''; + $markup .= $hasName ? "" : ''; } elseif ($hasName) { From 107223d3a0f65c89ac0c2afa4ead3fa6a8545004 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 22:32:42 +0100 Subject: [PATCH 215/238] Avoid recomputation --- Parsedown.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 8fac5cd..238ec0e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -572,13 +572,15 @@ class Parsedown $matches[1] .= ' '; } + $markerWithoutWhitespace = strstr($matches[1], ' ', true); + $Block = array( 'indent' => $Line['indent'], 'pattern' => $pattern, 'data' => array( 'type' => $name, 'marker' => $matches[1], - 'markerType' => ($name === 'ul' ? strstr($matches[1], ' ', true) : substr(strstr($matches[1], ' ', true), -1)), + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), ), 'element' => array( 'name' => $name, From b42add3762da22437a9509f67bd5f6e5e8a49656 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 15:41:18 +0100 Subject: [PATCH 216/238] Make some regexes possesive --- Parsedown.php | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 238ec0e..057dd49 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -442,7 +442,7 @@ class Parsedown protected function blockFencedCode($Line) { - if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) + if (preg_match('/^(['.$Line['text'][0].']{3,}+)[ ]*+([^`]++)?+[ ]*+$/', $Line['text'], $matches)) { $Element = array( 'name' => 'code', @@ -486,7 +486,7 @@ class Parsedown } if ( - preg_match('/^(['.preg_quote($Block['char']).']{3,})[ ]*$/', $Line['text'], $matches) + preg_match('/^(['.preg_quote($Block['char']).']{3,}+)[ ]*+$/', $Line['text'], $matches) and mb_strlen($matches[1]) >= $Block['openerLength'] ) { $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); @@ -555,9 +555,9 @@ class Parsedown protected function blockList($Line, array $CurrentBlock = null) { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}[.\)]'); + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); - if (preg_match('/^('.$pattern.'([ ]+|$))(.*)/', $Line['text'], $matches)) + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) { $contentIndent = strlen($matches[2]); @@ -587,6 +587,7 @@ class Parsedown 'elements' => array(), ), ); + $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); if ($name === 'ol') { @@ -634,10 +635,10 @@ class Parsedown and ( ( $Block['data']['type'] === 'ol' - and preg_match('/^[0-9]+'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) ) or ( $Block['data']['type'] === 'ul' - and preg_match('/^'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) + and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) ) ) ) { @@ -699,7 +700,7 @@ class Parsedown if ( ! isset($Block['interrupted'])) { - $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); $Block['li']['handler']['argument'] []= $text; @@ -728,7 +729,7 @@ class Parsedown protected function blockQuote($Line) { - if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { $Block = array( 'element' => array( @@ -752,7 +753,7 @@ class Parsedown return; } - if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { $Block['element']['handler']['argument'] []= $matches[1]; @@ -772,7 +773,7 @@ class Parsedown protected function blockRule($Line) { - if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) + if (preg_match('/^(['.$Line['text'][0].'])([ ]*+\1){2,}+[ ]*+$/', $Line['text'])) { $Block = array( 'element' => array( @@ -814,7 +815,7 @@ class Parsedown return; } - if (preg_match('/^<[\/]?+(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) { $element = strtolower($matches[1]); @@ -852,7 +853,7 @@ class Parsedown protected function blockReference($Line) { - if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + if (preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)) { $id = strtolower($matches[1]); @@ -1013,7 +1014,7 @@ class Parsedown $row = trim($row); $row = trim($row, '|'); - preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); $cells = array_slice($matches[0], 0, count($Block['alignments'])); @@ -1236,10 +1237,10 @@ class Parsedown { $marker = $Excerpt['text'][0]; - if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), @@ -1395,7 +1396,7 @@ class Parsedown return; } - if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) { $Element['attributes']['href'] = $matches[1]; @@ -1444,7 +1445,7 @@ class Parsedown return; } - if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), @@ -1452,7 +1453,7 @@ class Parsedown ); } - if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), @@ -1460,7 +1461,7 @@ class Parsedown ); } - if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), @@ -1512,7 +1513,7 @@ class Parsedown return; } - if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + if (preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) { $url = $matches[0][0]; @@ -1534,7 +1535,7 @@ class Parsedown protected function inlineUrlTag($Excerpt) { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) { $url = $matches[1]; @@ -1939,8 +1940,8 @@ class Parsedown ); protected $StrongRegex = array( - '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', - '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', ); protected $EmRegex = array( @@ -1948,7 +1949,7 @@ class Parsedown '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', ); - protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; protected $voidElements = array( 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', From 2faba6fef5de5611c0f167e8a6a3bbf7360bc7b5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sun, 8 Apr 2018 23:55:08 +0100 Subject: [PATCH 217/238] Remove unneeded complete function --- Parsedown.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 057dd49..1dc4f0e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -383,15 +383,6 @@ class Parsedown } } - protected function blockCodeComplete($Block) - { - $text = $Block['element']['element']['text']; - - $Block['element']['element']['text'] = $text; - - return $Block; - } - # # Comment From c45e41950fc2969b24d41223605a661195636a36 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 00:05:12 +0100 Subject: [PATCH 218/238] Use standard library over while loop --- Parsedown.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 1dc4f0e..0057566 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -199,12 +199,7 @@ class Parsedown } } - $indent = 0; - - while (isset($line[$indent]) and $line[$indent] === ' ') - { - $indent ++; - } + $indent = strspn($line, ' '); $text = $indent > 0 ? substr($line, $indent) : $line; From 3ea08140b62158fbd6d468672afb60bb4c52d9fa Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 00:46:26 +0100 Subject: [PATCH 219/238] Remove use of array --- Parsedown.php | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 0057566..22c2558 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -182,21 +182,17 @@ class Parsedown continue; } - if (strpos($line, "\t") !== false) - { - $parts = explode("\t", $line); + for ( + $beforeTab = strstr($line, "\t", true); + $beforeTab !== false; + $beforeTab = strstr($line, "\t", true) + ) { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; - $line = $parts[0]; - - unset($parts[0]); - - foreach ($parts as $part) - { - $shortage = 4 - mb_strlen($line, 'utf-8') % 4; - - $line .= str_repeat(' ', $shortage); - $line .= $part; - } + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; } $indent = strspn($line, ' '); From b53aa74a72b43ab3e13d7792a122ac607378e8e8 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 00:55:36 +0100 Subject: [PATCH 220/238] Use standard library function --- Parsedown.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 22c2558..794438a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -497,12 +497,7 @@ class Parsedown protected function blockHeader($Line) { - $level = 1; - - while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') - { - $level ++; - } + $level = strspn($Line['text'], '#'); if ($level > 6) { From e74a5bd7ed8ac3eaf0e95bca6d1096bdbad6bd4f Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 01:46:36 +0100 Subject: [PATCH 221/238] In theory PHP stores the length of strings, so looking this up should be quick --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 794438a..a973478 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1102,7 +1102,7 @@ class Parsedown { $marker = $excerpt[0]; - $markerPosition = strpos($text, $marker); + $markerPosition = strlen($text) - strlen($excerpt); $Excerpt = array('text' => $excerpt, 'context' => $text); From d2dd736e1b41b65101098c9eeebdd7180dea24e3 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 02:30:22 +0100 Subject: [PATCH 222/238] Remove regex from fenced code block Also remove unused function --- Parsedown.php | 73 +++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index a973478..fc9533f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -424,33 +424,42 @@ class Parsedown protected function blockFencedCode($Line) { - if (preg_match('/^(['.$Line['text'][0].']{3,}+)[ ]*+([^`]++)?+[ ]*+$/', $Line['text'], $matches)) + $marker = $Line['text'][0]; + + $openerLength = strspn($Line['text'], $marker); + + if ($openerLength < 3) { - $Element = array( - 'name' => 'code', - 'text' => '', - ); - - if (isset($matches[2])) - { - $class = "language-{$matches[2]}"; - - $Element['attributes'] = array( - 'class' => $class, - ); - } - - $Block = array( - 'char' => $Line['text'][0], - 'openerLength' => mb_strlen($matches[1]), - 'element' => array( - 'name' => 'pre', - 'element' => $Element, - ), - ); - - return $Block; + return; } + + $infostring = trim(substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) + { + return; + } + + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if ($infostring !== '') + { + $Element['attributes'] = array('class' => "language-$infostring"); + } + + $Block = array( + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => array( + 'name' => 'pre', + 'element' => $Element, + ), + ); + + return $Block; } protected function blockFencedCodeContinue($Line, $Block) @@ -467,9 +476,8 @@ class Parsedown unset($Block['interrupted']); } - if ( - preg_match('/^(['.preg_quote($Block['char']).']{3,}+)[ ]*+$/', $Line['text'], $matches) - and mb_strlen($matches[1]) >= $Block['openerLength'] + if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + and chop(substr($Line['text'], $len), ' ') === '' ) { $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); @@ -483,15 +491,6 @@ class Parsedown return $Block; } - protected function blockFencedCodeComplete($Block) - { - $text = $Block['element']['element']['text']; - - $Block['element']['element']['text'] = $text; - - return $Block; - } - # # Header From 7e15d99d90f99066d39c4771bb061a7682ed0cb5 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 02:31:36 +0100 Subject: [PATCH 223/238] Remove regex from block rule --- Parsedown.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index fc9533f..210e721 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -749,7 +749,9 @@ class Parsedown protected function blockRule($Line) { - if (preg_match('/^(['.$Line['text'][0].'])([ ]*+\1){2,}+[ ]*+$/', $Line['text'])) + $marker = $Line['text'][0]; + + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') { $Block = array( 'element' => array( From 450a74fedf2986987512b0f638b971ba2ddd1fb7 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 02:32:01 +0100 Subject: [PATCH 224/238] More expensive statement last --- Parsedown.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 210e721..6b7ff86 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -773,10 +773,8 @@ class Parsedown return; } - if ( - chop(chop($Line['text'], ' '), $Line['text'][0]) === '' - and $Line['indent'] < 4 - ) { + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') + { $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; return $Block; From 726d4ef44a676de2e2af0c3b1861c9429a257196 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 9 Apr 2018 02:32:23 +0100 Subject: [PATCH 225/238] Sanity checks before starting regex engine --- Parsedown.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 6b7ff86..7fa06e3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -829,8 +829,9 @@ class Parsedown protected function blockReference($Line) { - if (preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)) - { + if (strpos($Line['text'], ']') !== false + and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { $id = strtolower($matches[1]); $Data = array( @@ -1448,8 +1449,9 @@ class Parsedown protected function inlineSpecialCharacter($Excerpt) { - if (preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] !== ' ' and strpos($Excerpt['text'], ';') !== false + and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { return array( 'element' => array('rawHtml' => "&{$matches[1]};"), 'extent' => strlen($matches[0]), @@ -1489,8 +1491,9 @@ class Parsedown return; } - if (preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) - { + if (strpos($Excerpt['context'], 'http') !== false + and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { $url = $matches[0][0]; $Inline = array( From 88a3f31dd74a8ef3b7c7f2bfb4abd03343d3f3bd Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 12 Apr 2018 19:33:01 +0100 Subject: [PATCH 226/238] Rewrite as one statement --- Parsedown.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 7fa06e3..153dc74 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1196,16 +1196,12 @@ class Parsedown $safeText = self::escape($text, true); - if ($this->breaksEnabled) - { - $Inline['element']['rawHtml'] = preg_replace('/[ ]*+\n/', "
    \n", $safeText); - $Inline['element']['allowRawHtmlInSafeMode'] = true; - } - else - { - $Inline['element']['rawHtml'] = preg_replace('/(?:[ ]*+\\\\|[ ]{2,}+)\n/', "
    \n", $safeText); - $Inline['element']['allowRawHtmlInSafeMode'] = true; - } + $Inline['element']['rawHtml'] = preg_replace( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + "
    \n", + $safeText + ); + $Inline['element']['allowRawHtmlInSafeMode'] = true; return $Inline; } From b75fd409ffb0a935a02a8af3c84c0c9e92b49dd3 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 12 Apr 2018 21:10:09 +0100 Subject: [PATCH 227/238] Must unset text key so that our destination is preferred as content --- Parsedown.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Parsedown.php b/Parsedown.php index 5baf6ab..87f9337 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1585,6 +1585,7 @@ class Parsedown { $function = $Element['handler']; $argument = $Element['text']; + unset($Element['text']); $destination = 'rawHtml'; } else From 2bf7ca41a007fe144bf673eed24e92956a746dbe Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 12 Apr 2018 21:25:50 +0100 Subject: [PATCH 228/238] Add compat for extensions using old `markup` key. --- Parsedown.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 87f9337..986b86e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -264,7 +264,7 @@ class Parsedown { if (isset($CurrentBlock)) { - $Elements[] = $CurrentBlock['element']; + $Elements[] = $this->extractElement($CurrentBlock); } $Block['identified'] = true; @@ -296,7 +296,7 @@ class Parsedown { if (isset($CurrentBlock)) { - $Elements[] = $CurrentBlock['element']; + $Elements[] = $this->extractElement($CurrentBlock); } $CurrentBlock = $this->paragraph($Line); @@ -316,7 +316,7 @@ class Parsedown if (isset($CurrentBlock)) { - $Elements[] = $CurrentBlock['element']; + $Elements[] = $this->extractElement($CurrentBlock); } # ~ @@ -324,6 +324,16 @@ class Parsedown return $Elements; } + protected function extractElement(array $Component) + { + if ( ! isset($Component['element']) and isset($Component['markup'])) + { + $Component['element'] = array('rawHtml' => $Component['markup']); + } + + return $Component['element']; + } + protected function isBlockContinuable($Type) { return method_exists($this, 'block'.$Type.'Continue'); @@ -1169,7 +1179,7 @@ class Parsedown $Elements[] = $InlineText['element']; # compile the inline - $Elements[] = $Inline['element']; + $Elements[] = $this->extractElement($Inline); # remove the examined text $text = substr($text, $Inline['position'] + $Inline['extent']); From 3e70819a20d950623f15ab040b4ef76cacdc95ed Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 12 Apr 2018 22:16:25 +0100 Subject: [PATCH 229/238] Readability improvements, thanks @PhrozenByte --- Parsedown.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 153dc74..4ec6a7a 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -182,11 +182,8 @@ class Parsedown continue; } - for ( - $beforeTab = strstr($line, "\t", true); - $beforeTab !== false; - $beforeTab = strstr($line, "\t", true) - ) { + while (($beforeTab = strstr($line, "\t", true)) !== false) + { $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; $line = $beforeTab @@ -207,7 +204,8 @@ class Parsedown if (isset($CurrentBlock['continuable'])) { - $Block = $this->{"block{$CurrentBlock['type']}Continue"}($Line, $CurrentBlock); + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); if (isset($Block)) { @@ -219,7 +217,8 @@ class Parsedown { if ($this->isBlockCompletable($CurrentBlock['type'])) { - $CurrentBlock = $this->{"block{$CurrentBlock['type']}Complete"}($CurrentBlock); + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); } } } @@ -300,7 +299,8 @@ class Parsedown if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { - $CurrentBlock = $this->{"block{$CurrentBlock['type']}Complete"}($CurrentBlock); + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); } # ~ @@ -317,12 +317,12 @@ class Parsedown protected function isBlockContinuable($Type) { - return method_exists($this, "block${Type}Continue"); + return method_exists($this, 'block' . $Type . 'Continue'); } protected function isBlockCompletable($Type) { - return method_exists($this, "block${Type}Complete"); + return method_exists($this, 'block' . $Type . 'Complete'); } # @@ -1138,10 +1138,11 @@ class Parsedown # cause the new element to 'inherit' our non nestables - foreach ($nonNestables as $nonNestable) - { - $Inline['element']['nonNestables'][] = $nonNestable; - } + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; # the text that comes before the inline $unmarkedText = substr($text, 0, $Inline['position']); From 7f4318dbdb5eed781a259ace08b5d5f31ac65208 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 12 Apr 2018 22:22:53 +0100 Subject: [PATCH 230/238] =?UTF-8?q?PHP=205.3=20=3D=3D=20=F0=9F=92=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Parsedown.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 4ec6a7a..74f1d0e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1094,7 +1094,10 @@ class Parsedown { $Elements = array(); - $nonNestables = array_combine($nonNestables, $nonNestables); + $nonNestables = (empty($nonNestables) + ? array() + : array_combine($nonNestables, $nonNestables) + ); # $excerpt is based on the first occurrence of a marker From a9764ec90f40cc440f9afc9b0f69c54b0d478c2d Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Sat, 14 Apr 2018 15:27:06 +0100 Subject: [PATCH 231/238] Remove complex string interpolation expressions --- Parsedown.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 74f1d0e..46062df 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -409,7 +409,7 @@ class Parsedown return; } - $Block['element']['rawHtml'] .= "\n{$Line['body']}"; + $Block['element']['rawHtml'] .= "\n" . $Line['body']; if (strpos($Line['text'], '-->') !== false) { @@ -486,7 +486,7 @@ class Parsedown return $Block; } - $Block['element']['element']['text'] .= "\n{$Line['body']}"; + $Block['element']['element']['text'] .= "\n" . $Line['body']; return $Block; } @@ -819,7 +819,7 @@ class Parsedown return; } - $Block['element']['rawHtml'] .= "\n{$Line['body']}"; + $Block['element']['rawHtml'] .= "\n" . $Line['body']; return $Block; } @@ -1011,7 +1011,7 @@ class Parsedown if (isset($Block['alignments'][$index])) { $Element['attributes'] = array( - 'style' => "text-align: {$Block['alignments'][$index]};", + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', ); } @@ -1453,7 +1453,7 @@ class Parsedown and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) ) { return array( - 'element' => array('rawHtml' => "&{$matches[1]};"), + 'element' => array('rawHtml' => '&' . $matches[1] . ';'), 'extent' => strlen($matches[0]), ); } @@ -1656,7 +1656,7 @@ class Parsedown if ($hasName) { - $markup .= "<{$Element['name']}"; + $markup .= '<' . $Element['name']; if (isset($Element['attributes'])) { @@ -1714,7 +1714,7 @@ class Parsedown } } - $markup .= $hasName ? "" : ''; + $markup .= $hasName ? '' : ''; } elseif ($hasName) { From 113c6d2b212a70d91d97c94965b42da5b946ba24 Mon Sep 17 00:00:00 2001 From: Attila Vachter Date: Mon, 23 Apr 2018 15:09:30 +0200 Subject: [PATCH 232/238] Tilde characters may be escaped --- Parsedown.php | 2 +- test/data/strikethrough.html | 3 ++- test/data/strikethrough.md | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index 986b86e..3232a1f 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1955,7 +1955,7 @@ class Parsedown # Read-Only protected $specialCharacters = array( - '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' ); protected $StrongRegex = array( diff --git a/test/data/strikethrough.html b/test/data/strikethrough.html index 2a9da98..14bb5a2 100644 --- a/test/data/strikethrough.html +++ b/test/data/strikethrough.html @@ -1,3 +1,4 @@

    strikethrough

    here's one followed by another one

    -

    ~~ this ~~ is not one neither is ~this~

    \ No newline at end of file +

    ~~ this ~~ is not one neither is ~this~

    +

    escaped ~~this~~

    \ No newline at end of file diff --git a/test/data/strikethrough.md b/test/data/strikethrough.md index d169144..83d5b35 100644 --- a/test/data/strikethrough.md +++ b/test/data/strikethrough.md @@ -2,4 +2,6 @@ here's ~~one~~ followed by ~~another one~~ -~~ this ~~ is not one neither is ~this~ \ No newline at end of file +~~ this ~~ is not one neither is ~this~ + +escaped \~\~this\~\~ \ No newline at end of file From fd95703da589461125a4dbf3782967761e7ccc42 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 7 May 2018 14:25:27 +0100 Subject: [PATCH 233/238] Version bump --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index b5f8005..a2c4f98 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.8.0-beta-1'; + const version = '1.8.0-beta-3'; # ~ From 9eed1104e70ffc7cb84029922916661ded1170a4 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 8 May 2018 21:54:30 +0100 Subject: [PATCH 234/238] Intepret special "hidden" key as an empty element --- Parsedown.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index a2c4f98..a35dda3 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -317,9 +317,16 @@ class Parsedown protected function extractElement(array $Component) { - if ( ! isset($Component['element']) and isset($Component['markup'])) + if ( ! isset($Component['element'])) { - $Component['element'] = array('rawHtml' => $Component['markup']); + if (isset($Component['markup'])) + { + $Component['element'] = array('rawHtml' => $Component['markup']); + } + elseif (isset($Component['hidden'])) + { + $Component['element'] = array(); + } } return $Component['element']; From 4686daf8c29278cc1cd22697e43241846bb88466 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 8 May 2018 22:32:57 +0100 Subject: [PATCH 235/238] Preserve plain-text in AST to avoid blinding extensions to it --- Parsedown.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Parsedown.php b/Parsedown.php index a35dda3..3eb0f22 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -1215,14 +1215,14 @@ class Parsedown 'element' => array(), ); - $safeText = self::escape($text, true); - - $Inline['element']['rawHtml'] = preg_replace( + $Inline['element']['elements'] = self::pregReplaceElements( $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', - "
    \n", - $safeText + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text ); - $Inline['element']['allowRawHtmlInSafeMode'] = true; return $Inline; } From 1f69f7e69797104e73fdceaae22a4b4e3978f0a4 Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Tue, 8 May 2018 22:46:15 +0100 Subject: [PATCH 236/238] Bump version --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index 3eb0f22..57e10fa 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.8.0-beta-3'; + const version = '1.8.0-beta-4'; # ~ From 0b1e6b8c86da2ed1975defa4ce2514e39a1acc5c Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Thu, 7 Jun 2018 19:47:09 +0100 Subject: [PATCH 237/238] Restore existence of protected API methods --- Parsedown.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Parsedown.php b/Parsedown.php index 57e10fa..de8679e 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -391,6 +391,11 @@ class Parsedown } } + protected function blockCodeComplete($Block) + { + return $Block; + } + # # Comment @@ -508,6 +513,11 @@ class Parsedown return $Block; } + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + # # Header From c26a2ee4bf8ba0270daab7da0353f2525ca6564a Mon Sep 17 00:00:00 2001 From: Aidan Woods Date: Mon, 11 Jun 2018 19:15:32 +0100 Subject: [PATCH 238/238] Bump beta version --- Parsedown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parsedown.php b/Parsedown.php index de8679e..9008a89 100644 --- a/Parsedown.php +++ b/Parsedown.php @@ -17,7 +17,7 @@ class Parsedown { # ~ - const version = '1.8.0-beta-4'; + const version = '1.8.0-beta-5'; # ~