fix: separate block elements from the inline content before them
An image or link right before a heading or paragraph ended up on the same line, e.g. `### A`. Fixes #259
This commit is contained in:
1 parent
6b7a074e8d
commit
1bb05d0606
2 files changed
+29
-3
No files matched your search
+25
-3
@@ -16,6 +16,11 @@ namespace League\HTMLToMarkdown;
|
|||||||
*/
|
*/
|
||||||
class HtmlConverter implements HtmlConverterInterface
|
class HtmlConverter implements HtmlConverterInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Blocks which start a new paragraph when they come right after inline content
|
||||||
|
*/
|
||||||
|
private const SEPARATED_BLOCK_TAGS = ['blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'p', 'pre'];
|
||||||
|
|
||||||
/** @var Environment */
|
/** @var Environment */
|
||||||
protected $environment;
|
protected $environment;
|
||||||
|
|
||||||
@@ -173,13 +178,17 @@ class HtmlConverter implements HtmlConverterInterface
|
|||||||
*
|
*
|
||||||
* Finds children of each node and convert those to #text nodes containing their Markdown equivalent,
|
* Finds children of each node and convert those to #text nodes containing their Markdown equivalent,
|
||||||
* starting with the innermost element and working up to the outermost element.
|
* starting with the innermost element and working up to the outermost element.
|
||||||
|
*
|
||||||
|
* @param bool $followsInlineContent Whether the element comes right after inline content, like an image, on the same line
|
||||||
|
*
|
||||||
|
* @return string The Markdown the element was replaced with
|
||||||
*/
|
*/
|
||||||
private function convertChildren(ElementInterface $element): void
|
private function convertChildren(ElementInterface $element, bool $followsInlineContent = false): string
|
||||||
{
|
{
|
||||||
// Don't convert HTML code inside <code> and <pre> blocks to Markdown - that should stay as HTML
|
// Don't convert HTML code inside <code> and <pre> blocks to Markdown - that should stay as HTML
|
||||||
// except if the current node is a code tag, which needs to be converted by the CodeConverter.
|
// except if the current node is a code tag, which needs to be converted by the CodeConverter.
|
||||||
if ($element->isDescendantOf(['pre', 'code']) && $element->getTagName() !== 'code') {
|
if ($element->isDescendantOf(['pre', 'code']) && $element->getTagName() !== 'code') {
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give converter a chance to inspect/modify the DOM before children are converted
|
// Give converter a chance to inspect/modify the DOM before children are converted
|
||||||
@@ -190,18 +199,31 @@ class HtmlConverter implements HtmlConverterInterface
|
|||||||
|
|
||||||
// If the node has children, convert those to Markdown first
|
// If the node has children, convert those to Markdown first
|
||||||
if ($element->hasChildren()) {
|
if ($element->hasChildren()) {
|
||||||
|
$childFollowsInlineContent = false;
|
||||||
foreach ($element->getChildren() as $child) {
|
foreach ($element->getChildren() as $child) {
|
||||||
$this->convertChildren($child);
|
$childMarkdown = $this->convertChildren($child, $childFollowsInlineContent);
|
||||||
|
if ($child->getTagName() === '#comment' || \trim($childMarkdown, ' ') === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$childFollowsInlineContent = ! $child->isBlock() && \substr(\rtrim($childMarkdown, ' '), -1) !== "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that child nodes have been converted, convert the original node
|
// Now that child nodes have been converted, convert the original node
|
||||||
$markdown = $this->convertToMarkdown($element);
|
$markdown = $this->convertToMarkdown($element);
|
||||||
|
|
||||||
|
// These blocks only add newlines after themselves, so one that follows inline content, like an image, needs them before it too
|
||||||
|
if ($followsInlineContent && \in_array($element->getTagName(), self::SEPARATED_BLOCK_TAGS, true) && \trim($markdown) !== '') {
|
||||||
|
$markdown = "\n\n" . $markdown;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a DOM text node containing the Markdown equivalent of the original node
|
// Create a DOM text node containing the Markdown equivalent of the original node
|
||||||
|
|
||||||
// Replace the old $node e.g. '<h3>Title</h3>' with the new $markdown_node e.g. '### Title'
|
// Replace the old $node e.g. '<h3>Title</h3>' with the new $markdown_node e.g. '### Title'
|
||||||
$element->setFinalMarkdown($markdown);
|
$element->setFinalMarkdown($markdown);
|
||||||
|
|
||||||
|
return $markdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ class HtmlConverterTest extends TestCase
|
|||||||
public function testImage(): void
|
public function testImage(): void
|
||||||
{
|
{
|
||||||
$this->assertHtmlGivesMarkdown('<img src="/path/img.jpg" alt="alt text" title="Title" />', '');
|
$this->assertHtmlGivesMarkdown('<img src="/path/img.jpg" alt="alt text" title="Title" />', '');
|
||||||
|
$this->assertHtmlGivesMarkdown("<img src=\"a.png\" />\n<h3>A new header</h3>", "\n\n### A new header");
|
||||||
|
$this->assertHtmlGivesMarkdown('<img src="a.png" /><p>Paragraph</p>', "\n\nParagraph");
|
||||||
|
$this->assertHtmlGivesMarkdown('<a href="b.html"><img src="a.png" /></a><h3>A new header</h3>', "[](b.html)\n\n### A new header");
|
||||||
|
$this->assertHtmlGivesMarkdown('<blockquote><img src="a.png" /><p>Paragraph</p></blockquote>', "> \n> \n> Paragraph");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAnchor(): void
|
public function testAnchor(): void
|
||||||
|
|||||||
Reference in new issue
Block a user