1
0
mirror of https://github.com/erusev/parsedown.git synced 2023-08-10 21:13:06 +03:00

Compare commits

..

51 Commits

Author SHA1 Message Date
d60bcdc469 Bump version 2019-03-17 17:19:46 +00:00
c390a9e406 Merge pull request #700 from aidantwoods/fix/spaces-in-class-names-1.7.x
[1.7.x] Fix spaces in class names
2019-03-17 17:14:45 +00:00
0f1e9da8f4 Fix test platforms 2019-03-17 17:05:15 +00:00
bc003952fc [1.7.x] Fix spaces in class names 2019-03-17 16:49:45 +00:00
92e9c27ba0 Merge pull request #563 from luizbills/master
bump version
2018-03-08 01:11:30 +00:00
9857334186 bump version 2018-03-07 22:04:55 -03:00
ae7e8e5067 bump version 2018-03-07 21:51:35 -03:00
253822057a refactor who uses it section in readme a bit more 2018-03-02 17:46:45 +02:00
a18bf495ed refactor who uses it section in readme 2018-03-02 17:40:21 +02:00
e5bf9560d7 add Laravel to who uses it 2018-03-02 17:37:16 +02:00
33b51eaefa Fix typo 2018-03-02 01:16:27 +00:00
d686a50292 Merge pull request #557 from aidantwoods/documentation/safe-mode
Talk about safe mode in the README
2018-03-01 19:59:44 +00:00
f3068df45a Remove extra line breaks 2018-03-01 19:54:58 +00:00
9b1f54b9d3 Lets be consistent with hyphenation 2018-03-01 18:45:38 +00:00
90439ef882 Rewrite section 2018-03-01 18:44:11 +00:00
97dd037e6f Merge pull request #561 from PhrozenByte/patch-3
Add mbstring dependency to composer.json
2018-02-28 23:47:01 +00:00
fa89f0d743 Add mbstring dependency to composer.json 2018-02-28 20:42:25 +01:00
d638fd8a25 Merge pull request #560 from PhrozenByte/patch-2
Travis: Issue build error when Parsedown::version isn't up-to-date
2018-02-28 19:09:57 +00:00
cc53d5ae29 Travis: Issue build error when Parsedown::version isn't up-to-date 2018-02-28 20:04:45 +01:00
45f40696f6 Merge pull request #559 from PhrozenByte/patch-1
Update "Who uses it"
2018-02-28 18:07:37 +00:00
e8f3d4efc0 Merge pull request #558 from harikt/issue-232
Add test case to make sure issue 232 no longer exists
2018-02-28 18:02:14 +00:00
096e164756 Update README.md
Sort "Who uses it" alphabetically, add Laravel + Pico
2018-02-28 18:59:34 +01:00
e2f3961f80 Add test case to make sure issue 232 no longer exists 2018-02-28 23:25:38 +05:30
e941dcc3f0 Merge pull request #525 from aidantwoods/fix/infostring
Properly support fenced code block infostring
2018-02-28 17:06:25 +00:00
c192001a7e Merge pull request #433 from aidantwoods/patch-3
Fix Issue #358 – preventing double nested links
2018-02-28 17:05:58 +00:00
48a053fe29 Merge pull request #423 from PhrozenByte/bugfix/CommonMarkTest
Fix CommonMark test
2018-02-28 17:05:24 +00:00
5057e505d8 Merge pull request #475 from aidantwoods/loose-lists
Loose lists
2018-02-28 17:05:00 +00:00
ad62bf5a6f Talk about safe mode in the README 2018-02-28 17:03:46 +00:00
722b776684 Test multiple multiline lists 2018-01-29 14:38:19 +01:00
7fd92a8fbd update tests 2018-01-29 14:38:19 +01:00
0e1043a8d6 consistent li items for loose list 2018-01-29 14:38:19 +01:00
03e1a6ac02 Merge branch 'master' into bugfix/CommonMarkTest
Conflicts:
	.travis.yml
	test/CommonMarkTest.php
	test/ParsedownTest.php
	test/bootstrap.php
2017-11-14 22:09:25 +01:00
4404201175 Properly support fenced code block infostring
Reference: http://spec.commonmark.org/0.28/#info-string
2017-08-20 10:28:46 +01:00
ae0211a84c Travis: Add PHP nightly 2016-10-13 22:17:03 +02:00
a9f696f7bb Improve CommonMark spec example regex
CommonMark spec example [#170](http://spec.commonmark.org/0.26/#example-170) has a empty HTML result.
2016-10-13 22:16:46 +02:00
2423644d72 Move test/CommonMarkTest.php to test/CommonMarkTestStrict.php
Add parameter `$id` to CommonMark tests
2016-10-12 02:02:55 +02:00
be671e72a3 Don't let Travis skip Parsedown's phpunit tests 2016-10-09 14:21:17 +02:00
f0587d41a9 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.
2016-10-09 14:17:03 +02:00
3aef89b399 Line handler may prevent specified element nesting
Swap `under_scores` for `camelCasing`
2016-10-08 17:54:04 +01:00
543a6c4175 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.
2016-10-04 18:59:36 +01:00
a81aedeb10 Line handler may prevent specified element nesting
Removed granularity controls – elements are assumed to be non nestable indefinitely once declared.
2016-10-04 15:27:11 +01:00
50952b3243 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.
2016-10-02 18:26:13 +01:00
4d3600f273 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.
2016-10-02 17:37:08 +01:00
d6d5f53ff4 Fix Issue #358 – preventing double nested links
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.
2016-10-01 15:56:14 +01:00
73dbe2fd17 Remove PHPUnit bootstrap in favour of composer 2016-09-05 22:04:46 +02:00
33a23fbfb2 Refactor PHPUnit bootstrap
This allows Parsedown extensions (like Parsedown Extra) to reuse existing Parsedown tests. See erusev/parsedown-extra#96 for details.
2016-09-05 21:10:23 +02:00
228d5f4754 Improve test/CommonMarkTestWeak.php 2016-09-05 15:31:07 +02:00
2cacfb8da4 Improve test/CommonMarkTestWeak.php 2016-09-05 15:17:52 +02:00
d33e736fa3 Add test/CommonMarkTestWeak.php 2016-09-05 14:38:47 +02:00
3a46a31e09 Fix test/CommonMarkTest.php example regex 2016-09-05 14:37:34 +02:00
e1bcc1c472 Fix test/CommonMarkTest.php 2016-09-05 04:51:28 +02:00
18 changed files with 285 additions and 102 deletions

View File

@ -12,16 +12,17 @@ matrix:
- php: 5.6 - php: 5.6
- php: 7.0 - php: 7.0
- php: 7.1 - php: 7.1
- php: 7.2
- php: 7.3
- php: nightly - php: nightly
- php: hhvm
- php: hhvm-nightly
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- php: nightly - php: nightly
- php: hhvm-nightly
before_script: install:
- composer install --prefer-dist --no-interaction --no-progress - composer install --prefer-dist --no-interaction --no-progress
script: script:
- vendor/bin/phpunit - vendor/bin/phpunit
- vendor/bin/phpunit test/CommonMarkTestWeak.php || true
- '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"Parsedown.php\"); echo Parsedown::version;")" ]'

View File

@ -17,7 +17,7 @@ class Parsedown
{ {
# ~ # ~
const version = '1.6.0'; const version = '1.7.2';
# ~ # ~
@ -420,7 +420,7 @@ class Parsedown
protected function blockFencedCode($Line) 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( $Element = array(
'name' => 'code', 'name' => 'code',
@ -429,7 +429,21 @@ class Parsedown
if (isset($matches[1])) if (isset($matches[1]))
{ {
$class = 'language-'.$matches[1]; /**
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
* Every HTML element may have a class attribute specified.
* The attribute, if specified, must have a value that is a set
* of space-separated tokens representing the various classes
* that the element belongs to.
* [...]
* The space characters, for the purposes of this specification,
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
* U+000D CARRIAGE RETURN (CR).
*/
$language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
$class = 'language-'.$language;
$Element['attributes'] = array( $Element['attributes'] = array(
'class' => $class, 'class' => $class,
@ -569,6 +583,8 @@ class Parsedown
{ {
$Block['li']['text'] []= ''; $Block['li']['text'] []= '';
$Block['loose'] = true;
unset($Block['interrupted']); unset($Block['interrupted']);
} }
@ -617,6 +633,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 # Quote
@ -1019,7 +1051,7 @@ class Parsedown
# ~ # ~
# #
public function line($text) public function line($text, $nonNestables=array())
{ {
$markup = ''; $markup = '';
@ -1035,6 +1067,13 @@ class Parsedown
foreach ($this->InlineTypes[$marker] as $inlineType) foreach ($this->InlineTypes[$marker] as $inlineType)
{ {
# check to see if the current inline type is nestable in the current context
if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
{
continue;
}
$Inline = $this->{'inline'.$inlineType}($Excerpt); $Inline = $this->{'inline'.$inlineType}($Excerpt);
if ( ! isset($Inline)) if ( ! isset($Inline))
@ -1056,6 +1095,13 @@ class Parsedown
$Inline['position'] = $markerPosition; $Inline['position'] = $markerPosition;
} }
# cause the new element to 'inherit' our non nestables
foreach ($nonNestables as $non_nestable)
{
$Inline['element']['nonNestables'][] = $non_nestable;
}
# the text that comes before the inline # the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']); $unmarkedText = substr($text, 0, $Inline['position']);
@ -1214,6 +1260,7 @@ class Parsedown
$Element = array( $Element = array(
'name' => 'a', 'name' => 'a',
'handler' => 'line', 'handler' => 'line',
'nonNestables' => array('Url', 'Link'),
'text' => null, 'text' => null,
'attributes' => array( 'attributes' => array(
'href' => null, 'href' => null,
@ -1446,9 +1493,14 @@ class Parsedown
{ {
$markup .= '>'; $markup .= '>';
if (!isset($Element['nonNestables']))
{
$Element['nonNestables'] = array();
}
if (isset($Element['handler'])) if (isset($Element['handler']))
{ {
$markup .= $this->{$Element['handler']}($Element['text']); $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
} }
else else
{ {

View File

@ -38,7 +38,32 @@ More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [t
### Security ### 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 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/).
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
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 ### Questions
@ -54,7 +79,7 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w
**Who uses it?** **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). [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?** **How can I help?**

View File

@ -13,12 +13,21 @@
} }
], ],
"require": { "require": {
"php": ">=5.3.0" "php": ">=5.3.0",
"ext-mbstring": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35" "phpunit/phpunit": "^4.8.35"
}, },
"autoload": { "autoload": {
"psr-0": {"Parsedown": ""} "psr-0": {"Parsedown": ""}
},
"autoload-dev": {
"psr-0": {
"TestParsedown": "test/",
"ParsedownTest": "test/",
"CommonMarkTest": "test/",
"CommonMarkTestWeak": "test/"
}
} }
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="test/bootstrap.php" colors="true"> <phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites> <testsuites>
<testsuite> <testsuite>
<file>test/ParsedownTest.php</file> <file>test/ParsedownTest.php</file>

View File

@ -1,77 +0,0 @@
<?php
/**
* Test Parsedown against the CommonMark spec.
*
* Some code based on the original JavaScript test runner by jgm.
*
* @link http://commonmark.org/ CommonMark
* @link http://git.io/8WtRvQ JavaScript test runner
*/
use PHPUnit\Framework\TestCase;
class CommonMarkTest extends TestCase
{
const SPEC_URL = 'https://raw.githubusercontent.com/jgm/stmd/master/spec.txt';
/**
* @dataProvider data
* @param $section
* @param $markdown
* @param $expectedHtml
*/
function test_($section, $markdown, $expectedHtml)
{
$Parsedown = new Parsedown();
$Parsedown->setUrlsLinked(false);
$actualHtml = $Parsedown->text($markdown);
$actualHtml = $this->normalizeMarkup($actualHtml);
$this->assertEquals($expectedHtml, $actualHtml);
}
function data()
{
$spec = file_get_contents(self::SPEC_URL);
$spec = strstr($spec, '<!-- END TESTS -->', true);
$tests = array();
$currentSection = '';
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;
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* Test Parsedown against the CommonMark spec
*
* @link http://commonmark.org/ CommonMark
*/
class CommonMarkTestStrict extends PHPUnit_Framework_TestCase
{
const SPEC_URL = 'https://raw.githubusercontent.com/jgm/CommonMark/master/spec.txt';
protected $parsedown;
protected function setUp()
{
$this->parsedown = new TestParsedown();
$this->parsedown->setUrlsLinked(false);
}
/**
* @dataProvider data
* @param $id
* @param $section
* @param $markdown
* @param $expectedHtml
*/
public function testExample($id, $section, $markdown, $expectedHtml)
{
$actualHtml = $this->parsedown->text($markdown);
$this->assertEquals($expectedHtml, $actualHtml);
}
/**
* @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, '<!-- END TESTS -->', true);
$matches = array();
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 {
$currentId++;
$markdown = str_replace('→', "\t", $match[1]);
$expectedHtml = isset($match[2]) ? str_replace('→', "\t", $match[2]) : '';
$data[$currentId] = array(
'id' => $currentId,
'section' => $currentSection,
'markdown' => $markdown,
'expectedHtml' => $expectedHtml
);
}
}
return $data;
}
}

View File

@ -0,0 +1,63 @@
<?php
require_once(__DIR__ . '/CommonMarkTestStrict.php');
/**
* Test Parsedown against the CommonMark spec, but less aggressive
*
* The resulting HTML markup is cleaned up before comparison, so examples
* which would normally fail due to actually invisible differences (e.g.
* superfluous whitespaces), don't fail. However, cleanup relies on block
* element detection. The detection doesn't work correctly when a element's
* `display` CSS property is manipulated. According to that this test is only
* a interim solution on Parsedown's way to full CommonMark compatibility.
*
* @link http://commonmark.org/ CommonMark
*/
class CommonMarkTestWeak extends CommonMarkTestStrict
{
protected $textLevelElementRegex;
protected function setUp()
{
parent::setUp();
$textLevelElements = $this->parsedown->getTextLevelElements();
array_walk($textLevelElements, function (&$element) {
$element = preg_quote($element, '/');
});
$this->textLevelElementRegex = '\b(?:' . implode('|', $textLevelElements) . ')\b';
}
/**
* @dataProvider data
* @param $id
* @param $section
* @param $markdown
* @param $expectedHtml
*/
public function testExample($id, $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 <pre> 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;
}
}

View File

@ -29,7 +29,7 @@ class ParsedownTest extends TestCase
*/ */
protected function initParsedown() protected function initParsedown()
{ {
$Parsedown = new Parsedown(); $Parsedown = new TestParsedown();
return $Parsedown; return $Parsedown;
} }
@ -136,15 +136,14 @@ color: red;
<p>comment</p> <p>comment</p>
<p>&lt;!-- html comment --&gt;</p> <p>&lt;!-- html comment --&gt;</p>
EXPECTED_HTML; EXPECTED_HTML;
$parsedownWithNoMarkup = new Parsedown();
$parsedownWithNoMarkup = new TestParsedown();
$parsedownWithNoMarkup->setMarkupEscaped(true); $parsedownWithNoMarkup->setMarkupEscaped(true);
$this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml)); $this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml));
} }
public function testLateStaticBinding() public function testLateStaticBinding()
{ {
include __DIR__ . '/TestParsedown.php';
$parsedown = Parsedown::instance(); $parsedown = Parsedown::instance();
$this->assertInstanceOf('Parsedown', $parsedown); $this->assertInstanceOf('Parsedown', $parsedown);

View File

@ -2,4 +2,8 @@
class TestParsedown extends Parsedown class TestParsedown extends Parsedown
{ {
public function getTextLevelElements()
{
return $this->textLevelElements;
}
} }

View File

@ -1,3 +0,0 @@
<?php
include 'Parsedown.php';

View File

@ -3,4 +3,9 @@
$message = 'fenced code block'; $message = 'fenced code block';
echo $message;</code></pre> echo $message;</code></pre>
<pre><code>tilde</code></pre> <pre><code>tilde</code></pre>
<pre><code class="language-php">echo 'language identifier';</code></pre> <pre><code class="language-php">echo 'language identifier';</code></pre>
<pre><code class="language-c#">echo 'language identifier with non words';</code></pre>
<pre><code class="language-html+php">&lt;?php
echo "Hello World";
?&gt;
&lt;a href="http://auraphp.com" &gt;Aura Project&lt;/a&gt;</code></pre>

View File

@ -11,4 +11,15 @@ tilde
```php ```php
echo 'language identifier'; echo 'language identifier';
```
```c#
echo 'language identifier with non words';
```
```html+php
<?php
echo "Hello World";
?>
<a href="http://auraphp.com" >Aura Project</a>
``` ```

View File

@ -0,0 +1,10 @@
<ol>
<li>
<p>One
First body copy</p>
</li>
<li>
<p>Two
Last body copy</p>
</li>
</ol>

View File

@ -0,0 +1,5 @@
1. One
First body copy
2. Two
Last body copy

View File

@ -8,5 +8,7 @@
<li> <li>
<p>li</p> <p>li</p>
</li> </li>
<li>li</li> <li>
<p>li</p>
</li>
</ul> </ul>

View File

@ -2,6 +2,10 @@
<li> <li>
<p>li</p> <p>li</p>
</li> </li>
<li>li</li> <li>
<li>li</li> <p>li</p>
</li>
<li>
<p>li</p>
</li>
</ul> </ul>

View File

@ -2,7 +2,9 @@
<li> <li>
<p>li</p> <p>li</p>
</li> </li>
<li>li</li> <li>
<p>li</p>
</li>
</ul> </ul>
<hr /> <hr />
<ul> <ul>