*/ private const PRODUCT_MARKER_PATTERN = '/^PRESSNATIVE_PRODUCT:(\d+)$/'; /** * Tags that must never be recursed into — their full outer HTML becomes * a BlockHtml so the native app can render them inside a micro-WebView. */ private const BAILOUT_TAGS = array( 'table', 'form', 'iframe', 'script', 'style', 'video', ); /** * If a
,
) → BlockQuote ──────────────────── if ( 'blockquote' === $tag ) { return $this->parse_blockquote_element( $node ); } // ── Default: recurse into children (div, section, figure, …) ─── $blocks = array(); foreach ( $node->childNodes as $child ) { $blocks = array_merge( $blocks, $this->walk_node( $child ) ); } return $blocks; } // ─── Element-Specific Parsers ─────────────────────────────────────── /** * @param DOMElement $node Anelement. * @return array Zero or one BlockImage objects. */ private function parse_img_element( DOMElement $node ): array { if ( ! $node->hasAttributes() ) { return array(); } $src = $node->getAttribute( 'src' ); if ( '' === $src ) { return array(); } $width = $node->hasAttribute( 'width' ) ? (int) $node->getAttribute( 'width' ) : null; $height = $node->hasAttribute( 'height' ) ? (int) $node->getAttribute( 'height' ) : null; $alt = $node->hasAttribute( 'alt' ) ? $node->getAttribute( 'alt' ) : ''; return array( array( 'type' => 'BlockImage', 'url' => $src, 'width' => $width, 'height' => $height, 'alt' => $alt, ), ); } /** * @param DOMElement $node A
or
element. * @return array Zero or one BlockList objects. */ private function parse_list_element( DOMElement $node ): array { $items = array(); foreach ( $node->childNodes as $child ) { if ( XML_ELEMENT_NODE !== $child->nodeType ) { continue; } if ( 'li' !== strtolower( $child->nodeName ) ) { continue; } $inner = trim( $this->get_inner_html( $child ) ); if ( '' !== $inner ) { $items[] = $inner; } } if ( empty( $items ) ) { return array(); } return array( array( 'type' => 'BlockList', 'items' => $items, ), ); } /** * @param DOMElement $node A
element. * @return array Zero or one BlockQuote objects. */ private function parse_blockquote_element( DOMElement $node ): array { $author = ''; $cites = $node->getElementsByTagName( 'cite' ); if ( $cites->length > 0 ) { $cite_node = $cites->item( 0 ); $author = trim( $cite_node->textContent ); } $full_text = trim( $node->textContent ); if ( '' !== $author ) { $full_text = trim( str_replace( $author, '', $full_text ) ); } if ( '' === $full_text ) { return array(); } return array( array( 'type' => 'BlockQuote', 'text' => $full_text, 'author' => $author, ), ); } // ─── Helpers ──────────────────────────────────────────────────────── /** * Returns true if a class string contains any bailout prefix. * * @param string $class_attr The element's class attribute value. * @return bool */ private function class_triggers_bailout( string $class_attr ): bool { foreach ( self::BAILOUT_CLASS_PREFIXES as $prefix ) { if ( false !== strpos( $class_attr, $prefix ) ) { return true; } } return false; } /** * Serialises all child nodes of an element back to an HTML string. * * This preserves inline formatting (, , , , ) * so the native app's rich-text renderer can interpret it. * * @param DOMNode $node Parent node whose children we want as HTML. * @return string The concatenated inner HTML. */ private function get_inner_html( DOMNode $node ): string { if ( ! $node->hasChildNodes() ) { return ''; } $html = ''; $doc = $node->ownerDocument; foreach ( $node->childNodes as $child ) { $html .= $doc->saveHTML( $child ); } return $html; } }