Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip

- Fixed a line of only `=` or `-` after a line break not being escaped, which made a heading out of the line before it, such as in `<p>Foo<br>=</p>` (#226). Lines of `-` were already escaped inside of paragraphs, but not inside of list items, blockquotes or headings.
- Fixed emphasis next to an empty one of the same kind losing one of its delimiters, such as `<strong> </strong><strong>hello</strong>` becoming ` hello**` (#252). Emphasis next to one of the same kind which is removed with `remove_nodes` also lost one.
- Fixed `&lt;/body&gt;` or `&lt;/html&gt;` in code or comments causing the document's own closing tags to be output after it, and everything from `&lt;!DOCTYPE ` in code up to the next `>` being removed, which could be the rest of the document (#254)

## [5.2.3] - 2026-10-19

Expand Down
7 changes: 4 additions & 3 deletions src/HtmlConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,8 @@ private function endsOnBlankLine(ElementInterface $element, ConverterInterface $
protected function sanitize(string $markdown): string
{
$markdown = \html_entity_decode($markdown, ENT_QUOTES, 'UTF-8');
$markdown = \preg_replace('/<!DOCTYPE [^>]+>/', '', $markdown); // Strip doctype declaration
// Only the declaration itself, which comes first, as code could contain the same text once decoded
$markdown = \preg_replace('/^<!DOCTYPE [^>]+>/', '', $markdown);
\assert($markdown !== null);
$markdown = \trim($markdown); // Remove blank spaces at the beggining of the html

Expand All @@ -668,8 +669,8 @@ protected function sanitize(string $markdown): string
$markdown = \substr($markdown, \strlen($tag));
}
} else {
// Closing tags
if (\strpos($markdown, $tag) === \strlen($markdown) - \strlen($tag)) {
// Closing tags, which code before them could also contain once decoded
if (\substr($markdown, -\strlen($tag)) === $tag) {
$markdown = \substr($markdown, 0, -\strlen($tag));
}
}
Expand Down
16 changes: 16 additions & 0 deletions tests/HtmlConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,22 @@ public function testCodeContainingTextWhichLooksLikeItsOwnTags(): void
$this->assertHtmlGivesMarkdown('<p><code class="a>b">x</code></p>', '`x`');
}

/**
* @see https://github.com/thephpleague/html-to-markdown/issues/254
*/
public function testCodeContainingTextWhichLooksLikeDocumentTags(): void
{
$this->assertHtmlGivesMarkdown('<pre><code>&lt;/body&gt;</code></pre>', "```\n</body>\n```");
$this->assertHtmlGivesMarkdown('<pre><code>&lt;/html&gt;</code></pre>', "```\n</html>\n```");
$this->assertHtmlGivesMarkdown('<pre><code>&lt;/head&gt;&lt;/body&gt;&lt;/html&gt;</code></pre>', "```\n</head></body></html>\n```");
$this->assertHtmlGivesMarkdown('<pre><code>&lt;html&gt;&lt;body&gt;</code></pre>', "```\n<html><body>\n```");
$this->assertHtmlGivesMarkdown('<p>a <code>&lt;/body&gt;&lt;/html&gt;</code></p>', 'a `</body></html>`');
$this->assertHtmlGivesMarkdown('<pre><code>&lt;!DOCTYPE html&gt;</code></pre>', "```\n<!DOCTYPE html>\n```");
$this->assertHtmlGivesMarkdown('<!DOCTYPE html><pre><code>&lt;!DOCTYPE html&gt;&lt;/html&gt;</code></pre>', "```\n<!DOCTYPE html></html>\n```");
$this->assertHtmlGivesMarkdown('<p>Use <code>&lt;!DOCTYPE html</code> first.</p><p>a &gt; b</p>', "Use `<!DOCTYPE html` first.\n\na &gt; b");
$this->assertHtmlGivesMarkdown('<p>x</p><!-- </body> -->', "x\n\n<!-- </body> -->", ['preserve_comments' => true]);
}

public function testNestedCode(): void
{
$this->assertHtmlGivesMarkdown('<pre><pre>a</pre></pre>', "```\na\n```");
Expand Down
Loading