Compare commits
10
Commits
12911d68c6
...
edc0d7ff06
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc0d7ff06 | ||
|
|
af172f8665 | ||
|
|
87f03af20c | ||
|
|
94062cea69 | ||
|
|
a82cc3b9b6 | ||
|
|
1bb05d0606 | ||
|
|
6b7a074e8d | ||
|
|
806387f3f6 | ||
|
|
710bc5b8ca | ||
|
|
f194c36ae3 |
No files matched your search
@@ -0,0 +1,10 @@
|
||||
# Contributions to thephpleague/html-to-markdown
|
||||
|
||||
Convert HTML to Markdown with PHP
|
||||
|
||||
This repository holds only my commits to the upstream project, with their original author, date and diff.
|
||||
Each of my commits is preceded by a snapshot of the upstream files it touched, so every diff shows exactly what I changed.
|
||||
|
||||
## Merged pull requests
|
||||
|
||||
- [fix: honor start="0" on ordered lists](https://github.com/thephpleague/html-to-markdown/pull/277) (2026-09-07)
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace League\HTMLToMarkdown\Converter;
|
||||
|
||||
use League\HTMLToMarkdown\Coerce;
|
||||
use League\HTMLToMarkdown\Configuration;
|
||||
use League\HTMLToMarkdown\ConfigurationAwareInterface;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class EmphasisConverter implements ConverterInterface, ConfigurationAwareInterface
|
||||
{
|
||||
/** @var Configuration */
|
||||
protected $config;
|
||||
|
||||
protected function getNormTag(?ElementInterface $element): string
|
||||
{
|
||||
if ($element !== null && ! $element->isText()) {
|
||||
$tag = $element->getTagName();
|
||||
if ($tag === 'i' || $tag === 'em') {
|
||||
return 'em';
|
||||
}
|
||||
|
||||
if ($tag === 'b' || $tag === 'strong') {
|
||||
return 'strong';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function setConfig(Configuration $config): void
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$tag = $this->getNormTag($element);
|
||||
$value = $element->getValue();
|
||||
|
||||
// Unlike trim(), this also strips non-breaking spaces, which would keep the delimiters from flanking the text
|
||||
$content = \preg_replace('~^\s+|\s+$~u', '', $value);
|
||||
\assert(\is_string($content));
|
||||
|
||||
if (! $content) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($tag === 'em') {
|
||||
$style = Coerce::toString($this->config->getOption('italic_style'));
|
||||
} else {
|
||||
$style = Coerce::toString($this->config->getOption('bold_style'));
|
||||
}
|
||||
|
||||
$prefix = \preg_match('~^\s~u', $value) === 1 ? ' ' : '';
|
||||
$suffix = \preg_match('~\s$~u', $value) === 1 ? ' ' : '';
|
||||
|
||||
/* If this node is immediately preceded or followed by one of the same type don't emit
|
||||
* the start or end $style, respectively. This prevents <em>foo</em><em>bar</em> from
|
||||
* being converted to *foo**bar* which is incorrect. We want *foobar* instead.
|
||||
*/
|
||||
$preStyle = $this->getNormTag($element->getPreviousSibling()) === $tag ? '' : $style;
|
||||
$postStyle = $this->getNormTag($element->getNextSibling()) === $tag ? '' : $style;
|
||||
|
||||
return $prefix . $preStyle . $content . $postStyle . $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['em', 'i', 'strong', 'b'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace League\HTMLToMarkdown\Converter;
|
||||
|
||||
use League\HTMLToMarkdown\Configuration;
|
||||
use League\HTMLToMarkdown\ConfigurationAwareInterface;
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class LinkConverter implements ConverterInterface, ConfigurationAwareInterface
|
||||
{
|
||||
/** @var Configuration */
|
||||
protected $config;
|
||||
|
||||
public function setConfig(Configuration $config): void
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$href = $element->getAttribute('href');
|
||||
$title = $element->getAttribute('title');
|
||||
$text = \trim($element->getValue(), "\t\n\r\0\x0B");
|
||||
|
||||
if ($title !== '') {
|
||||
$markdown = '[' . $text . '](' . $href . ' "' . $title . '")';
|
||||
} elseif ($href === \str_replace('&', '&', $text) && $this->isValidAutolink($href)) {
|
||||
$markdown = '<' . $href . '>';
|
||||
} elseif ($href === 'mailto:' . $text && $this->isValidEmail($text)) {
|
||||
$markdown = '<' . $text . '>';
|
||||
} else {
|
||||
if (\stristr($href, ' ')) {
|
||||
$href = '<' . $href . '>';
|
||||
}
|
||||
|
||||
$markdown = '[' . $text . '](' . $href . ')';
|
||||
}
|
||||
|
||||
if (! $href) {
|
||||
if ($this->shouldStrip()) {
|
||||
$markdown = $text;
|
||||
} else {
|
||||
$markdown = \html_entity_decode($element->getChildrenAsString());
|
||||
}
|
||||
}
|
||||
|
||||
return $markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['a'];
|
||||
}
|
||||
|
||||
private function isValidAutolink(string $href): bool
|
||||
{
|
||||
$useAutolinks = $this->config->getOption('use_autolinks');
|
||||
|
||||
return $useAutolinks && (\preg_match('/^[A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*/i', $href) === 1);
|
||||
}
|
||||
|
||||
private function isValidEmail(string $email): bool
|
||||
{
|
||||
// Email validation is messy business, but this should cover most cases
|
||||
return \filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
private function shouldStrip(): bool
|
||||
{
|
||||
return \boolval($this->config->getOption('strip_placeholder_links') ?? false);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,8 @@ class ListItemConverter implements ConverterInterface, ConfigurationAwareInterfa
|
||||
return $prefix . $this->listItemStyle . ' ' . $value . "\n";
|
||||
}
|
||||
|
||||
if ($listType === 'ol' && ($parent = $element->getParent()) && ($start = \intval($parent->getAttribute('start')))) {
|
||||
$number = $start + $element->getSiblingPosition() - 1;
|
||||
if ($listType === 'ol' && ($parent = $element->getParent()) && \is_numeric($start = $parent->getAttribute('start'))) {
|
||||
$number = (int) $start + $element->getSiblingPosition() - 1;
|
||||
} else {
|
||||
$number = $element->getSiblingPosition();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace League\HTMLToMarkdown\Converter;
|
||||
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class PreformattedConverter implements ConverterInterface
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$preContent = \html_entity_decode($element->getChildrenAsString());
|
||||
$preContent = \preg_replace('/<pre\b[^>]*>/', '', $preContent);
|
||||
\assert($preContent !== null);
|
||||
$preContent = \str_replace('</pre>', '', $preContent);
|
||||
|
||||
// A newline right after the <pre> start tag is not part of the content (HTML spec), but the parser keeps it
|
||||
$preContent = \preg_replace('/^(?:\r\n|\r|\n)/', '', $preContent);
|
||||
\assert($preContent !== null);
|
||||
|
||||
/*
|
||||
* Checking for the code tag.
|
||||
* Usually pre tags are used along with code tags. This conditional will check for already converted code tags,
|
||||
* which use backticks, and if those backticks are at the beginning and at the end of the string it means
|
||||
* there's no more information to convert.
|
||||
*/
|
||||
|
||||
$firstBacktick = \strpos(\trim($preContent), '`');
|
||||
$lastBacktick = \strrpos(\trim($preContent), '`');
|
||||
if ($firstBacktick === 0 && $lastBacktick === \strlen(\trim($preContent)) - 1) {
|
||||
return $preContent . "\n\n";
|
||||
}
|
||||
|
||||
// If the execution reaches this point it means it's just a pre tag, with no code tag nested
|
||||
|
||||
// Empty lines are a special case
|
||||
if ($preContent === '') {
|
||||
return "```\n```\n\n";
|
||||
}
|
||||
|
||||
// Normalizing new lines
|
||||
$preContent = \preg_replace('/\r\n|\r|\n/', "\n", $preContent);
|
||||
\assert(\is_string($preContent));
|
||||
|
||||
// Ensure there's a newline at the end
|
||||
if (\strrpos($preContent, "\n") !== \strlen($preContent) - \strlen("\n")) {
|
||||
$preContent .= "\n";
|
||||
}
|
||||
|
||||
// Use three backticks
|
||||
return "```\n" . $preContent . "```\n\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['pre'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace League\HTMLToMarkdown\Converter;
|
||||
|
||||
use League\HTMLToMarkdown\ElementInterface;
|
||||
|
||||
class TextConverter implements ConverterInterface
|
||||
{
|
||||
public function convert(ElementInterface $element): string
|
||||
{
|
||||
$markdown = $element->getValue();
|
||||
|
||||
// Remove leftover \n at the beginning of the line
|
||||
$markdown = \ltrim($markdown, "\n");
|
||||
|
||||
// Replace sequences of invisible characters with spaces, keeping non-breaking spaces
|
||||
$markdown = \preg_replace('~[^\S\x{00A0}]+~u', ' ', $markdown);
|
||||
\assert(\is_string($markdown));
|
||||
|
||||
// Escape the following characters: '*', '_', '[', ']' and '\'
|
||||
if (($parent = $element->getParent()) && $parent->getTagName() !== 'div') {
|
||||
$markdown = \preg_replace('~([*_\\[\\]\\\\])~u', '\\\\$1', $markdown);
|
||||
\assert(\is_string($markdown));
|
||||
}
|
||||
|
||||
$markdown = \preg_replace('~^#~u', '\\\\#', $markdown);
|
||||
\assert(\is_string($markdown));
|
||||
|
||||
if ($markdown === ' ') {
|
||||
$next = $element->getNext();
|
||||
if (! $next || $next->isBlock()) {
|
||||
$markdown = '';
|
||||
}
|
||||
}
|
||||
|
||||
return \htmlspecialchars($markdown, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedTags(): array
|
||||
{
|
||||
return ['#text'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace League\HTMLToMarkdown;
|
||||
|
||||
/**
|
||||
* A helper class to convert HTML to Markdown.
|
||||
*
|
||||
* @author Colin O'Dell <colinodell@gmail.com>
|
||||
* @author Nick Cernis <nick@cern.is>
|
||||
*
|
||||
* @link https://github.com/thephpleague/html-to-markdown/ Latest version on GitHub.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT
|
||||
*/
|
||||
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 */
|
||||
protected $environment;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Environment|array<string, mixed> $options Environment object or configuration options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if ($options instanceof Environment) {
|
||||
$this->environment = $options;
|
||||
} elseif (\is_array($options)) {
|
||||
$defaults = [
|
||||
'header_style' => 'setext', // Set to 'atx' to output H1 and H2 headers as # Header1 and ## Header2
|
||||
'suppress_errors' => true, // Set to false to show warnings when loading malformed HTML
|
||||
'strip_tags' => false, // Set to true to strip tags that don't have markdown equivalents. N.B. Strips tags, not their content. Useful to clean MS Word HTML output.
|
||||
'strip_placeholder_links' => false, // Set to true to remove <a> that doesn't have href.
|
||||
'bold_style' => '**', // DEPRECATED: Set to '__' if you prefer the underlined style
|
||||
'italic_style' => '*', // DEPRECATED: Set to '_' if you prefer the underlined style
|
||||
'remove_nodes' => '', // space-separated list of dom nodes that should be removed. example: 'meta style script'
|
||||
'hard_break' => false, // Set to true to turn <br> into `\n` instead of ` \n`
|
||||
'list_item_style' => '-', // Set the default character for each <li> in a <ul>. Can be '-', '*', or '+'
|
||||
'preserve_comments' => false, // Set to true to preserve comments, or set to an array of strings to preserve specific comments
|
||||
'use_autolinks' => true, // Set to true to use simple link syntax if possible. Will always use []() if set to false
|
||||
'table_pipe_escape' => '\|', // Replacement string for pipe characters inside markdown table cells
|
||||
'table_caption_side' => 'top', // Set to 'top' or 'bottom' to show <caption> content before or after table, null to suppress
|
||||
];
|
||||
|
||||
$this->environment = Environment::createDefaultEnvironment($defaults);
|
||||
|
||||
$this->environment->getConfig()->merge($options);
|
||||
}
|
||||
}
|
||||
|
||||
public function getEnvironment(): Environment
|
||||
{
|
||||
return $this->environment;
|
||||
}
|
||||
|
||||
public function getConfig(): Configuration
|
||||
{
|
||||
return $this->environment->getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert
|
||||
*
|
||||
* @see HtmlConverter::convert
|
||||
*
|
||||
* @return string The Markdown version of the html
|
||||
*/
|
||||
public function __invoke(string $html): string
|
||||
{
|
||||
return $this->convert($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert
|
||||
*
|
||||
* Loads HTML and passes to getMarkdown()
|
||||
*
|
||||
* @return string The Markdown version of the html
|
||||
*
|
||||
* @throws \InvalidArgumentException|\RuntimeException
|
||||
*/
|
||||
public function convert(string $html): string
|
||||
{
|
||||
if (\trim($html) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$document = $this->createDOMDocument($html);
|
||||
|
||||
// Work on the entire DOM tree (including head and body)
|
||||
if (! ($root = $document->getElementsByTagName('html')->item(0))) {
|
||||
throw new \InvalidArgumentException('Invalid HTML was provided');
|
||||
}
|
||||
|
||||
$rootElement = new Element($root);
|
||||
$this->convertChildren($rootElement);
|
||||
|
||||
// Store the now-modified DOMDocument as a string
|
||||
$markdown = $document->saveHTML();
|
||||
|
||||
if ($markdown === false) {
|
||||
throw new \RuntimeException('Unknown error occurred during HTML to Markdown conversion');
|
||||
}
|
||||
|
||||
return $this->sanitize($markdown);
|
||||
}
|
||||
|
||||
private function createDOMDocument(string $html): \DOMDocument
|
||||
{
|
||||
$document = new \DOMDocument();
|
||||
|
||||
if ($this->getConfig()->getOption('suppress_errors')) {
|
||||
// Suppress conversion errors (from http://bit.ly/pCCRSX)
|
||||
\libxml_use_internal_errors(true);
|
||||
}
|
||||
|
||||
// Hack to load utf-8 HTML (from http://bit.ly/pVDyCt)
|
||||
$document->loadHTML('<?xml encoding="UTF-8">' . $html);
|
||||
$document->encoding = 'UTF-8';
|
||||
|
||||
$this->replaceMisplacedComments($document);
|
||||
|
||||
if ($this->getConfig()->getOption('suppress_errors')) {
|
||||
\libxml_clear_errors();
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds any comment nodes outside <html> element and moves them into <body>.
|
||||
*
|
||||
* @see https://github.com/thephpleague/html-to-markdown/issues/212
|
||||
* @see https://3v4l.org/7bC33
|
||||
*/
|
||||
private function replaceMisplacedComments(\DOMDocument $document): void
|
||||
{
|
||||
// Find ny comment nodes at the root of the document.
|
||||
$misplacedComments = (new \DOMXPath($document))->query('/comment()');
|
||||
if ($misplacedComments === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $document->getElementsByTagName('body')->item(0);
|
||||
if ($body === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over comment nodes in reverse so we put them inside <body> in
|
||||
// their original order.
|
||||
for ($index = $misplacedComments->length - 1; $index >= 0; $index--) {
|
||||
// DOMNameSpaceNode, which item() can also return, isn't a DOMNode and can't be inserted.
|
||||
$comment = $misplacedComments->item($index);
|
||||
if (! $comment instanceof \DOMNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($body->firstChild === null) {
|
||||
$body->insertBefore($comment);
|
||||
} else {
|
||||
$body->insertBefore($comment, $body->firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Children
|
||||
*
|
||||
* Recursive function to drill into the DOM and convert each node into Markdown from the inside out.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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, bool $followsInlineContent = false): string
|
||||
{
|
||||
// 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.
|
||||
if ($element->isDescendantOf(['pre', 'code']) && $element->getTagName() !== 'code') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Give converter a chance to inspect/modify the DOM before children are converted
|
||||
$converter = $this->environment->getConverterByTag($element->getTagName());
|
||||
if ($converter instanceof PreConverterInterface) {
|
||||
$converter->preConvert($element);
|
||||
}
|
||||
|
||||
// If the node has children, convert those to Markdown first
|
||||
if ($element->hasChildren()) {
|
||||
$childFollowsInlineContent = false;
|
||||
foreach ($element->getChildren() as $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
|
||||
$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
|
||||
|
||||
// Replace the old $node e.g. '<h3>Title</h3>' with the new $markdown_node e.g. '### Title'
|
||||
$element->setFinalMarkdown($markdown);
|
||||
|
||||
return $markdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to Markdown
|
||||
*
|
||||
* Converts an individual node into a #text node containing a string of its Markdown equivalent.
|
||||
*
|
||||
* Example: An <h3> node with text content of 'Title' becomes a text node with content of '### Title'
|
||||
*
|
||||
* @return string The converted HTML as Markdown
|
||||
*/
|
||||
protected function convertToMarkdown(ElementInterface $element): string
|
||||
{
|
||||
$tag = $element->getTagName();
|
||||
|
||||
// Strip nodes named in remove_nodes
|
||||
$tagsToRemove = \explode(' ', Coerce::toString($this->getConfig()->getOption('remove_nodes') ?? ''));
|
||||
if (\in_array($tag, $tagsToRemove, true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$converter = $this->environment->getConverterByTag($tag);
|
||||
|
||||
return $converter->convert($element);
|
||||
}
|
||||
|
||||
protected function sanitize(string $markdown): string
|
||||
{
|
||||
$markdown = \html_entity_decode($markdown, ENT_QUOTES, 'UTF-8');
|
||||
$markdown = \preg_replace('/<!DOCTYPE [^>]+>/', '', $markdown); // Strip doctype declaration
|
||||
\assert($markdown !== null);
|
||||
$markdown = \trim($markdown); // Remove blank spaces at the beggining of the html
|
||||
|
||||
/*
|
||||
* Removing unwanted tags. Tags should be added to the array in the order they are expected.
|
||||
* XML, html and body opening tags should be in that order. Same case with closing tags
|
||||
*/
|
||||
$unwanted = ['<?xml encoding="UTF-8">', '<html>', '</html>', '<body>', '</body>', '<head>', '</head>', '
'];
|
||||
|
||||
foreach ($unwanted as $tag) {
|
||||
if (\strpos($tag, '/') === false) {
|
||||
// Opening tags
|
||||
if (\strpos($markdown, $tag) === 0) {
|
||||
$markdown = \substr($markdown, \strlen($tag));
|
||||
}
|
||||
} else {
|
||||
// Closing tags
|
||||
if (\strpos($markdown, $tag) === \strlen($markdown) - \strlen($tag)) {
|
||||
$markdown = \substr($markdown, 0, -\strlen($tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \trim($markdown, "\n\r\0\x0B");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a series of key-value pairs in an array; these will be passed
|
||||
* through the config and set.
|
||||
* The advantage of this is that it can allow for static use (IE in Laravel).
|
||||
* An example being:
|
||||
*
|
||||
* HtmlConverter::setOptions(['strip_tags' => true])->convert('<h1>test</h1>');
|
||||
*
|
||||
* @param array<string, mixed> $options
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
|
||||
foreach ($options as $key => $option) {
|
||||
$config->setOption($key, $option);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,7 @@ class HtmlConverterTest extends TestCase
|
||||
$this->assertHtmlGivesMarkdown('<ol><li> Item A</li><li> Item B</li></ol>', "1. Item A\n2. Item B");
|
||||
$this->assertHtmlGivesMarkdown('<ol><li> <h3> Item A</h3><p>Description</p></li><li> Item B</li></ol>', "1. ### Item A\n \n Description\n2. Item B");
|
||||
$this->assertHtmlGivesMarkdown('<ol start="120"><li>Item A</li><li>Item B</li></ol>', "120. Item A\n121. Item B");
|
||||
$this->assertHtmlGivesMarkdown('<ol start="0"><li>Item A</li><li>Item B</li></ol>', "0. Item A\n1. Item B");
|
||||
$this->assertHtmlGivesMarkdown('<ul><li>first item of first list</li><li>second item of first list</li></ul><ul><li>first item of second list</li></ul>', "- first item of first list\n- second item of first list\n\n* first item of second list", ['list_item_style_alternate' => '*']);
|
||||
}
|
||||
|
||||
@@ -377,6 +378,14 @@ EOT;
|
||||
$this->assertHtmlGivesMarkdown('<code><p>Some sample HTML</p></code>', '`<p>Some sample HTML</p>`');
|
||||
}
|
||||
|
||||
public function testNonBreakingSpaces(): void
|
||||
{
|
||||
$this->assertHtmlGivesMarkdown('<p>This is a test and test</p>', "This is a test and\u{00A0}test");
|
||||
$this->assertHtmlGivesMarkdown("<p>12,50\u{00A0}\u{00A0}€ \n total</p>", "12,50\u{00A0}\u{00A0}€ total");
|
||||
$this->assertHtmlGivesMarkdown('<p><strong>Total: </strong>12,50 €</p>', "**Total:** 12,50\u{00A0}€");
|
||||
$this->assertHtmlGivesMarkdown('<p>a<em> </em>b</p>', "a\u{00A0}b");
|
||||
}
|
||||
|
||||
public function testSetOption(): void
|
||||
{
|
||||
$markdown = new HtmlConverter();
|
||||
|
||||
Reference in new issue
Block a user