5 use RemexHtml\HTMLData;
6 use RemexHtml\Serializer\Serializer;
7 use RemexHtml\Serializer\SerializerNode;
8 use RemexHtml\Tokenizer\Attributes;
9 use RemexHtml\Tokenizer\PlainAttributes;
10 use RemexHtml\TreeBuilder\TreeBuilder;
11 use RemexHtml\TreeBuilder\TreeHandler;
12 use RemexHtml\TreeBuilder\Element;
108 $this->serializer->startDocument( $fragmentNamespace, $fragmentName );
109 $root = $this->serializer->getRootNode();
111 $root->snData->needsPWrapping =
true;
115 $this->serializer->endDocument( $pos );
119 if ( $preposition === TreeBuilder::ROOT ) {
120 return [ $this->serializer->getRootNode(), null ];
121 } elseif ( $preposition === TreeBuilder::BEFORE ) {
122 $refNode = $refElement->userData;
123 return [ $this->serializer->getParentNode( $refNode ), $refNode ];
125 $refNode = $refElement->userData;
126 $refData = $refNode->snData;
127 if ( $refData->currentCloneElement ) {
129 $origRefData = $refData;
130 while ( $refData->currentCloneElement ) {
131 $refElement = $refData->currentCloneElement;
132 $refNode = $refElement->userData;
133 $refData = $refNode->snData;
136 $origRefData->currentCloneElement = $refElement;
137 } elseif ( $refData->childPElement ) {
138 $refElement = $refData->childPElement;
139 $refNode = $refElement->userData;
141 return [ $refNode, $refNode ];
153 $pWrap =
new Element( HTMLData::NS_HTML,
'mw:p-wrap',
new PlainAttributes );
154 $this->serializer->insertElement( TreeBuilder::UNDER,
$parent, $pWrap,
false,
157 $data->isPWrapper =
true;
159 $pWrap->userData->snData = $data;
160 $parent->snData->childPElement = $pWrap;
161 return $pWrap->userData;
164 public function characters( $preposition, $refElement, $text, $start, $length,
165 $sourceStart, $sourceLength
167 $isBlank = strspn( $text,
"\t\n\f\r ", $start, $length ) === $length;
172 if ( $preposition === TreeBuilder::UNDER ) {
173 if ( $parentData->needsPWrapping && !$isBlank ) {
178 } elseif ( $parentData->isSplittable && !$parentData->ancestorPNode ) {
180 $refNode = $this->
splitTagStack( $refNode,
true, $sourceStart );
188 $parentData->nonblankNodeCount++;
190 $this->serializer->characters( $preposition, $refNode, $text, $start,
191 $length, $sourceStart, $sourceLength );
195 if ( $this->
trace ) {
253 public function insertElement( $preposition, $refElement, Element $element, $void,
254 $sourceStart, $sourceLength
258 $elementName = $element->htmlName;
260 $inline = isset( self::$onlyInlineElements[$elementName] );
261 $under = $preposition === TreeBuilder::UNDER;
262 $elementToEnd =
null;
264 if ( $under && $parentData->isPWrapper && !$inline ) {
267 $this->
trace(
'insert B/b' );
268 $newParent = $this->serializer->getParentNode(
$parent );
271 $pElement = $parentData->childPElement;
272 $parentData->childPElement =
null;
273 $newRef = $refElement->userData;
274 } elseif ( $under && $parentData->isSplittable
275 && (
bool)$parentData->ancestorPNode !== $inline
280 $this->
trace( $inline ?
'insert DS/i' :
'insert CS/b' );
281 $newRef = $this->
splitTagStack( $newRef, $inline, $sourceStart );
284 } elseif ( $under && $parentData->needsPWrapping && $inline ) {
287 $this->
trace(
'insert A/i' );
291 } elseif ( $parentData->ancestorPNode && !$inline ) {
295 $this->
trace(
'insert CU/b' );
299 $this->
trace(
'insert normal' );
303 $parentData->nonblankNodeCount++;
306 $this->serializer->insertElement( $preposition, $newRef,
307 $element, $void, $sourceStart, $sourceLength );
310 if ( !$element->userData->snData ) {
313 $elementData = $element->userData->snData;
315 if ( ( $parentData->isPWrapper || $parentData->isSplittable )
316 && isset( self::$formattingElements[$elementName] )
318 $elementData->isSplittable =
true;
320 if ( $parentData->isPWrapper ) {
321 $elementData->ancestorPNode =
$parent;
322 } elseif ( $parentData->ancestorPNode ) {
323 $elementData->ancestorPNode = $parentData->ancestorPNode;
325 if ( $parentData->wrapBaseNode ) {
326 $elementData->wrapBaseNode = $parentData->wrapBaseNode;
327 } elseif ( $parentData->needsPWrapping ) {
328 $elementData->wrapBaseNode =
$parent;
330 if ( $elementName ===
'body'
331 || $elementName ===
'blockquote'
332 || $elementName ===
'html'
334 $elementData->needsPWrapping =
true;
346 private function splitTagStack( SerializerNode $parentNode, $inline, $pos ) {
347 $parentData = $parentNode->snData;
348 $wrapBase = $parentData->wrapBaseNode;
349 $pWrap = $parentData->ancestorPNode;
351 $cloneEnd = $wrapBase;
353 $cloneEnd = $parentData->ancestorPNode;
360 $removableNodes = [];
361 while ( $node !== $cloneEnd ) {
363 if ( $nextParent === $root ) {
364 throw new \Exception(
'Did not find end of clone range' );
367 if ( $node->snData->nonblankNodeCount === 0 ) {
368 $removableNodes[] = $node;
369 $nextParent->snData->nonblankNodeCount--;
380 $wrapBase->snData->childPElement =
null;
386 for ( $i =
count( $nodes ) - 1; $i >= 0; $i-- ) {
387 $oldNode = $nodes[$i];
388 $oldData = $oldNode->snData;
390 $element =
new Element( $oldNode->namespace, $oldNode->name, $oldNode->attrs );
391 $this->serializer->insertElement( TreeBuilder::UNDER, $nodeParent,
392 $element,
false, $pos, 0 );
393 $oldData->currentCloneElement = $element;
395 $newNode = $element->userData;
398 $newData->ancestorPNode = $pWrap;
400 $newData->isSplittable =
true;
401 $newData->wrapBaseNode = $wrapBase;
402 $newData->isPWrapper = $oldData->isPWrapper;
404 $nodeParent->snData->nonblankNodeCount++;
408 foreach ( $removableNodes
as $rNode ) {
409 $fakeElement =
new Element( $rNode->namespace, $rNode->name, $rNode->attrs );
410 $fakeElement->userData = $rNode;
411 $this->serializer->removeNode( $fakeElement, $pos );
421 $nodeData = $node->snData;
422 $pWrapNode = $nodeData->ancestorPNode;
423 $newParent = $this->serializer->getParentNode( $pWrapNode );
424 if ( $pWrapNode !== $this->serializer->getLastChild( $newParent ) ) {
431 $victim = $nextParent;
432 $victim->snData->ancestorPNode =
null;
433 $nextParent = $this->serializer->getParentNode( $victim );
434 }
while ( $nextParent !== $pWrapNode );
437 $victimElement =
new Element( $victim->namespace, $victim->name, $victim->attrs );
438 $victimElement->userData = $victim;
441 $this->serializer->insertElement( TreeBuilder::UNDER, $newParent, $victimElement,
442 false, $sourceStart, 0 );
445 $pWrapNode->snData->nonblankNodeCount--;
448 $newParent->snData->childPElement =
null;
451 public function endTag( Element $element, $sourceStart, $sourceLength ) {
452 $data = $element->userData->snData;
453 if ( $data->childPElement ) {
454 $this->
endTag( $data->childPElement, $sourceStart, 0 );
456 $this->serializer->endTag( $element, $sourceStart, $sourceLength );
457 $element->userData->snData =
null;
458 $element->userData =
null;
461 public function doctype(
$name, $public, $system, $quirks, $sourceStart, $sourceLength ) {
462 $this->serializer->doctype(
$name, $public, $system, $quirks,
463 $sourceStart, $sourceLength );
466 public function comment( $preposition, $refElement, $text, $sourceStart, $sourceLength ) {
468 $this->serializer->comment( $preposition, $refNode, $text,
469 $sourceStart, $sourceLength );
472 public function error( $text, $pos ) {
473 $this->serializer->error( $text, $pos );
476 public function mergeAttributes( Element $element, Attributes $attrs, $sourceStart ) {
477 $this->serializer->mergeAttributes( $element, $attrs, $sourceStart );
480 public function removeNode( Element $element, $sourceStart ) {
481 $this->serializer->removeNode( $element, $sourceStart );
485 $self = $element->userData;
486 if (
$self->snData->childPElement ) {
500 $children =
$self->children;
501 $self->children = [];
502 $this->
insertElement( TreeBuilder::UNDER, $element, $newParent,
false, $sourceStart, 0 );
503 $newParentNode = $newParent->userData;
504 $newParentId = $newParentNode->id;
505 foreach ( $children
as $child ) {
506 if ( is_object( $child ) ) {
507 $this->
trace(
"reparent <{$child->name}>" );
508 $child->parentId = $newParentId;
511 $newParentNode->children = $children;