1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Wt2Html\PP\Processors;
5
6use DOMElement;
7use DOMNode;
8use Wikimedia\Parsoid\Config\Env;
9use Wikimedia\Parsoid\Config\WikitextConstants;
10use Wikimedia\Parsoid\Utils\DOMDataUtils;
11use Wikimedia\Parsoid\Utils\DOMUtils;
12use Wikimedia\Parsoid\Utils\WTUtils;
13use Wikimedia\Parsoid\Wt2Html\Wt2HtmlDOMProcessor;
14
15class MigrateTemplateMarkerMetas implements Wt2HtmlDOMProcessor {
16	/**
17	 * This will move the start/end-meta closest to the content
18	 * that the template/extension produced and improve accuracy
19	 * of finding dom ranges and wrapping templates.
20	 *
21	 * If the last child of a node is a start-meta,
22	 * move it up and make it the parent's right sibling.
23	 *
24	 * If the first child of a node is an end-meta,
25	 * move it up and make it the parent's left sibling.
26	 *
27	 * @param DOMNode $node
28	 * @param Env $env
29	 */
30	private function doMigrate( DOMNode $node, Env $env ): void {
31		$c = $node->firstChild;
32		while ( $c ) {
33			$sibling = $c->nextSibling;
34			if ( $c->hasChildNodes() ) {
35				$this->doMigrate( $c, $env );
36			}
37			$c = $sibling;
38		}
39
40		// No migration out of BODY
41		if ( DOMUtils::isBody( $node ) ) {
42			return;
43		}
44
45		$firstChild = DOMUtils::firstNonSepChild( $node );
46		if ( $firstChild && WTUtils::isTplEndMarkerMeta( $firstChild ) ) {
47			// We can migrate the meta-tag across this node's start-tag barrier only
48			// if that start-tag is zero-width, or auto-inserted.
49			$tagWidth = WikitextConstants::$WtTagWidths[$node->nodeName] ?? null;
50			DOMUtils::assertElt( $node );
51			if ( ( $tagWidth && $tagWidth[0] === 0 && !WTUtils::isLiteralHTMLNode( $node ) ) ||
52				!empty( DOMDataUtils::getDataParsoid( $node )->autoInsertedStart )
53			) {
54				$sentinel = $firstChild;
55				do {
56					$firstChild = $node->firstChild;
57					$node->parentNode->insertBefore( $firstChild, $node );
58				} while ( $sentinel !== $firstChild );
59			}
60		}
61
62		$lastChild = DOMUtils::lastNonSepChild( $node );
63		if ( $lastChild && WTUtils::isTplStartMarkerMeta( $lastChild ) ) {
64			// We can migrate the meta-tag across this node's end-tag barrier only
65			// if that end-tag is zero-width, or auto-inserted.
66			$tagWidth = WikitextConstants::$WtTagWidths[$node->nodeName] ?? null;
67			DOMUtils::assertElt( $node );
68			if ( ( $tagWidth && $tagWidth[1] === 0 &&
69				// Except, don't migrate out of a table since the end meta
70				!WTUtils::isLiteralHTMLNode( $node ) ) ||
71				// marker may have been fostered and this is more likely to
72				// result in a flipped range that isn't enclosed.
73				( !empty( DOMDataUtils::getDataParsoid( $node )->autoInsertedEnd ) &&
74				$node->nodeName !== 'table' )
75			) {
76				$sentinel = $lastChild;
77				do {
78					$lastChild = $node->lastChild;
79					$node->parentNode->insertBefore( $lastChild, $node->nextSibling );
80				} while ( $sentinel !== $lastChild );
81			}
82		}
83	}
84
85	/**
86	 * @inheritDoc
87	 */
88	public function run(
89		Env $env, DOMElement $root, array $options = [], bool $atTopLevel = false
90	): void {
91		$this->doMigrate( $root, $env );
92	}
93}
94