Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/vendor/wikimedia/parsoid/src/Wt2Html/DOM/Handlers/TableFixups.php
Ðазад
<?php declare( strict_types = 1 ); namespace Wikimedia\Parsoid\Wt2Html\DOM\Handlers; use Wikimedia\Assert\Assert; use Wikimedia\Parsoid\Config\Env; use Wikimedia\Parsoid\Core\Sanitizer; use Wikimedia\Parsoid\DOM\Comment; use Wikimedia\Parsoid\DOM\Element; use Wikimedia\Parsoid\DOM\Node; use Wikimedia\Parsoid\DOM\Text; use Wikimedia\Parsoid\NodeData\DataMw; use Wikimedia\Parsoid\NodeData\TempData; use Wikimedia\Parsoid\NodeData\TemplateInfo; use Wikimedia\Parsoid\Utils\DiffDOMUtils; use Wikimedia\Parsoid\Utils\DOMCompat; use Wikimedia\Parsoid\Utils\DOMDataUtils; use Wikimedia\Parsoid\Utils\DOMUtils; use Wikimedia\Parsoid\Utils\DTState; use Wikimedia\Parsoid\Utils\PHPUtils; use Wikimedia\Parsoid\Utils\Utils; use Wikimedia\Parsoid\Utils\WTUtils; use Wikimedia\Parsoid\Wt2Html\Frame; use Wikimedia\Parsoid\Wt2Html\PegTokenizer; /** * Provides DOMTraverser visitors that fix template-induced interrupted table cell parsing * by recombining table cells and/or reparsing table cell content as attributes. * - handleTableCellTemplates */ class TableFixups { private static function isSimpleTemplatedSpan( Node $node ): bool { return DOMCompat::nodeName( $node ) === 'span' && DOMUtils::hasTypeOf( $node, 'mw:Transclusion' ) && DOMUtils::allChildrenAreTextOrComments( $node ); } /** * @param list<string|TemplateInfo> &$parts * @param Frame $frame * @param int $offset1 * @param int $offset2 */ private static function fillDSRGap( array &$parts, Frame $frame, int $offset1, int $offset2 ): void { if ( $offset1 < $offset2 ) { $parts[] = PHPUtils::safeSubstr( $frame->getSrcText(), $offset1, $offset2 - $offset1 ); } } /** * Hoist transclusion information from cell content / attributes * onto the cell itself. */ private static function hoistTransclusionInfo( DTState $dtState, array $transclusions, Element $td ): void { // Initialize dsr for $td // In `handleTableCellTemplates`, we're creating a cell w/o dsr info. $tdDp = DOMDataUtils::getDataParsoid( $td ); if ( !Utils::isValidDSR( $tdDp->dsr ?? null ) ) { $tplDp = DOMDataUtils::getDataParsoid( $transclusions[0] ); Assert::invariant( Utils::isValidDSR( $tplDp->dsr ?? null ), 'Expected valid DSR' ); $tdDp->dsr = clone $tplDp->dsr; } // Build up $parts, $pi to set up the combined transclusion info on $td. // Note that content for all but the last template has been swallowed into // the attributes of $td. $parts = []; $pi = []; $lastTpl = null; $prevDp = null; $frame = $dtState->options['frame']; $index = 0; foreach ( $transclusions as $i => $tpl ) { $tplDp = DOMDataUtils::getDataParsoid( $tpl ); Assert::invariant( Utils::isValidDSR( $tplDp->dsr ?? null ), 'Expected valid DSR' ); // Plug DSR gaps between transclusions if ( !$prevDp ) { self::fillDSRGap( $parts, $frame, $tdDp->dsr->start, $tplDp->dsr->start ); } else { self::fillDSRGap( $parts, $frame, $prevDp->dsr->end, $tplDp->dsr->start ); } // Assimilate $tpl's data-mw and data-parsoid pi info $dmw = DOMDataUtils::getDataMw( $tpl ); foreach ( $dmw->parts ?? [] as $part ) { // Template index is relative to other transclusions. // This index is used to extract whitespace information from // data-parsoid and that array only includes info for templates. // So skip over strings here. if ( !is_string( $part ) ) { // Cloning is strictly not needed here, but mimicking // code in WrapSectionsState.php $part = clone $part; $part->i = $index++; } $parts[] = $part; } PHPUtils::pushArray( $pi, $tplDp->pi ?? [ [] ] ); DOMDataUtils::setDataMw( $tpl, null ); $lastTpl = $tpl; $prevDp = $tplDp; } $aboutId = DOMCompat::getAttribute( $lastTpl, 'about' ); // Hoist transclusion information to $td. $td->setAttribute( 'typeof', 'mw:Transclusion' ); $td->setAttribute( 'about', $aboutId ); // Add wikitext for the table cell content following $lastTpl self::fillDSRGap( $parts, $frame, $prevDp->dsr->end, $tdDp->dsr->end ); // Save the new data-mw on the td $dmw = new DataMw( [] ); $dmw->parts = $parts; DOMDataUtils::setDataMw( $td, $dmw ); $tdDp->pi = $pi; // td wraps everything now. // Remove template encapsulation from here on. // This simplifies the problem of analyzing the <td> // for additional fixups (|| Boo || Baz) by potentially // invoking 'reparseTemplatedAttributes' on split cells // with some modifications. $child = $lastTpl; // Transclusions may be nested in elements in some ugly wikitext so // make sure we're starting at a direct descendant of td while ( $child->parentNode !== $td ) { $child = $child->parentNode; } while ( $child ) { if ( DOMCompat::nodeName( $child ) === 'span' && DOMCompat::getAttribute( $child, 'about' ) === $aboutId ) { // Remove the encapsulation attributes. If there are no more attributes left, // the span wrapper is useless and can be removed. $child->removeAttribute( 'about' ); $child->removeAttribute( 'typeof' ); if ( DOMDataUtils::noAttrs( $child ) ) { $next = $child->firstChild ?: $child->nextSibling; DOMUtils::migrateChildren( $child, $td, $child ); $child->parentNode->removeChild( $child ); $child = $next; } else { $child = $child->nextSibling; } } else { $child = $child->nextSibling; } } // $dtState->tplInfo can be null when information is hoisted // from children to $td because DOMTraverser hasn't seen the // children yet! if ( !$dtState->tplInfo ) { $dtState->tplInfo = (object)[ 'first' => $td, 'last' => $td, 'clear' => false ]; } } /** * Collect potential attribute content. * * We expect this to be text nodes without a pipe character followed by one or * more nowiki spans, followed by a template encapsulation with pure-text and * nowiki content. Collection stops when encountering a pipe character. * * @param Env $env * @param Element $cell known to be <td> / <th> * @param ?Element $templateWrapper * @return ?array */ public static function collectAttributishContent( Env $env, Element $cell, ?Element $templateWrapper ): ?array { $buf = []; $nowikis = []; $transclusions = $templateWrapper ? [ $templateWrapper ] : []; // Some of this logic could be replaced by DSR-based recovery of // wikitext that is outside templates. But since we have to walk over // templated content in this fashion anyway, we might as well use the // same logic uniformly. $traverse = static function ( ?Node $child ) use ( &$traverse, &$buf, &$nowikis, &$transclusions ): bool { while ( $child ) { if ( $child instanceof Comment ) { // Legacy parser strips comments during parsing => drop them. } elseif ( $child instanceof Text ) { $text = $child->nodeValue; $buf[] = $text; // Are we done accumulating? if ( preg_match( '/(?:^|[^|])\|(?:[^|]|$)/D', $text ) ) { return true; } } else { '@phan-var Element $child'; /** @var Element $child */ if ( DOMUtils::hasTypeOf( $child, 'mw:Transclusion' ) ) { $transclusions[] = $child; } if ( WTUtils::isFirstExtensionWrapperNode( $child ) ) { // "|" chars in extension content don't trigger table-cell parsing // since they have higher precedence in tokenization. The extension // content will simply be dropped (but any side effects it had will // continue to apply. Ex: <ref> tags might leave an orphaned ref in // the <references> section). $child = WTUtils::skipOverEncapsulatedContent( $child ); continue; } elseif ( DOMUtils::hasTypeOf( $child, 'mw:Entity' ) ) { // Get entity's wikitext source, not rendered content. // " " is "\n" which breaks attribute parsing! $buf[] = DOMDataUtils::getDataParsoid( $child )->src ?? $child->textContent; } elseif ( DOMUtils::hasTypeOf( $child, 'mw:Nowiki' ) ) { // Nowiki span were added to protect otherwise // meaningful wikitext chars used in attributes. // Save the content and add in a marker to splice out later. $nowikis[] = $child->textContent; $buf[] = '<nowiki-marker>'; } elseif ( DOMUtils::hasRel( $child, 'mw:WikiLink' ) || WTUtils::isGeneratedFigure( $child ) ) { // Wikilinks/images abort attribute parsing return true; } else { if ( $traverse( $child->firstChild ) ) { return true; } } } $child = $child->nextSibling; } return false; }; if ( $traverse( $cell->firstChild ) ) { return [ 'txt' => implode( '', $buf ), 'nowikis' => $nowikis, 'transclusions' => $transclusions, ]; } else { return null; } } /** * T46498, second part of T52603 * * Handle wikitext like * ``` * {| * |{{nom|Bar}} * |} * ``` * where nom expands to `style="foo" class="bar"|Bar`. The attributes are * tokenized and stripped from the table contents. * * This method works well for the templates documented in * https://en.wikipedia.org/wiki/Template:Table_cell_templates/doc * * Nevertheless, there are some limitations: * - We assume that attributes don't contain wiki markup (apart from <nowiki>) * and end up in text or nowiki nodes. * - Only a single table cell is produced / opened by the template that * contains the attributes. This limitation could be lifted with more * aggressive re-parsing if really needed in practice. * - There is only a single transclusion in the table cell content. This * limitation can be lifted with more advanced data-mw construction. * * $cell known to be <td> / <th> */ public static function reparseTemplatedAttributes( DTState $dtState, Element $cell, ?Element $templateWrapper ): void { $env = $dtState->env; $frame = $dtState->options['frame']; // Collect attribute content and examine it $attributishContent = self::collectAttributishContent( $env, $cell, $templateWrapper ); if ( !$attributishContent ) { return; } /** * FIXME: These checks are insufficient. * Previous rounds of table fixups might have created this cell without * any templated content (the while loop in handleTableCellTemplates). * Till we figure out a reliable test for this, we'll reparse attributes always. * * // This DOM pass is trying to bridge broken parses across * // template boundaries. So, if templates aren't involved, * // no reason to reparse. * if ( count( $attributishContent['transclusions'] ) === 0 && * !WTUtils::fromEncapsulatedContent( $cell ) * ) { * return; * } */ $attrText = $attributishContent['txt']; if ( !preg_match( '/(^[^|]+\|)([^|]|$)/D', $attrText, $matches ) ) { return; } $attributishPrefix = $matches[1]; // Splice in nowiki content. We added in <nowiki> markers to prevent the // above regexps from matching on nowiki-protected chars. if ( str_contains( $attributishPrefix, '<nowiki-marker>' ) ) { $attributishPrefix = preg_replace_callback( '/<nowiki-marker>/', static function ( $unused ) use ( &$attributishContent ) { // This is a little tricky. We want to use the content from the // nowikis to reparse the string to key/val pairs but the rule, // single_cell_table_args, will invariably get tripped up on // newlines which, to this point, were shuttled through in the // nowiki. Core sanitizer will do this replacement in attr vals // so it's a safe normalization to do here. return preg_replace( '/\s+/', ' ', array_shift( $attributishContent['nowikis'] ) ); }, $attributishPrefix ); } // re-parse the attributish prefix if ( !$dtState->tokenizer ) { $dtState->tokenizer = new PegTokenizer( $env ); } $attributeTokens = $dtState->tokenizer->tokenizeTableCellAttributes( $attributishPrefix, false ); // No attributes => nothing more to do! if ( !$attributeTokens ) { return; } // Note that `row_syntax_table_args` (the rule used for tokenizing above) // returns an array consisting of [table_attributes, spaces, pipe] $attrs = $attributeTokens[0]; // Sanitize attrs and transfer them to the td node Sanitizer::applySanitizedArgs( $env->getSiteConfig(), $cell, $attrs ); $cellDp = DOMDataUtils::getDataParsoid( $cell ); // Reparsed cells start off as non-mergeable-table cells // and preserve that property after reparsing $cellDp->setTempFlag( TempData::NON_MERGEABLE_TABLE_CELL ); $cellDp->setTempFlag( TempData::NO_ATTRS, false ); // If the transclusion node was embedded within the td node, // lift up the about group to the td node. $transclusions = $attributishContent['transclusions']; if ( $transclusions && ( $cell !== $transclusions[0] || count( $transclusions ) > 1 ) ) { self::hoistTransclusionInfo( $dtState, $transclusions, $cell ); } // Drop content that has been consumed by the reparsed attribute content. // NOTE: We serialize and reparse data-object-id attributes as well which // ensures stashed data-* attributes continue to be usable. // FIXME: This is too naive. What about all the care we showed in `collectAttributishContent`? DOMCompat::setInnerHTML( $cell, preg_replace( '/^[^|]*\|/', '', DOMCompat::getInnerHTML( $cell ) ) ); } /** * Possibilities: * - $cell and $prev are both <td>s (or both <th>s) * - Common case * - Ex: "|align=left {{tpl returning | foobar}}" * So, "|align=left |foobar" is the combined string * - Combined cell is a <td> (will be <th> if both were <th>s) * - We assign new attributes to $cell and drop $prev * - $cell is <td> and $prev is <th> * - Ex: "!align=left {{tpl returning | foobar}}" * So, "!align=left |foobar" is the combined string * - The combined cell will be a <th> with attributes "align=left" * and content "foobar" * - $cell is <th> and $prev is <td> * - Ex: "|align=left {{tpl returning !scope=row | foobar}}" * So "|align=left !scope=row | foobar" is the combined string * and we need to drop the th-attributes entirely after combining * - The combined cell will be a <td> * - $cell's attribute is dropped * - $prev's content is dropped * * FIXME: There are a number of other merge possibilities that end up * in this function that aren't accounted for yet! Couple of them are * in the unsupported scenario 1/2 buckets below. * * @param DTState $dtState * @param Element $cell * @return bool */ private static function combineAttrsWithPreviousCell( DTState $dtState, Element $cell ): bool { // UNSUPPORTED SCENARIO 1: // In this cell-combining scenario, $prev can have attributes only if it // also had content. See example below: // Ex: |class="foo"|bar{{1x|1={{!}}foo}} // should parse as <td class="foo">bar|foo</td> // In this case, the attributes/content of $cell would end up as the // content of the combined cell. // // UNSUPPORTED SCENARIO 2: // The template produced attributes as well as maybe a new cell. // Ex: |class="foo"{{1x| foo}} and |class="foo"{{1x| foo}} // We let the more general 'reparseTemplatedAttributes' code handle // this scenario for now. $prev = $cell->previousSibling; DOMUtils::assertElt( $prev ); $prevDp = DOMDataUtils::getDataParsoid( $prev ); // If $prevDp has attributes already, we don't want to reparse content // as the attributes. However, we might be in unsupported scenario 1 // above, but that's definitionally still unsupported so bail for now. if ( !$prevDp->getTempFlag( TempData::NO_ATTRS ) ) { return false; } // Build the attribute string $frame = $dtState->options['frame']; $prevCellSrc = PHPUtils::safeSubstr( $frame->getSrcText(), $prevDp->dsr->start, $prevDp->dsr->length() ); $reparseSrc = substr( $prevCellSrc, $prevDp->dsr->openWidth ); // The previous cell had NO_ATTRS, from the check above, but the cell // ends in a vertical bar. This isn't a scenario where we'll combine // the cell content to form attributes, so there's no sense in trying // to tokenize them below; they probably already failed during the // original tokenizing However, the trailing vertical probably does // want to be hoisted into the next cell, to combine to form row syntax. if ( substr( $reparseSrc, -1 ) === "|" ) { return false; } // "|" or "!", but doesn't matter since we discard that anyway $reparseSrc .= "|"; // Reparse the attributish prefix $env = $dtState->env; if ( !$dtState->tokenizer ) { $dtState->tokenizer = new PegTokenizer( $env ); } $attributeTokens = $dtState->tokenizer->tokenizeTableCellAttributes( $reparseSrc, false ); if ( !is_array( $attributeTokens ) ) { $env->log( "error/wt2html", "TableFixups: Failed to successfully reparse $reparseSrc as table cell attributes" ); return false; } // Update data-mw, DSR if $cell is an encapsulation wrapper $cellDp = DOMDataUtils::getDataParsoid( $cell ); if ( DOMUtils::hasTypeOf( $cell, 'mw:Transclusion' ) ) { $dataMW = DOMDataUtils::getDataMw( $cell ); array_unshift( $dataMW->parts, $prevCellSrc ); $cellDSR = $cellDp->dsr ?? null; if ( $cellDSR && $cellDSR->start ) { $cellDSR->start -= strlen( $prevCellSrc ); } } $parent = $cell->parentNode; if ( DOMCompat::nodeName( $cell ) === DOMCompat::nodeName( $prev ) ) { // Matching cell types $combinedCell = $cell; $combinedCellDp = $cellDp; $parent->removeChild( $prev ); } else { // Different cell types $combinedCell = $prev; // Remove all content on $prev which will // become the new combined cell while ( $prev->firstChild ) { $prev->removeChild( $prev->firstChild ); } // Note that this implicitly migrates data-mw and data-parsoid foreach ( DOMUtils::attributes( $cell ) as $k => $v ) { $combinedCell->setAttribute( $k, $v ); } DOMUtils::migrateChildren( $cell, $combinedCell ); $parent->removeChild( $cell ); $combinedCellDp = DOMDataUtils::getDataParsoid( $combinedCell ); } // Note that `row_syntax_table_args` (the rule used for tokenizing above) // returns an array consisting of [table_attributes, spaces, pipe] $attrs = $attributeTokens[0]; Sanitizer::applySanitizedArgs( $env->getSiteConfig(), $combinedCell, $attrs ); // Combined cells don't merge further $combinedCellDp->setTempFlag( TempData::NON_MERGEABLE_TABLE_CELL ); $combinedCellDp->setTempFlag( TempData::NO_ATTRS, false ); return true; } private const NO_REPARSING = 0; private const COMBINE_WITH_PREV_CELL = 1; private const OTHER_REPARSE = 2; /** * $cell is known to be <td>/<th> */ private static function getReparseType( Element $cell, DTState $dtState ): int { $inTplContent = $dtState->tplInfo !== null; $dp = DOMDataUtils::getDataParsoid( $cell ); if ( !$dp->getTempFlag( TempData::NON_MERGEABLE_TABLE_CELL ) && !$dp->getTempFlag( TempData::FAILED_REPARSE ) && // This is a good proxy for what we need: "Is $cell a template wrapper?". // That info won't be available for nested templates unless we want // to use a more expensive hacky check. // "inTplContent" is sufficient because we won't have mergeable // cells for wikitext that doesn't get any part of its content from // templates because NON_MERGEABLE_TABLE_CELL prevents such merges. $inTplContent ) { // Look for opportunities where table cells could combine. This requires // $cell to be a templated cell. But, we don't support combining // templated cells with other templated cells. So, previous sibling // cannot be templated. // // So, bail out of scenarios where prevDp comes from a template (the checks // for isValidDSR( $prevDp-> dsr ) and valid opening tag width catch this. $prev = $cell->previousSibling; $prevDp = $prev instanceof Element ? DOMDataUtils::getDataParsoid( $prev ) : null; if ( $prevDp && !WTUtils::hasLiteralHTMLMarker( $prevDp ) && Utils::isValidDSR( $prevDp->dsr ?? null, true ) && !DOMUtils::hasTypeOf( $prev, 'mw:Transclusion' ) && !str_contains( DOMCompat::getInnerHTML( $prev ), "\n" ) ) { return self::COMBINE_WITH_PREV_CELL; } } $cellIsTd = DOMCompat::nodeName( $cell ) === 'td'; $testRE = $cellIsTd ? '/[|]/' : '/[!|]/'; $child = $cell->firstChild; while ( $child ) { if ( !$inTplContent && DOMUtils::hasTypeOf( $child, 'mw:Transclusion' ) ) { $inTplContent = true; } if ( $inTplContent && $child instanceof Text && preg_match( $testRE, $child->textContent ) ) { return self::OTHER_REPARSE; } if ( $child instanceof Element ) { if ( WTUtils::isFirstExtensionWrapperNode( $child ) ) { // "|" chars in extension/language variant content don't trigger // table-cell parsing since they have higher precedence in tokenization $child = WTUtils::skipOverEncapsulatedContent( $child ); } else { if ( DOMUtils::hasRel( $child, 'mw:WikiLink' ) || WTUtils::isGeneratedFigure( $child ) ) { // Wikilinks/images abort attribute parsing return self::NO_REPARSING; } // FIXME: Ugly for now $outerHTML = DOMCompat::getOuterHTML( $child ); if ( preg_match( $testRE, $outerHTML ) && ( $inTplContent || preg_match( '/"mw:Transclusion"/', $outerHTML ) ) ) { // A "|" char in the HTML will trigger table cell tokenization. // Ex: "| foobar <div> x | y </div>" will split the <div> // in table-cell tokenization context. return self::OTHER_REPARSE; } $child = $child->nextSibling; } } else { $child = $child->nextSibling; } } return self::NO_REPARSING; } /** * In a wikitext-syntax-table-parsing context, the meaning of * "|", "||", "!", "!!" is context-sensitive. Additionally, the * complete syntactical construct for a table cell (including leading * pipes, attributes, and content-separating pipe char) might straddle * a template boundary - with some content coming from the top-level and * some from a template. * * This impacts parsing of tables when some cells are templated since * Parsoid parses template content independent of top-level content * (without any preceding context). This means that Parsoid's table-cell * parsing in templated contexts might be incorrect * * To deal with this, Parsoid implements this table-fixups pass that * has to deal with cell-merging and cell-reparsing scenarios. * * HTML-syntax cells and non-templated cells without any templated content * are not subject to this transformation and can be skipped right away. * * FIXME: This pass can benefit from a customized procsssor rather than * piggyback on top of DOMTraverser since the DOM can be significantly * mutated in these handlers. * * @param Element $cell $cell is known to be <td>/<th> * @param DTState $dtState * @return mixed */ public static function handleTableCellTemplates( Element $cell, DTState $dtState ) { if ( WTUtils::isLiteralHTMLNode( $cell ) ) { return true; } // Deal with <th> special case where "!! foo" is parsed as <th>! foo</th> // but should have been parsed as <th>foo</th> when not the first child if ( DOMCompat::nodeName( $cell ) === 'th' && DOMUtils::hasTypeOf( $cell, 'mw:Transclusion' ) && // This is checking that previous sibling is not "\n" which would // signal that this <th> is on a fresh line and the "!" shouldn't be stripped. // If this weren't template output, we would check for "stx" === 'row'. // FIXME: Note that ths check is fragile and doesn't work always, but this is // the price we pay for Parsoid's independent template parsing! $cell->previousSibling instanceof Element ) { $fc = DiffDOMUtils::firstNonSepChild( $cell ); if ( $fc instanceof Text ) { $leadingText = $fc->nodeValue; if ( str_starts_with( $leadingText, "!" ) ) { $fc->nodeValue = substr( $leadingText, 1 ); } } } $reparseType = self::getReparseType( $cell, $dtState ); if ( $reparseType === self::NO_REPARSING ) { return true; } $cellDp = DOMDataUtils::getDataParsoid( $cell ); if ( $reparseType === self::COMBINE_WITH_PREV_CELL ) { if ( self::combineAttrsWithPreviousCell( $dtState, $cell ) ) { return true; } else { // Clear property and retry $cell for other reparses // The DOMTraverser will resume the handler on the // returned $cell. $cellDp->setTempFlag( TempData::FAILED_REPARSE ); return $cell; } } // If the cell didn't have attrs, extract and reparse templated attrs if ( $cellDp->getTempFlag( TempData::NO_ATTRS ) ) { $frame = $dtState->options['frame']; $templateWrapper = DOMUtils::hasTypeOf( $cell, 'mw:Transclusion' ) ? $cell : null; self::reparseTemplatedAttributes( $dtState, $cell, $templateWrapper ); } // Now, examine the <td> to see if it hides additional <td>s // and split it up if required. // // DOMTraverser will process the new cell and invoke // handleTableCellTemplates on it which ensures that // if any addition attribute fixup or splits are required, // they will get done. $newCell = null; $isTd = DOMCompat::nodeName( $cell ) === 'td'; $ownerDoc = $cell->ownerDocument; $child = $cell->firstChild; while ( $child ) { $next = $child->nextSibling; if ( $newCell ) { $newCell->appendChild( $child ); } elseif ( $child instanceof Text || self::isSimpleTemplatedSpan( $child ) ) { // FIXME: This skips over scenarios like <div>foo||bar</div>. $cellName = DOMCompat::nodeName( $cell ); $hasSpanWrapper = !( $child instanceof Text ); $match = $match1 = $match2 = null; // Find the first match of || preg_match( '/^((?:[^|]*(?:\|[^|])?)*)\|\|([^|].*)?$/D', $child->textContent, $match1 ); if ( $isTd ) { $match = $match1; } else { // Find the first match !! preg_match( '/^((?:[^!]*(?:\![^!])?)*)\!\!([^!].*)?$/D', $child->textContent, $match2 ); // Pick the shortest match if ( $match1 && $match2 ) { $match = strlen( $match1[1] ?? '' ) < strlen( $match2[1] ?? '' ) ? $match1 : $match2; } else { $match = $match1 ?: $match2; } } if ( $match ) { $child->textContent = $match[1] ?? ''; $newCell = $ownerDoc->createElement( $cellName ); if ( $hasSpanWrapper ) { /** * $hasSpanWrapper above ensures $child is a span. * * @var Element $child */ '@phan-var Element $child'; // Fix up transclusion wrapping $about = DOMCompat::getAttribute( $child, 'about' ); self::hoistTransclusionInfo( $dtState, [ $child ], $cell ); } else { // Refetch the about attribute since 'reparseTemplatedAttributes' // might have added one to it. $about = DOMCompat::getAttribute( $cell, 'about' ); } // about may not be present if the cell was inside // wrapped template content rather than being part // of the outermost wrapper. if ( $about !== null ) { $newCell->setAttribute( 'about', $about ); if ( $dtState->tplInfo && $dtState->tplInfo->last === $cell ) { $dtState->tplInfo->last = $newCell; } } $newCell->appendChild( $ownerDoc->createTextNode( $match[2] ?? '' ) ); $cell->parentNode->insertBefore( $newCell, $cell->nextSibling ); // Set data-parsoid noAttrs flag $newCellDp = DOMDataUtils::getDataParsoid( $newCell ); // This new cell has 'row' stx (would be set if the tokenizer had parsed it) $newCellDp->stx = 'row'; $newCellDp->setTempFlag( TempData::NO_ATTRS ); // It is important to set this so that when $newCell is processed by this pass, // it won't accidentally recombine again with the previous cell! $newCellDp->setTempFlag( TempData::NON_MERGEABLE_TABLE_CELL ); } } $child = $next; } return true; } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка