Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 62 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
| MigrateTemplateMarkerMetas | |
0.00% |
0 / 62 |
|
0.00% |
0 / 5 |
1122 | |
0.00% |
0 / 1 |
| migrateFirstChild | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
| migrateLastChild | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
| updateDepths | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| doMigrate | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
462 | |||
| run | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
| 1 | <?php |
| 2 | declare( strict_types = 1 ); |
| 3 | |
| 4 | namespace Wikimedia\Parsoid\Wt2Html\DOM\Processors; |
| 5 | |
| 6 | use Wikimedia\Parsoid\Config\Env; |
| 7 | use Wikimedia\Parsoid\DOM\DocumentFragment; |
| 8 | use Wikimedia\Parsoid\DOM\Element; |
| 9 | use Wikimedia\Parsoid\DOM\Node; |
| 10 | use Wikimedia\Parsoid\Utils\DiffDOMUtils; |
| 11 | use Wikimedia\Parsoid\Utils\DOMCompat; |
| 12 | use Wikimedia\Parsoid\Utils\DOMDataUtils; |
| 13 | use Wikimedia\Parsoid\Utils\DOMUtils; |
| 14 | use Wikimedia\Parsoid\Utils\WTUtils; |
| 15 | use Wikimedia\Parsoid\Wikitext\Consts; |
| 16 | use Wikimedia\Parsoid\Wt2Html\Wt2HtmlDOMProcessor; |
| 17 | |
| 18 | class MigrateTemplateMarkerMetas implements Wt2HtmlDOMProcessor { |
| 19 | |
| 20 | private function migrateFirstChild( Node $firstChild ): bool { |
| 21 | if ( WTUtils::isTplEndMarkerMeta( $firstChild ) ) { |
| 22 | return true; |
| 23 | } |
| 24 | |
| 25 | if ( WTUtils::isTplStartMarkerMeta( $firstChild ) ) { |
| 26 | '@phan-var Element $firstChild'; // @var Element $firstChild |
| 27 | |
| 28 | $docDataBag = DOMDataUtils::getBag( $firstChild->ownerDocument ); |
| 29 | $about = DOMCompat::getAttribute( $firstChild, 'about' ); |
| 30 | $startDepth = $docDataBag->transclusionMetaTagDepthMap[$about]['start']; |
| 31 | $endDepth = $docDataBag->transclusionMetaTagDepthMap[$about]['end']; |
| 32 | return $startDepth > $endDepth; |
| 33 | } |
| 34 | |
| 35 | return false; |
| 36 | } |
| 37 | |
| 38 | private function migrateLastChild( Node $lastChild ): bool { |
| 39 | if ( WTUtils::isTplStartMarkerMeta( $lastChild ) ) { |
| 40 | return true; |
| 41 | } |
| 42 | |
| 43 | if ( WTUtils::isTplEndMarkerMeta( $lastChild ) ) { |
| 44 | '@phan-var Element $lastChild'; // @var Element $lastChild |
| 45 | $docDataBag = DOMDataUtils::getBag( $lastChild->ownerDocument ); |
| 46 | $about = DOMCompat::getAttribute( $lastChild, 'about' ); |
| 47 | $startDepth = $docDataBag->transclusionMetaTagDepthMap[$about]['start']; |
| 48 | $endDepth = $docDataBag->transclusionMetaTagDepthMap[$about]['end']; |
| 49 | return $startDepth < $endDepth; |
| 50 | } |
| 51 | |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | private function updateDepths( Element $elt ): void { |
| 56 | // Update depths |
| 57 | $docDataBag = DOMDataUtils::getBag( $elt->ownerDocument ); |
| 58 | $about = DOMCompat::getAttribute( $elt, 'about' ); |
| 59 | if ( WTUtils::isTplEndMarkerMeta( $elt ) ) { |
| 60 | // end depth |
| 61 | $docDataBag->transclusionMetaTagDepthMap[$about]['end']--; |
| 62 | } else { |
| 63 | // start depth |
| 64 | $docDataBag->transclusionMetaTagDepthMap[$about]['start']--; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * The goal of this pass is to assist the WrapTemplates pass |
| 70 | * by using some simple heuristics to bring the DOM into a more |
| 71 | * canonical form. There is no correctness issue with WrapTemplates |
| 72 | * wrapping a wider range of content than what a template generated. |
| 73 | * These heuristics can be evolved as needed. |
| 74 | * |
| 75 | * Given the above considerations, we are going to consider migration |
| 76 | * possibilities only where the migration won't lead to additional |
| 77 | * untemplated content getting pulled into the template wrapper. |
| 78 | * |
| 79 | * The simplest heuristics that satisfy this constraint are: |
| 80 | * - Only examine first/last child of a node. |
| 81 | * - We relax the first/last child constraint by ignoring |
| 82 | * separator nodes (comments, whitespace) but this is |
| 83 | * something worth revisiting in the future. |
| 84 | * - Only migrate upwards if the node's start/end tag (barrier) |
| 85 | * comes from zero-width-wikitext. |
| 86 | * - If the start meta is the last child OR if the end meta is |
| 87 | * the first child, migrate up. |
| 88 | * - If the start meta is the first child OR if the end meta is |
| 89 | * the last child, there is no benefit to migrating the meta tags |
| 90 | * up if both the start and end metas are at the same tree depth. |
| 91 | * - In some special cases, it might be possible to migrate |
| 92 | * metas downward rather than upward. Migrating downwards has |
| 93 | * wt2wt corruption implications if done incorrectly. So, we |
| 94 | * aren't considering this possibility right now. |
| 95 | * |
| 96 | * @param Element|DocumentFragment $node |
| 97 | * @param Env $env |
| 98 | */ |
| 99 | private function doMigrate( Node $node, Env $env ): void { |
| 100 | $c = $node->firstChild; |
| 101 | while ( $c ) { |
| 102 | $sibling = $c->nextSibling; |
| 103 | if ( $c->hasChildNodes() ) { |
| 104 | '@phan-var Element $c'; // @var Element $c |
| 105 | $this->doMigrate( $c, $env ); |
| 106 | } |
| 107 | $c = $sibling; |
| 108 | } |
| 109 | |
| 110 | // No migration out of fragment |
| 111 | if ( DOMUtils::atTheTop( $node ) ) { |
| 112 | return; |
| 113 | } |
| 114 | |
| 115 | '@phan-var Element $node'; // @var Element $node |
| 116 | |
| 117 | // Check if $node is a fostered node |
| 118 | $fostered = !empty( DOMDataUtils::getDataParsoid( $node )->fostered ); |
| 119 | |
| 120 | $firstChild = DiffDOMUtils::firstNonSepChild( $node ); |
| 121 | if ( $firstChild && $this->migrateFirstChild( $firstChild ) ) { |
| 122 | // We can migrate the meta-tag across this node's start-tag barrier only |
| 123 | // if that start-tag is zero-width, or auto-inserted. |
| 124 | $tagWidth = Consts::$WtTagWidths[DOMUtils::nodeName( $node )] ?? null; |
| 125 | if ( ( $tagWidth && $tagWidth[0] === 0 && !WTUtils::isLiteralHTMLNode( $node ) ) || |
| 126 | !empty( DOMDataUtils::getDataParsoid( $node )->autoInsertedStart ) |
| 127 | ) { |
| 128 | $sentinel = $firstChild; |
| 129 | do { |
| 130 | $firstChild = $node->firstChild; |
| 131 | $node->parentNode->insertBefore( $firstChild, $node ); |
| 132 | if ( $fostered && $firstChild instanceof Element ) { |
| 133 | // $firstChild is being migrated out of a fostered node |
| 134 | // So, mark $lastChild itself fostered! |
| 135 | DOMDataUtils::getDataParsoid( $firstChild )->fostered = true; |
| 136 | } |
| 137 | } while ( $sentinel !== $firstChild ); |
| 138 | |
| 139 | $this->updateDepths( $firstChild ); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | $lastChild = DiffDOMUtils::lastNonSepChild( $node ); |
| 144 | if ( $lastChild && $this->migrateLastChild( $lastChild ) ) { |
| 145 | // We can migrate the meta-tag across this node's end-tag barrier only |
| 146 | // if that end-tag is zero-width, or auto-inserted. |
| 147 | $tagWidth = Consts::$WtTagWidths[DOMUtils::nodeName( $node )] ?? null; |
| 148 | '@phan-var Element $node'; // @var Element $node |
| 149 | if ( ( $tagWidth && $tagWidth[1] === 0 && |
| 150 | !WTUtils::isLiteralHTMLNode( $node ) ) || |
| 151 | ( !empty( DOMDataUtils::getDataParsoid( $node )->autoInsertedEnd ) && |
| 152 | // Except, don't migrate out of a table since the end meta |
| 153 | // marker may have been fostered and this is more likely to |
| 154 | // result in a flipped range that isn't enclosed. |
| 155 | DOMUtils::nodeName( $node ) !== 'table' ) |
| 156 | ) { |
| 157 | $sentinel = $lastChild; |
| 158 | do { |
| 159 | $lastChild = $node->lastChild; |
| 160 | $node->parentNode->insertBefore( $lastChild, $node->nextSibling ); |
| 161 | if ( $fostered && $lastChild instanceof Element ) { |
| 162 | // $lastChild is being migrated out of a fostered node |
| 163 | // So, mark $lastChild itself fostered! |
| 164 | DOMDataUtils::getDataParsoid( $lastChild )->fostered = true; |
| 165 | } |
| 166 | } while ( $sentinel !== $lastChild ); |
| 167 | |
| 168 | $this->updateDepths( $lastChild ); |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * @inheritDoc |
| 175 | */ |
| 176 | public function run( |
| 177 | Env $env, Node $root, array $options = [], bool $atTopLevel = false |
| 178 | ): void { |
| 179 | // Don't run this in template content |
| 180 | if ( $options['inTemplate'] ) { |
| 181 | return; |
| 182 | } |
| 183 | if ( $root instanceof Element || $root instanceof DocumentFragment ) { |
| 184 | $this->doMigrate( $root, $env ); |
| 185 | } |
| 186 | } |
| 187 | } |