121 if ( $tree !==
false ) {
122 $store = json_decode( $tree );
123 if ( is_array( $store ) ) {
128 $forInclusion =
$flags & Parser::PTD_FOR_INCLUSION;
130 $xmlishElements = $this->parser->getStripList();
131 $xmlishAllowMissingEndTag = [
'includeonly',
'noinclude',
'onlyinclude' ];
132 $enableOnlyinclude =
false;
133 if ( $forInclusion ) {
134 $ignoredTags = [
'includeonly',
'/includeonly' ];
135 $ignoredElements = [
'noinclude' ];
136 $xmlishElements[] =
'noinclude';
137 if ( strpos( $text,
'<onlyinclude>' ) !==
false
138 && strpos( $text,
'</onlyinclude>' ) !==
false
140 $enableOnlyinclude =
true;
143 $ignoredTags = [
'noinclude',
'/noinclude',
'onlyinclude',
'/onlyinclude' ];
144 $ignoredElements = [
'includeonly' ];
145 $xmlishElements[] =
'includeonly';
147 $xmlishRegex = implode(
'|', array_merge( $xmlishElements, $ignoredTags ) );
150 $elementsRegex =
"~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
154 $searchBase =
"[{<\n";
156 $revText = strrev( $text );
157 $lengthText = strlen( $text );
162 $accum =& $stack->getAccum();
173 $noMoreClosingTag = [];
175 $findOnlyinclude = $enableOnlyinclude;
177 $fakeLineStart =
true;
182 if ( $findOnlyinclude ) {
184 $startPos = strpos( $text,
'<onlyinclude>', $i );
185 if ( $startPos ===
false ) {
187 $accum[] = [
'ignore', [ substr( $text, $i ) ] ];
190 $tagEndPos = $startPos + strlen(
'<onlyinclude>' );
191 $accum[] = [
'ignore', [ substr( $text, $i, $tagEndPos - $i ) ] ];
193 $findOnlyinclude =
false;
196 if ( $fakeLineStart ) {
197 $found =
'line-start';
200 # Find next opening brace, closing brace or pipe
201 $search = $searchBase;
202 if ( $stack->top ===
false ) {
203 $currentClosing =
'';
205 $currentClosing = $stack->top->close;
206 $search .= $currentClosing;
216 # Output literal section, advance input counter
217 $literalLength = strcspn( $text, $search, $i );
218 if ( $literalLength > 0 ) {
220 $i += $literalLength;
222 if ( $i >= $lengthText ) {
223 if ( $currentClosing ==
"\n" ) {
232 $curChar = $text[$i];
233 if ( $curChar ==
'|' ) {
235 } elseif ( $curChar ==
'=' ) {
237 } elseif ( $curChar ==
'<' ) {
239 } elseif ( $curChar ==
"\n" ) {
243 $found =
'line-start';
245 } elseif ( $curChar == $currentClosing ) {
247 } elseif ( isset( $this->rules[$curChar] ) ) {
249 $rule = $this->rules[$curChar];
251 # Some versions of PHP have a strcspn which stops on null characters
252 # Ignore and continue
259 if ( $found ==
'angle' ) {
262 if ( $enableOnlyinclude
263 && substr( $text, $i, strlen(
'</onlyinclude>' ) ) ==
'</onlyinclude>'
265 $findOnlyinclude =
true;
270 if ( !preg_match( $elementsRegex, $text,
$matches, 0, $i + 1 ) ) {
285 $endPos = strpos( $text,
'-->', $i + 4 );
286 if ( $endPos ===
false ) {
288 $inner = substr( $text, $i );
289 $accum[] = [
'comment', [ $inner ] ];
293 $wsStart = $i ? ( $i - strspn( $revText,
" \t", $lengthText - $i ) ) : 0;
297 $wsEnd = $endPos + 2 + strspn( $text,
" \t", $endPos + 3 );
301 $comments = [ [ $wsStart, $wsEnd ] ];
302 while ( substr( $text, $wsEnd + 1, 4 ) ==
'<!--' ) {
303 $c = strpos( $text,
'-->', $wsEnd + 4 );
304 if ( $c ===
false ) {
307 $c = $c + 2 + strspn( $text,
" \t", $c + 3 );
308 $comments[] = [ $wsEnd + 1, $c ];
316 if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) ==
"\n"
317 && substr( $text, $wsEnd + 1, 1 ) ==
"\n"
320 $wsLength = $i - $wsStart;
321 $endIndex = count( $accum ) - 1;
326 && is_string( $accum[$endIndex] )
327 && strspn( $accum[$endIndex],
" \t", -$wsLength ) === $wsLength
329 $accum[$endIndex] = substr( $accum[$endIndex], 0, -$wsLength );
333 foreach ( $comments
as $j => $com ) {
335 $endPos = $com[1] + 1;
336 if ( $j == ( count( $comments ) - 1 ) ) {
339 $inner = substr( $text, $startPos, $endPos - $startPos );
340 $accum[] = [
'comment', [ $inner ] ];
344 $fakeLineStart =
true;
352 $part = $stack->top->getCurrentPart();
353 if ( !( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) ) {
354 $part->visualEnd = $wsStart;
357 $part->commentEnd = $endPos;
360 $inner = substr( $text, $startPos, $endPos - $startPos + 1 );
361 $accum[] = [
'comment', [ $inner ] ];
366 $lowerName = strtolower(
$name );
367 $attrStart = $i + strlen(
$name ) + 1;
370 $tagEndPos = $noMoreGT ?
false : strpos( $text,
'>', $attrStart );
371 if ( $tagEndPos ===
false ) {
381 if ( in_array( $lowerName, $ignoredTags ) ) {
382 $accum[] = [
'ignore', [ substr( $text, $i, $tagEndPos - $i + 1 ) ] ];
388 if ( $text[$tagEndPos - 1] ==
'/' ) {
390 $attrEnd = $tagEndPos - 1;
395 $attrEnd = $tagEndPos;
398 !isset( $noMoreClosingTag[
$name] ) &&
399 preg_match(
"/<\/" . preg_quote(
$name,
'/' ) .
"\s*>/i",
400 $text,
$matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 )
402 $inner = substr( $text, $tagEndPos + 1,
$matches[0][1] - $tagEndPos - 1 );
407 if ( in_array(
$name, $xmlishAllowMissingEndTag ) ) {
409 $inner = substr( $text, $tagEndPos + 1 );
416 substr( $text, $tagStartPos, $tagEndPos + 1 - $tagStartPos ) );
418 $noMoreClosingTag[
$name] =
true;
424 if ( in_array( $lowerName, $ignoredElements ) ) {
425 $accum[] = [
'ignore', [ substr( $text, $tagStartPos, $i - $tagStartPos ) ] ];
429 if ( $attrEnd <= $attrStart ) {
434 $attr = substr( $text, $attrStart, $attrEnd - $attrStart );
438 [
'name', [
$name ] ],
439 [
'attr', [ $attr ] ] ];
440 if ( $inner !==
null ) {
441 $children[] = [
'inner', [ $inner ] ];
443 if ( $close !==
null ) {
444 $children[] = [
'close', [ $close ] ];
446 $accum[] = [
'ext', $children ];
447 } elseif ( $found ==
'line-start' ) {
450 if ( $fakeLineStart ) {
451 $fakeLineStart =
false;
457 $count = strspn( $text,
'=', $i, 6 );
458 if (
$count == 1 && $findEquals ) {
470 $stack->push( $piece );
471 $accum =& $stack->getAccum();
472 extract( $stack->getFlags() );
475 } elseif ( $found ==
'line-end' ) {
476 $piece = $stack->top;
478 assert( $piece->open ===
"\n" );
479 $part = $piece->getCurrentPart();
483 $wsLength = strspn( $revText,
" \t", $lengthText - $i );
484 $searchStart = $i - $wsLength;
485 if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
488 $searchStart = $part->visualEnd;
489 $searchStart -= strspn( $revText,
" \t", $lengthText - $searchStart );
492 $equalsLength = strspn( $revText,
'=', $lengthText - $searchStart );
493 if ( $equalsLength > 0 ) {
494 if ( $searchStart - $equalsLength == $piece->startPos ) {
509 $element = [ [
'possible-h',
513 [
'@i', [ $headingIndex++ ] ]
528 $accum =& $stack->getAccum();
529 extract( $stack->getFlags() );
532 array_splice( $accum, count( $accum ), 0, $element );
539 } elseif ( $found ==
'open' ) {
540 # count opening brace characters
541 $count = strspn( $text, $curChar, $i );
543 # we need to add to stack only if opening brace count is enough for one of the rules
544 if (
$count >= $rule[
'min'] ) {
545 # Add it to the stack
548 'close' => $rule[
'end'],
550 'lineStart' => ( $i > 0 && $text[$i - 1] ==
"\n" ),
553 $stack->push( $piece );
554 $accum =& $stack->getAccum();
555 extract( $stack->getFlags() );
557 # Add literal brace(s)
561 } elseif ( $found ==
'close' ) {
562 $piece = $stack->top;
563 # lets check if there are enough characters for closing brace
564 $maxCount = $piece->count;
565 $count = strspn( $text, $curChar, $i, $maxCount );
567 # check for maximum matching characters (if there are 5 closing
568 # characters, we will probably need only 3 - depending on the rules)
569 $rule = $this->rules[$piece->open];
570 if (
$count > $rule[
'max'] ) {
571 # The specified maximum exists in the callback array, unless the caller
573 $matchingCount = $rule[
'max'];
575 # Count is less than the maximum
576 # Skip any gaps in the callback array to find the true largest match
577 # Need to use array_key_exists not isset because the callback can be null
579 while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule[
'names'] ) ) {
584 if ( $matchingCount <= 0 ) {
585 # No matching element found in callback array
586 # Output a literal closing brace and continue
591 $name = $rule[
'names'][$matchingCount];
592 if (
$name ===
null ) {
594 $element = $piece->breakSyntax( $matchingCount );
598 $parts = $piece->parts;
599 $titleAccum = $parts[0]->out;
604 # The invocation is at the start of the line if lineStart is set in
605 # the stack, and all opening brackets are used up.
606 if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
607 $children[] = [
'@lineStart', [ 1 ] ];
609 $titleNode = [
'title', $titleAccum ];
610 $children[] = $titleNode;
612 foreach ( $parts
as $part ) {
613 if ( isset( $part->eqpos ) ) {
614 $equalsNode = $part->out[$part->eqpos];
615 $nameNode = [
'name', array_slice( $part->out, 0, $part->eqpos ) ];
616 $valueNode = [
'value', array_slice( $part->out, $part->eqpos + 1 ) ];
617 $partNode = [
'part', [ $nameNode, $equalsNode, $valueNode ] ];
618 $children[] = $partNode;
620 $nameNode = [
'name', [ [
'@index', [ $argIndex++ ] ] ] ];
621 $valueNode = [
'value', $part->out ];
622 $partNode = [
'part', [ $nameNode, $valueNode ] ];
623 $children[] = $partNode;
626 $element = [ [
$name, $children ] ];
629 # Advance input pointer
630 $i += $matchingCount;
634 $accum =& $stack->getAccum();
636 # Re-add the old stack element if it still has unmatched opening characters remaining
637 if ( $matchingCount < $piece->count ) {
639 $piece->count -= $matchingCount;
640 # do we still qualify for any callback with remaining count?
641 $min = $this->rules[$piece->open][
'min'];
642 if ( $piece->count >= $min ) {
643 $stack->push( $piece );
644 $accum =& $stack->getAccum();
650 extract( $stack->getFlags() );
652 # Add XML element to the enclosing accumulator
653 array_splice( $accum, count( $accum ), 0, $element );
654 } elseif ( $found ==
'pipe' ) {
657 $accum =& $stack->getAccum();
659 } elseif ( $found ==
'equals' ) {
661 $accum[] = [
'equals', [
'=' ] ];
662 $stack->getCurrentPart()->eqpos = count( $accum ) - 1;
667 # Output any remaining unclosed brackets
668 foreach ( $stack->stack
as $piece ) {
669 array_splice( $stack->rootAccum, count( $stack->rootAccum ), 0, $piece->breakSyntax() );
672 # Enable top-level headings
673 foreach ( $stack->rootAccum
as &$node ) {
679 $rootStore = [ [
'root', $stack->rootAccum ] ];
683 $tree = json_encode( $rootStore, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
684 if ( $tree !==
false ) {