MediaWiki  1.34.0
PPFrame_DOM.php
Go to the documentation of this file.
1 <?php
28 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
29 class PPFrame_DOM implements PPFrame {
30 
34  public $preprocessor;
35 
39  public $parser;
40 
44  public $title;
45  public $titleCache;
46 
52 
57  public $depth;
58 
59  private $volatile = false;
60  private $ttl = null;
61 
66 
71  public function __construct( $preprocessor ) {
72  $this->preprocessor = $preprocessor;
73  $this->parser = $preprocessor->parser;
74  $this->title = $this->parser->getTitle();
75  $this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
76  $this->loopCheckHash = [];
77  $this->depth = 0;
78  $this->childExpansionCache = [];
79  }
80 
90  public function newChild( $args = false, $title = false, $indexOffset = 0 ) {
91  $namedArgs = [];
92  $numberedArgs = [];
93  if ( $title === false ) {
95  }
96  if ( $args !== false ) {
97  $xpath = false;
98  if ( $args instanceof PPNode_DOM ) {
99  $args = $args->node;
100  }
101  // @phan-suppress-next-line PhanTypeSuspiciousNonTraversableForeach
102  foreach ( $args as $arg ) {
103  if ( $arg instanceof PPNode_DOM ) {
104  $arg = $arg->node;
105  }
106  if ( !$xpath || $xpath->document !== $arg->ownerDocument ) {
107  $xpath = new DOMXPath( $arg->ownerDocument );
108  }
109 
110  $nameNodes = $xpath->query( 'name', $arg );
111  $value = $xpath->query( 'value', $arg );
112  if ( $nameNodes->item( 0 )->hasAttributes() ) {
113  // Numbered parameter
114  $index = $nameNodes->item( 0 )->attributes->getNamedItem( 'index' )->textContent;
115  $index = $index - $indexOffset;
116  if ( isset( $namedArgs[$index] ) || isset( $numberedArgs[$index] ) ) {
117  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
118  wfEscapeWikiText( $this->title ),
120  wfEscapeWikiText( $index ) )->text() );
121  $this->parser->addTrackingCategory( 'duplicate-args-category' );
122  }
123  $numberedArgs[$index] = $value->item( 0 );
124  unset( $namedArgs[$index] );
125  } else {
126  // Named parameter
127  $name = trim( $this->expand( $nameNodes->item( 0 ), PPFrame::STRIP_COMMENTS ) );
128  if ( isset( $namedArgs[$name] ) || isset( $numberedArgs[$name] ) ) {
129  $this->parser->getOutput()->addWarning( wfMessage( 'duplicate-args-warning',
130  wfEscapeWikiText( $this->title ),
132  wfEscapeWikiText( $name ) )->text() );
133  $this->parser->addTrackingCategory( 'duplicate-args-category' );
134  }
135  $namedArgs[$name] = $value->item( 0 );
136  unset( $numberedArgs[$name] );
137  }
138  }
139  }
140  return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
141  }
142 
150  public function cachedExpand( $key, $root, $flags = 0 ) {
151  // we don't have a parent, so we don't have a cache
152  return $this->expand( $root, $flags );
153  }
154 
161  public function expand( $root, $flags = 0 ) {
162  static $expansionDepth = 0;
163  if ( is_string( $root ) ) {
164  return $root;
165  }
166 
167  if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
168  $this->parser->limitationWarn( 'node-count-exceeded',
169  $this->parser->mPPNodeCount,
170  $this->parser->mOptions->getMaxPPNodeCount()
171  );
172  return '<span class="error">Node-count limit exceeded</span>';
173  }
174 
175  if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
176  $this->parser->limitationWarn( 'expansion-depth-exceeded',
177  $expansionDepth,
178  $this->parser->mOptions->getMaxPPExpandDepth()
179  );
180  return '<span class="error">Expansion depth limit exceeded</span>';
181  }
182  ++$expansionDepth;
183  if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
184  $this->parser->mHighestExpansionDepth = $expansionDepth;
185  }
186 
187  if ( $root instanceof PPNode_DOM ) {
188  $root = $root->node;
189  }
190  if ( $root instanceof DOMDocument ) {
191  $root = $root->documentElement;
192  }
193 
194  $outStack = [ '', '' ];
195  $iteratorStack = [ false, $root ];
196  $indexStack = [ 0, 0 ];
197 
198  while ( count( $iteratorStack ) > 1 ) {
199  $level = count( $outStack ) - 1;
200  $iteratorNode =& $iteratorStack[$level];
201  $out =& $outStack[$level];
202  $index =& $indexStack[$level];
203 
204  if ( $iteratorNode instanceof PPNode_DOM ) {
205  $iteratorNode = $iteratorNode->node;
206  }
207 
208  if ( is_array( $iteratorNode ) ) {
209  if ( $index >= count( $iteratorNode ) ) {
210  // All done with this iterator
211  $iteratorStack[$level] = false;
212  $contextNode = false;
213  } else {
214  $contextNode = $iteratorNode[$index];
215  $index++;
216  }
217  } elseif ( $iteratorNode instanceof DOMNodeList ) {
218  if ( $index >= $iteratorNode->length ) {
219  // All done with this iterator
220  $iteratorStack[$level] = false;
221  $contextNode = false;
222  } else {
223  $contextNode = $iteratorNode->item( $index );
224  $index++;
225  }
226  } else {
227  // Copy to $contextNode and then delete from iterator stack,
228  // because this is not an iterator but we do have to execute it once
229  $contextNode = $iteratorStack[$level];
230  $iteratorStack[$level] = false;
231  }
232 
233  if ( $contextNode instanceof PPNode_DOM ) {
234  $contextNode = $contextNode->node;
235  }
236 
237  $newIterator = false;
238 
239  if ( $contextNode === false ) {
240  // nothing to do
241  } elseif ( is_string( $contextNode ) ) {
242  $out .= $contextNode;
243  } elseif ( is_array( $contextNode ) || $contextNode instanceof DOMNodeList ) {
244  $newIterator = $contextNode;
245  } elseif ( $contextNode instanceof DOMNode ) {
246  if ( $contextNode->nodeType == XML_TEXT_NODE ) {
247  $out .= $contextNode->nodeValue;
248  } elseif ( $contextNode->nodeName == 'template' ) {
249  # Double-brace expansion
250  $xpath = new DOMXPath( $contextNode->ownerDocument );
251  $titles = $xpath->query( 'title', $contextNode );
252  $title = $titles->item( 0 );
253  $parts = $xpath->query( 'part', $contextNode );
254  if ( $flags & PPFrame::NO_TEMPLATES ) {
255  $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $title, $parts );
256  } else {
257  $lineStart = $contextNode->getAttribute( 'lineStart' );
258  $params = [
259  'title' => new PPNode_DOM( $title ),
260  'parts' => new PPNode_DOM( $parts ),
261  'lineStart' => $lineStart ];
262  $ret = $this->parser->braceSubstitution( $params, $this );
263  if ( isset( $ret['object'] ) ) {
264  $newIterator = $ret['object'];
265  } else {
266  $out .= $ret['text'];
267  }
268  }
269  } elseif ( $contextNode->nodeName == 'tplarg' ) {
270  # Triple-brace expansion
271  $xpath = new DOMXPath( $contextNode->ownerDocument );
272  $titles = $xpath->query( 'title', $contextNode );
273  $title = $titles->item( 0 );
274  $parts = $xpath->query( 'part', $contextNode );
275  if ( $flags & PPFrame::NO_ARGS ) {
276  $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $title, $parts );
277  } else {
278  $params = [
279  'title' => new PPNode_DOM( $title ),
280  'parts' => new PPNode_DOM( $parts ) ];
281  $ret = $this->parser->argSubstitution( $params, $this );
282  if ( isset( $ret['object'] ) ) {
283  $newIterator = $ret['object'];
284  } else {
285  $out .= $ret['text'];
286  }
287  }
288  } elseif ( $contextNode->nodeName == 'comment' ) {
289  # HTML-style comment
290  # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
291  # Not in RECOVER_COMMENTS mode (msgnw) though.
292  if ( ( $this->parser->ot['html']
293  || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
294  || ( $flags & PPFrame::STRIP_COMMENTS )
295  ) && !( $flags & PPFrame::RECOVER_COMMENTS )
296  ) {
297  $out .= '';
298  } elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
299  # Add a strip marker in PST mode so that pstPass2() can
300  # run some old-fashioned regexes on the result.
301  # Not in RECOVER_COMMENTS mode (extractSections) though.
302  $out .= $this->parser->insertStripItem( $contextNode->textContent );
303  } else {
304  # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
305  $out .= $contextNode->textContent;
306  }
307  } elseif ( $contextNode->nodeName == 'ignore' ) {
308  # Output suppression used by <includeonly> etc.
309  # OT_WIKI will only respect <ignore> in substed templates.
310  # The other output types respect it unless NO_IGNORE is set.
311  # extractSections() sets NO_IGNORE and so never respects it.
312  if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] )
313  || ( $flags & PPFrame::NO_IGNORE )
314  ) {
315  $out .= $contextNode->textContent;
316  } else {
317  $out .= '';
318  }
319  } elseif ( $contextNode->nodeName == 'ext' ) {
320  # Extension tag
321  $xpath = new DOMXPath( $contextNode->ownerDocument );
322  $names = $xpath->query( 'name', $contextNode );
323  $attrs = $xpath->query( 'attr', $contextNode );
324  $inners = $xpath->query( 'inner', $contextNode );
325  $closes = $xpath->query( 'close', $contextNode );
326  if ( $flags & PPFrame::NO_TAGS ) {
327  $s = '<' . $this->expand( $names->item( 0 ), $flags );
328  if ( $attrs->length > 0 ) {
329  $s .= $this->expand( $attrs->item( 0 ), $flags );
330  }
331  if ( $inners->length > 0 ) {
332  $s .= '>' . $this->expand( $inners->item( 0 ), $flags );
333  if ( $closes->length > 0 ) {
334  $s .= $this->expand( $closes->item( 0 ), $flags );
335  }
336  } else {
337  $s .= '/>';
338  }
339  $out .= $s;
340  } else {
341  $params = [
342  'name' => new PPNode_DOM( $names->item( 0 ) ),
343  'attr' => $attrs->length > 0 ? new PPNode_DOM( $attrs->item( 0 ) ) : null,
344  'inner' => $inners->length > 0 ? new PPNode_DOM( $inners->item( 0 ) ) : null,
345  'close' => $closes->length > 0 ? new PPNode_DOM( $closes->item( 0 ) ) : null,
346  ];
347  $out .= $this->parser->extensionSubstitution( $params, $this );
348  }
349  } elseif ( $contextNode->nodeName == 'h' ) {
350  # Heading
351  $s = $this->expand( $contextNode->childNodes, $flags );
352 
353  # Insert a heading marker only for <h> children of <root>
354  # This is to stop extractSections from going over multiple tree levels
355  if ( $contextNode->parentNode->nodeName == 'root' && $this->parser->ot['html'] ) {
356  # Insert heading index marker
357  $headingIndex = $contextNode->getAttribute( 'i' );
358  $titleText = $this->title->getPrefixedDBkey();
359  $this->parser->mHeadings[] = [ $titleText, $headingIndex ];
360  $serial = count( $this->parser->mHeadings ) - 1;
361  $marker = Parser::MARKER_PREFIX . "-h-$serial-" . Parser::MARKER_SUFFIX;
362  $count = $contextNode->getAttribute( 'level' );
363  $s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
364  $this->parser->mStripState->addGeneral( $marker, '' );
365  }
366  $out .= $s;
367  } else {
368  # Generic recursive expansion
369  $newIterator = $contextNode->childNodes;
370  }
371  } else {
372  throw new MWException( __METHOD__ . ': Invalid parameter type' );
373  }
374 
375  if ( $newIterator !== false ) {
376  if ( $newIterator instanceof PPNode_DOM ) {
377  $newIterator = $newIterator->node;
378  }
379  $outStack[] = '';
380  $iteratorStack[] = $newIterator;
381  $indexStack[] = 0;
382  } elseif ( $iteratorStack[$level] === false ) {
383  // Return accumulated value to parent
384  // With tail recursion
385  while ( $iteratorStack[$level] === false && $level > 0 ) {
386  $outStack[$level - 1] .= $out;
387  array_pop( $outStack );
388  array_pop( $iteratorStack );
389  array_pop( $indexStack );
390  $level--;
391  }
392  }
393  }
394  --$expansionDepth;
395  return $outStack[0];
396  }
397 
404  public function implodeWithFlags( $sep, $flags, ...$args ) {
405  $first = true;
406  $s = '';
407  foreach ( $args as $root ) {
408  if ( $root instanceof PPNode_DOM ) {
409  $root = $root->node;
410  }
411  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
412  $root = [ $root ];
413  }
414  foreach ( $root as $node ) {
415  if ( $first ) {
416  $first = false;
417  } else {
418  $s .= $sep;
419  }
420  $s .= $this->expand( $node, $flags );
421  }
422  }
423  return $s;
424  }
425 
434  public function implode( $sep, ...$args ) {
435  $first = true;
436  $s = '';
437  foreach ( $args as $root ) {
438  if ( $root instanceof PPNode_DOM ) {
439  $root = $root->node;
440  }
441  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
442  $root = [ $root ];
443  }
444  foreach ( $root as $node ) {
445  if ( $first ) {
446  $first = false;
447  } else {
448  $s .= $sep;
449  }
450  $s .= $this->expand( $node );
451  }
452  }
453  return $s;
454  }
455 
465  public function virtualImplode( $sep, ...$args ) {
466  $out = [];
467  $first = true;
468 
469  foreach ( $args as $root ) {
470  if ( $root instanceof PPNode_DOM ) {
471  $root = $root->node;
472  }
473  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
474  $root = [ $root ];
475  }
476  foreach ( $root as $node ) {
477  if ( $first ) {
478  $first = false;
479  } else {
480  $out[] = $sep;
481  }
482  $out[] = $node;
483  }
484  }
485  return $out;
486  }
487 
497  public function virtualBracketedImplode( $start, $sep, $end, ...$args ) {
498  $out = [ $start ];
499  $first = true;
500 
501  foreach ( $args as $root ) {
502  if ( $root instanceof PPNode_DOM ) {
503  $root = $root->node;
504  }
505  if ( !is_array( $root ) && !( $root instanceof DOMNodeList ) ) {
506  $root = [ $root ];
507  }
508  foreach ( $root as $node ) {
509  if ( $first ) {
510  $first = false;
511  } else {
512  $out[] = $sep;
513  }
514  $out[] = $node;
515  }
516  }
517  $out[] = $end;
518  return $out;
519  }
520 
521  public function __toString() {
522  return 'frame{}';
523  }
524 
525  public function getPDBK( $level = false ) {
526  if ( $level === false ) {
527  return $this->title->getPrefixedDBkey();
528  } else {
529  return $this->titleCache[$level] ?? false;
530  }
531  }
532 
536  public function getArguments() {
537  return [];
538  }
539 
543  public function getNumberedArguments() {
544  return [];
545  }
546 
550  public function getNamedArguments() {
551  return [];
552  }
553 
559  public function isEmpty() {
560  return true;
561  }
562 
567  public function getArgument( $name ) {
568  return false;
569  }
570 
577  public function loopCheck( $title ) {
578  return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
579  }
580 
586  public function isTemplate() {
587  return false;
588  }
589 
595  public function getTitle() {
596  return $this->title;
597  }
598 
604  public function setVolatile( $flag = true ) {
605  $this->volatile = $flag;
606  }
607 
613  public function isVolatile() {
614  return $this->volatile;
615  }
616 
622  public function setTTL( $ttl ) {
623  if ( $ttl !== null && ( $this->ttl === null || $ttl < $this->ttl ) ) {
624  $this->ttl = $ttl;
625  }
626  }
627 
633  public function getTTL() {
634  return $this->ttl;
635  }
636 }
PPFrame_DOM\getPDBK
getPDBK( $level=false)
Definition: PPFrame_DOM.php:525
PPFrame_DOM\$preprocessor
Preprocessor $preprocessor
Definition: PPFrame_DOM.php:34
PPFrame\STRIP_COMMENTS
const STRIP_COMMENTS
Definition: PPFrame.php:31
PPNode_DOM
Definition: PPNode_DOM.php:28
PPFrame_DOM\isVolatile
isVolatile()
Get the volatile flag.
Definition: PPFrame_DOM.php:613
PPFrame\NO_ARGS
const NO_ARGS
Definition: PPFrame.php:29
PPFrame_DOM\$depth
$depth
Recursion depth of this frame, top = 0 Note that this is NOT the same as expansion depth in expand()
Definition: PPFrame_DOM.php:57
Title\getPrefixedDBkey
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1806
PPFrame_DOM\getNamedArguments
getNamedArguments()
Definition: PPFrame_DOM.php:550
PPFrame\NO_IGNORE
const NO_IGNORE
Definition: PPFrame.php:32
PPFrame_DOM\$titleCache
$titleCache
Definition: PPFrame_DOM.php:45
PPFrame_DOM\$volatile
$volatile
Definition: PPFrame_DOM.php:59
PPFrame_DOM\cachedExpand
cachedExpand( $key, $root, $flags=0)
Definition: PPFrame_DOM.php:150
PPFrame_DOM\getArgument
getArgument( $name)
Definition: PPFrame_DOM.php:567
PPFrame_DOM\$ttl
$ttl
Definition: PPFrame_DOM.php:60
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
$s
$s
Definition: mergeMessageFileList.php:185
PPFrame\RECOVER_COMMENTS
const RECOVER_COMMENTS
Definition: PPFrame.php:33
PPFrame\NO_TEMPLATES
const NO_TEMPLATES
Definition: PPFrame.php:30
Preprocessor
Definition: Preprocessor.php:30
PPFrame_DOM\isEmpty
isEmpty()
Returns true if there are no arguments in this frame.
Definition: PPFrame_DOM.php:559
MWException
MediaWiki exception.
Definition: MWException.php:26
PPFrame_DOM\setTTL
setTTL( $ttl)
Set the TTL.
Definition: PPFrame_DOM.php:622
PPFrame_DOM\loopCheck
loopCheck( $title)
Returns true if the infinite loop check is OK, false if a loop is detected.
Definition: PPFrame_DOM.php:577
PPFrame_DOM\getTTL
getTTL()
Get the TTL.
Definition: PPFrame_DOM.php:633
PPFrame_DOM\getArguments
getArguments()
Definition: PPFrame_DOM.php:536
PPFrame_DOM\isTemplate
isTemplate()
Return true if the frame is a template frame.
Definition: PPFrame_DOM.php:586
PPFrame_DOM\$loopCheckHash
$loopCheckHash
Hashtable listing templates which are disallowed for expansion in this frame, having been encountered...
Definition: PPFrame_DOM.php:51
PPFrame_DOM\virtualImplode
virtualImplode( $sep,... $args)
Makes an object that, when expand()ed, will be the same as one obtained with implode()
Definition: PPFrame_DOM.php:465
PPFrame_DOM\expand
expand( $root, $flags=0)
Definition: PPFrame_DOM.php:161
PPFrame_DOM\setVolatile
setVolatile( $flag=true)
Set the volatile flag.
Definition: PPFrame_DOM.php:604
PPFrame_DOM
An expansion frame, used as a context to expand the result of preprocessToObj()
Definition: PPFrame_DOM.php:29
PPFrame_DOM\implodeWithFlags
implodeWithFlags( $sep, $flags,... $args)
Definition: PPFrame_DOM.php:404
PPFrame\NO_TAGS
const NO_TAGS
Definition: PPFrame.php:34
PPFrame
Definition: PPFrame.php:28
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1551
PPFrame_DOM\virtualBracketedImplode
virtualBracketedImplode( $start, $sep, $end,... $args)
Virtual implode with brackets.
Definition: PPFrame_DOM.php:497
$args
if( $line===false) $args
Definition: cdb.php:64
Title
Represents a title within MediaWiki.
Definition: Title.php:42
PPFrame_DOM\$parser
Parser $parser
Definition: PPFrame_DOM.php:39
PPFrame_DOM\__toString
__toString()
Definition: PPFrame_DOM.php:521
PPFrame_DOM\getTitle
getTitle()
Get a title of frame.
Definition: PPFrame_DOM.php:595
PPFrame_DOM\newChild
newChild( $args=false, $title=false, $indexOffset=0)
Create a new child frame $args is optionally a multi-root PPNode or array containing the template arg...
Definition: PPFrame_DOM.php:90
PPFrame_DOM\$title
Title $title
Definition: PPFrame_DOM.php:44
PPFrame_DOM\implode
implode( $sep,... $args)
Implode with no flags specified This previously called implodeWithFlags but has now been inlined to r...
Definition: PPFrame_DOM.php:434
PPFrame_DOM\__construct
__construct( $preprocessor)
Construct a new preprocessor frame.
Definition: PPFrame_DOM.php:71
PPFrame_DOM\getNumberedArguments
getNumberedArguments()
Definition: PPFrame_DOM.php:543
PPFrame_DOM\$childExpansionCache
array $childExpansionCache
Definition: PPFrame_DOM.php:65
PPTemplateFrame_DOM
Expansion frame with template arguments.
Definition: PPTemplateFrame_DOM.php:28