type6HtmlElements); } if (preg_match("/^<\/?($patterns)(\s|>|\/>|$)/i", $line)) { // Type 6. return true; } if ( preg_match( '/^<(\/)?[a-z][a-z0-9\-]*(?(1) *| .*?)?>(\s)*$/i', $line, $matches ) && ( !isset($lines[$current - 1]) || $lines[$current - 1] === '' || ltrim($lines[$current - 1]) === '' ) ) { // Type 7. return true; } return false; } /** * Consume lines for an HTML block. */ protected function consumeHtml($lines, $current): array { $content = []; $line = ltrim($lines[$current], ' '); if (strncasecmp($line, '') !== false) { break; } } } elseif (strncasecmp($line, '') !== false) { break; } } } elseif (strncasecmp($line, '') !== false) { break; } } } elseif (strncasecmp($line, '') !== false) { break; } } } elseif (str_starts_with($line, '') !== false) { break; } } } elseif (str_starts_with($line, '') !== false) { break; } } } elseif (str_starts_with($line, '') !== false) { break; } } } elseif (str_starts_with($line, '') !== false) { break; } } } else { // Type 6 or 7 tag - consume until blank line... $content = []; for ($i = $current, $count = count($lines); $i < $count; $i++) { $line = $lines[$i]; if (ltrim($line) !== '') { $content[] = $line; } else { break; } } } $block = [ 'html', 'content' => implode("\n", $content), ]; return [$block, $i]; } /** * Renders an HTML block. */ protected function renderHtml($block): string { return $block['content'] . "\n"; } protected function parseEntityMarkers(): array { return array('&'); } /** * Parses an & or a HTML entity definition. * * @marker & */ protected function parseEntity($text): array { if ( preg_match( '/^&(#[\d]{1,7}|#[x][a-f0-9]{1,6}|[\w\d]{2,});/i', $text, $matches ) ) { // HTML entity. return [['entity', $matches[0]], strlen($matches[0])]; } else { // Just an ampersand. return [['text', '&'], 1]; } } /** * Renders a HTML entity definition. */ protected function renderEntity($block): string { $chr = $this->unEscapeHtmlEntities( $block[1], ENT_QUOTES | ENT_SUBSTITUTE ); switch ($chr) { case '&': return '&'; case '<': return '<'; case '>': return '>'; case '"': return '"'; default: return $chr; } } protected function parseLtMarkers(): array { return array('<'); } /** * Parses inline HTML. * * @marker < */ protected function parseLt($text): array { if (strpos($text, '>') !== false) { // First try bracketed link if we have LinkTrait. if (method_exists($this, 'parseBracketedLink')) { $block = $this->parseBracketedLink($text); if ($block[0][0] !== 'text') { return $block; } } if ( // Comment. preg_match('/^)/s', $text, $matches) // Processor. || preg_match('/^<\?.*?\?>/s', $text, $matches) // Declaration. || preg_match('/^/is', $text, $matches) // CDATA. || preg_match('/^/s', $text, $matches) ) { return [['lt', $matches[0]], strlen($matches[0])]; } if ( // Tag. preg_match( '/^<(\/)?[a-z][a-z0-9\-]*(?(1)[ \n]*|(\/|[ \n].*?))?>/is', $text, $matches ) ) { return [['lt', $matches[0]], strlen($matches[0])]; } } return [['text', '<'], 1]; } /** * Renders inline HTML. */ protected function renderLt($block): string { return $block[1]; } protected function parseGtMarkers(): array { return array('>'); } /** * Escapes `>` characters. * * @marker > */ protected function parseGt($text): array { return [['text', '>'], 1]; } protected function parseDoubleQuoteMarkers(): array { return array('"'); } /** * Escapes `"` characters. * * @marker " */ protected function parseDoubleQuote($text): array { return [['text', '"'], 1]; } abstract protected function unEscapeHtmlEntities($text, $flags = 0); }