18 private static $onlyInlineElements = [
83 private static $metadataElements = [
90 private static $formattingElements = [
117 public function __construct( Serializer $serializer, $trace =
false ) {
118 $this->serializer = $serializer;
119 $this->trace = $trace;
123 $this->serializer->startDocument( $fragmentNamespace, $fragmentName );
124 $root = $this->serializer->getRootNode();
126 $root->snData->needsPWrapping =
true;
130 $this->serializer->endDocument( $pos );
133 private function getParentForInsert( $preposition, $refElement ) {
134 if ( $preposition === TreeBuilder::ROOT ) {
135 return [ $this->serializer->getRootNode(), null ];
136 } elseif ( $preposition === TreeBuilder::BEFORE ) {
137 $refNode = $refElement->userData;
138 return [ $this->serializer->getParentNode( $refNode ), $refNode ];
140 $refNode = $refElement->userData;
141 $refData = $refNode->snData;
142 if ( $refData->currentCloneElement ) {
144 $origRefData = $refData;
145 while ( $refData->currentCloneElement ) {
146 $refElement = $refData->currentCloneElement;
147 $refNode = $refElement->userData;
148 $refData = $refNode->snData;
151 $origRefData->currentCloneElement = $refElement;
152 } elseif ( $refData->childPElement ) {
153 $refElement = $refData->childPElement;
154 $refNode = $refElement->userData;
156 return [ $refNode, $refNode ];
167 private function insertPWrapper( SerializerNode $parent, $sourceStart ) {
168 $pWrap =
new Element( HTMLData::NS_HTML,
'mw:p-wrap',
new PlainAttributes );
169 $this->serializer->insertElement( TreeBuilder::UNDER, $parent, $pWrap,
false,
171 $data =
new RemexMungerData;
172 $data->isPWrapper =
true;
173 $data->wrapBaseNode = $parent;
174 $pWrap->userData->snData = $data;
175 $parent->snData->childPElement = $pWrap;
176 return $pWrap->userData;
179 public function characters( $preposition, $refElement, $text, $start, $length,
180 $sourceStart, $sourceLength
182 $isBlank = strspn( $text,
"\t\n\f\r ", $start, $length ) === $length;
184 list( $parent, $refNode ) = $this->getParentForInsert( $preposition, $refElement );
185 $parentData = $parent->snData;
187 if ( $preposition === TreeBuilder::UNDER ) {
188 if ( $parentData->needsPWrapping && !$isBlank ) {
190 $refNode = $this->insertPWrapper( $refNode, $sourceStart );
192 $parentData = $parent->snData;
193 } elseif ( $parentData->isSplittable && !$parentData->ancestorPNode ) {
195 $refNode = $this->splitTagStack( $refNode,
true, $sourceStart );
197 $parentData = $parent->snData;
203 $parentData->nonblankNodeCount++;
205 $this->serializer->characters( $preposition, $refNode, $text, $start,
206 $length, $sourceStart, $sourceLength );
209 private function trace( $msg ) {
210 if ( $this->trace ) {
268 public function insertElement( $preposition, $refElement, Element $element, $void,
269 $sourceStart, $sourceLength
271 list( $parent, $newRef ) = $this->getParentForInsert( $preposition, $refElement );
272 $parentData = $parent->snData;
273 $elementName = $element->htmlName;
275 $inline = isset( self::$onlyInlineElements[$elementName] );
276 $under = $preposition === TreeBuilder::UNDER;
278 if ( isset( self::$metadataElements[$elementName] ) ) {
281 $this->trace(
'insert metadata' );
282 } elseif ( $under && $parentData->isPWrapper && !$inline ) {
285 $this->trace(
'insert B/b' );
286 $newParent = $this->serializer->getParentNode( $parent );
287 $parent = $newParent;
288 $parentData = $parent->snData;
289 $parentData->childPElement =
null;
290 $newRef = $refElement->userData;
291 } elseif ( $under && $parentData->isSplittable
292 && (
bool)$parentData->ancestorPNode !== $inline
297 $this->trace( $inline ?
'insert DS/i' :
'insert CS/b' );
298 $newRef = $this->splitTagStack( $newRef, $inline, $sourceStart );
300 $parentData = $parent->snData;
301 } elseif ( $under && $parentData->needsPWrapping && $inline ) {
304 $this->trace(
'insert A/i' );
305 $newRef = $this->insertPWrapper( $newRef, $sourceStart );
307 $parentData = $parent->snData;
308 } elseif ( $parentData->ancestorPNode && !$inline ) {
312 $this->trace(
'insert CU/b' );
313 $this->disablePWrapper( $parent, $sourceStart );
316 $this->trace(
'insert normal' );
320 $parentData->nonblankNodeCount++;
323 $this->serializer->insertElement( $preposition, $newRef,
324 $element, $void, $sourceStart, $sourceLength );
327 if ( !$element->userData->snData ) {
330 $elementData = $element->userData->snData;
332 if ( ( $parentData->isPWrapper || $parentData->isSplittable )
333 && isset( self::$formattingElements[$elementName] )
335 $elementData->isSplittable =
true;
337 if ( $parentData->isPWrapper ) {
338 $elementData->ancestorPNode = $parent;
339 } elseif ( $parentData->ancestorPNode ) {
340 $elementData->ancestorPNode = $parentData->ancestorPNode;
342 if ( $parentData->wrapBaseNode ) {
343 $elementData->wrapBaseNode = $parentData->wrapBaseNode;
344 } elseif ( $parentData->needsPWrapping ) {
345 $elementData->wrapBaseNode = $parent;
347 if ( $elementName ===
'body'
348 || $elementName ===
'blockquote'
349 || $elementName ===
'html'
351 $elementData->needsPWrapping =
true;
363 private function splitTagStack( SerializerNode $parentNode, $inline, $pos ) {
364 $parentData = $parentNode->snData;
365 $wrapBase = $parentData->wrapBaseNode;
366 $pWrap = $parentData->ancestorPNode;
368 $cloneEnd = $wrapBase;
370 $cloneEnd = $parentData->ancestorPNode;
373 $serializer = $this->serializer;
375 $root = $serializer->getRootNode();
377 $removableNodes = [];
378 while ( $node !== $cloneEnd ) {
379 $nextParent = $serializer->getParentNode( $node );
380 if ( $nextParent === $root ) {
381 throw new \Exception(
'Did not find end of clone range' );
384 if ( $node->snData->nonblankNodeCount === 0 ) {
385 $removableNodes[] = $node;
386 $nextParent->snData->nonblankNodeCount--;
392 $pWrap = $this->insertPWrapper( $wrapBase, $pos );
397 $wrapBase->snData->childPElement =
null;
403 for ( $i = count( $nodes ) - 1; $i >= 0; $i-- ) {
404 $oldNode = $nodes[$i];
405 $oldData = $oldNode->snData;
407 $element =
new Element( $oldNode->namespace, $oldNode->name, $oldNode->attrs );
408 $this->serializer->insertElement( TreeBuilder::UNDER, $nodeParent,
409 $element,
false, $pos, 0 );
410 $oldData->currentCloneElement = $element;
412 $newNode = $element->userData;
413 $newData = $newNode->snData =
new RemexMungerData;
415 $newData->ancestorPNode = $pWrap;
417 $newData->isSplittable =
true;
418 $newData->wrapBaseNode = $wrapBase;
419 $newData->isPWrapper = $oldData->isPWrapper;
421 $nodeParent->snData->nonblankNodeCount++;
425 foreach ( $removableNodes as $rNode ) {
426 $fakeElement =
new Element( $rNode->namespace, $rNode->name, $rNode->attrs );
427 $fakeElement->userData = $rNode;
428 $this->serializer->removeNode( $fakeElement, $pos );
440 private function disablePWrapper( SerializerNode $node, $sourceStart ) {
441 $nodeData = $node->snData;
442 $pWrapNode = $nodeData->ancestorPNode;
443 $newParent = $this->serializer->getParentNode( $pWrapNode );
444 if ( $pWrapNode !== $this->serializer->getLastChild( $newParent ) ) {
451 $victim = $nextParent;
452 $victim->snData->ancestorPNode =
null;
453 $nextParent = $this->serializer->getParentNode( $victim );
454 }
while ( $nextParent !== $pWrapNode );
457 $victimElement =
new Element( $victim->namespace, $victim->name, $victim->attrs );
458 $victimElement->userData = $victim;
461 $this->serializer->insertElement( TreeBuilder::UNDER, $newParent, $victimElement,
462 false, $sourceStart, 0 );
465 $pWrapNode->snData->nonblankNodeCount--;
468 $newParent->snData->childPElement =
null;
471 public function endTag( Element $element, $sourceStart, $sourceLength ) {
472 $data = $element->userData->snData;
473 if ( $data->childPElement ) {
474 $this->
endTag( $data->childPElement, $sourceStart, 0 );
476 $this->serializer->endTag( $element, $sourceStart, $sourceLength );
477 $element->userData->snData =
null;
478 $element->userData =
null;
481 public function doctype( $name, $public, $system, $quirks, $sourceStart, $sourceLength ) {
482 $this->serializer->doctype( $name, $public, $system, $quirks,
483 $sourceStart, $sourceLength );
486 public function comment( $preposition, $refElement, $text, $sourceStart, $sourceLength ) {
487 list( , $refNode ) = $this->getParentForInsert( $preposition, $refElement );
488 $this->serializer->comment( $preposition, $refNode, $text, $sourceStart, $sourceLength );
491 public function error( $text, $pos ) {
492 $this->serializer->error( $text, $pos );
495 public function mergeAttributes( Element $element, Attributes $attrs, $sourceStart ) {
496 $this->serializer->mergeAttributes( $element, $attrs, $sourceStart );
499 public function removeNode( Element $element, $sourceStart ) {
500 $this->serializer->removeNode( $element, $sourceStart );
504 $self = $element->userData;
505 if (
$self->snData->childPElement ) {
519 $children =
$self->children;
520 $self->children = [];
521 $this->
insertElement( TreeBuilder::UNDER, $element, $newParent,
false, $sourceStart, 0 );
522 $newParentNode = $newParent->userData;
523 $newParentId = $newParentNode->id;
524 foreach ( $children as $child ) {
525 if ( is_object( $child ) ) {
526 $this->trace(
"reparent <{$child->name}>" );
527 $child->parentId = $newParentId;
530 $newParentNode->children = $children;