, * @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ class Inline { /** * @var string */ protected $html = ''; /** * @var array */ protected $marks = []; /** * @param \DOMNode $node * @param array $marks */ public function __construct(DOMNode $node, array $marks = []) { $this->createMarkRules($marks); $this->html = trim(static::parseNode($node, $this->marks)); } /** * Loads all mark rules * * @param array $marks * @return array */ protected function createMarkRules(array $marks) { foreach ($marks as $mark) { $this->marks[$mark['tag']] = $mark; } return $this->marks; } /** * Get all allowed attributes for a DOMNode * as clean array * * @param DOMNode $node * @param array $marks * @return array */ public static function parseAttrs(DOMNode $node, array $marks = []): array { $attrs = []; $mark = $marks[$node->tagName]; $defaults = $mark['defaults'] ?? []; foreach ($mark['attrs'] ?? [] as $attr) { if ($node->hasAttribute($attr)) { $attrs[$attr] = $node->getAttribute($attr); } else { $attrs[$attr] = $defaults[$attr] ?? null; } } return $attrs; } /** * Parses all children and creates clean HTML * for each of them. * * @param \DOMNodeList $children * @param array $marks * @return string */ public static function parseChildren(DOMNodeList $children, array $marks): string { $html = ''; foreach ($children as $child) { $html .= static::parseNode($child, $marks); } return $html; } /** * Go through all child elements and create * clean inner HTML for them * * @param DOMNode $node * @return string|null */ public static function parseInnerHtml(DOMNode $node, array $marks = []): ?string { $html = static::parseChildren($node->childNodes, $marks); // trim the inner HTML for paragraphs if ($node->tagName === 'p') { $html = trim($html); } // return null for empty inner HTML if ($html === '') { return null; } return $html; } /** * Converts the given node to clean HTML * * @param \DOMNode $node * @param array $marks * @return string|null */ public static function parseNode(DOMNode $node, array $marks = []): ?string { if (is_a($node, 'DOMText') === true) { return Html::encode($node->textContent); } // ignore comments if (is_a($node, 'DOMComment') === true) { return null; } // unknown marks if (array_key_exists($node->tagName, $marks) === false) { return static::parseChildren($node->childNodes, $marks); } // collect all allowed attributes $attrs = static::parseAttrs($node, $marks); // close self-closing elements if (Html::isVoid($node->tagName) === true) { return '<' . $node->tagName . attr($attrs, ' ') . ' />'; } $innerHtml = static::parseInnerHtml($node, $marks); // skip empty paragraphs if ($innerHtml === null && $node->tagName === 'p') { return null; } // create the outer html for the element return '<' . $node->tagName . attr($attrs, ' ') . '>' . $innerHtml . 'tagName . '>'; } /** * Returns the HTML contents of the element * * @return string */ public function innerHtml(): string { return $this->html; } }