[ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2022 The s9e authors 6 * @license http://www.opensource.org/licenses/mit-license.php The MIT License 7 */ 8 namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP; 9 10 use Closure; 11 use RuntimeException; 12 use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder; 13 14 class Quick 15 { 16 /** 17 * Generate the Quick renderer's source 18 * 19 * @param array $compiledTemplates Array of tagName => compiled template 20 * @return string 21 */ 22 public static function getSource(array $compiledTemplates) 23 { 24 $map = ['dynamic' => [], 'php' => [], 'static' => []]; 25 $tagNames = []; 26 $unsupported = []; 27 28 // Ignore system tags 29 unset($compiledTemplates['br']); 30 unset($compiledTemplates['e']); 31 unset($compiledTemplates['i']); 32 unset($compiledTemplates['p']); 33 unset($compiledTemplates['s']); 34 35 foreach ($compiledTemplates as $tagName => $php) 36 { 37 $renderings = self::getRenderingStrategy($php); 38 if (empty($renderings)) 39 { 40 $unsupported[] = $tagName; 41 continue; 42 } 43 44 foreach ($renderings as $i => list($strategy, $replacement)) 45 { 46 $match = (($i) ? '/' : '') . $tagName; 47 $map[$strategy][$match] = $replacement; 48 } 49 50 // Record the names of tags whose template does not contain a passthrough 51 if (!isset($renderings[1])) 52 { 53 $tagNames[] = $tagName; 54 } 55 } 56 57 $php = []; 58 $php[] = ' /** {@inheritdoc} */'; 59 $php[] = ' public $enableQuickRenderer=true;'; 60 $php[] = ' /** {@inheritdoc} */'; 61 $php[] = ' protected $static=' . self::export($map['static']) . ';'; 62 $php[] = ' /** {@inheritdoc} */'; 63 $php[] = ' protected $dynamic=' . self::export($map['dynamic']) . ';'; 64 65 $quickSource = ''; 66 if (!empty($map['php'])) 67 { 68 $quickSource = SwitchStatement::generate('$id', $map['php']); 69 } 70 71 // Build a regexp that matches all the tags 72 $regexp = '(<(?:(?!/)('; 73 $regexp .= ($tagNames) ? RegexpBuilder::fromList($tagNames) : '(?!)'; 74 $regexp .= ')(?: [^>]*)?>.*?</\\1|(/?(?!br/|p>)[^ />]+)[^>]*?(/)?)>)s'; 75 $php[] = ' /** {@inheritdoc} */'; 76 $php[] = ' protected $quickRegexp=' . var_export($regexp, true) . ';'; 77 78 // Build a regexp that matches tags that cannot be rendered with the Quick renderer 79 if (!empty($unsupported)) 80 { 81 $regexp = '((?<=<)(?:[!?]|' . RegexpBuilder::fromList($unsupported) . '[ />]))'; 82 $php[] = ' /** {@inheritdoc} */'; 83 $php[] = ' protected $quickRenderingTest=' . var_export($regexp, true) . ';'; 84 } 85 86 $php[] = ' /** {@inheritdoc} */'; 87 $php[] = ' protected function renderQuickTemplate($id, $xml)'; 88 $php[] = ' {'; 89 $php[] = ' $attributes=$this->matchAttributes($xml);'; 90 $php[] = " \$html='';" . $quickSource; 91 $php[] = ''; 92 $php[] = ' return $html;'; 93 $php[] = ' }'; 94 95 return implode("\n", $php); 96 } 97 98 /** 99 * Export an array as PHP 100 * 101 * @param array $arr 102 * @return string 103 */ 104 protected static function export(array $arr) 105 { 106 $exportKeys = (array_keys($arr) !== range(0, count($arr) - 1)); 107 ksort($arr); 108 109 $entries = []; 110 foreach ($arr as $k => $v) 111 { 112 $entries[] = (($exportKeys) ? var_export($k, true) . '=>' : '') 113 . ((is_array($v)) ? self::export($v) : var_export($v, true)); 114 } 115 116 return '[' . implode(',', $entries) . ']'; 117 } 118 119 /** 120 * Compute the rendering strategy for a compiled template 121 * 122 * @param string $php Template compiled for the PHP renderer 123 * @return array[] An array containing 0 to 2 pairs of [<rendering type>, <replacement>] 124 */ 125 public static function getRenderingStrategy($php) 126 { 127 $phpRenderings = self::getQuickRendering($php); 128 if (empty($phpRenderings)) 129 { 130 return []; 131 } 132 $renderings = self::getStringRenderings($php); 133 134 // Keep string rendering where possible, use PHP rendering wherever else 135 foreach ($phpRenderings as $i => $phpRendering) 136 { 137 if (!isset($renderings[$i]) || strpos($phpRendering, '$this->attributes[]') !== false) 138 { 139 $renderings[$i] = ['php', $phpRendering]; 140 } 141 } 142 143 return $renderings; 144 } 145 146 /** 147 * Generate the code for rendering a compiled template with the Quick renderer 148 * 149 * Parse and record every code path that contains a passthrough. Parse every if-else structure. 150 * When the whole structure is parsed, there are 2 possible situations: 151 * - no code path contains a passthrough, in which case we discard the data 152 * - all the code paths including the mandatory "else" branch contain a passthrough, in which 153 * case we keep the data 154 * 155 * @param string $php Template compiled for the PHP renderer 156 * @return string[] An array containing one or two strings of PHP, or an empty array 157 * if the PHP cannot be converted 158 */ 159 protected static function getQuickRendering($php) 160 { 161 // xsl:apply-templates elements with a select expression and switch statements are not supported 162 if (preg_match('(\\$this->at\\((?!\\$node\\);)|switch\()', $php)) 163 { 164 return []; 165 } 166 167 // Tokenize the PHP and add an empty token as terminator 168 $tokens = token_get_all('<?php ' . $php); 169 $tokens[] = [0, '']; 170 171 // Remove the first token, which is a T_OPEN_TAG 172 array_shift($tokens); 173 $cnt = count($tokens); 174 175 // Prepare the main branch 176 $branch = [ 177 // We purposefully use a value that can never match 178 'braces' => -1, 179 'branches' => [], 180 'head' => '', 181 'passthrough' => 0, 182 'statement' => '', 183 'tail' => '' 184 ]; 185 186 $braces = 0; 187 $i = 0; 188 do 189 { 190 // Test whether we've reached a passthrough 191 if ($tokens[$i ][0] === T_VARIABLE 192 && $tokens[$i ][1] === '$this' 193 && $tokens[$i + 1][0] === T_OBJECT_OPERATOR 194 && $tokens[$i + 2][0] === T_STRING 195 && $tokens[$i + 2][1] === 'at' 196 && $tokens[$i + 3] === '(' 197 && $tokens[$i + 4][0] === T_VARIABLE 198 && $tokens[$i + 4][1] === '$node' 199 && $tokens[$i + 5] === ')' 200 && $tokens[$i + 6] === ';') 201 { 202 if (++$branch['passthrough'] > 1) 203 { 204 // Multiple passthroughs are not supported 205 return []; 206 } 207 208 // Skip to the semi-colon 209 $i += 6; 210 211 continue; 212 } 213 214 $key = ($branch['passthrough']) ? 'tail' : 'head'; 215 $branch[$key] .= (is_array($tokens[$i])) ? $tokens[$i][1] : $tokens[$i]; 216 217 if ($tokens[$i] === '{') 218 { 219 ++$braces; 220 continue; 221 } 222 223 if ($tokens[$i] === '}') 224 { 225 --$braces; 226 227 if ($branch['braces'] === $braces) 228 { 229 // Remove the last brace from the branch's content 230 $branch[$key] = substr($branch[$key], 0, -1); 231 232 // Jump back to the parent branch 233 $branch =& $branch['parent']; 234 235 // Copy the current index to look ahead 236 $j = $i; 237 238 // Skip whitespace 239 while ($tokens[++$j][0] === T_WHITESPACE); 240 241 // Test whether this is the last brace of an if-else structure by looking for 242 // an additional elseif/else case 243 if ($tokens[$j][0] !== T_ELSEIF && $tokens[$j][0] !== T_ELSE) 244 { 245 $passthroughs = self::getBranchesPassthrough($branch['branches']); 246 if ($passthroughs === [0]) 247 { 248 // No branch was passthrough, move their PHP source back to this branch 249 // then discard the data 250 foreach ($branch['branches'] as $child) 251 { 252 $branch['head'] .= $child['statement'] . '{' . $child['head'] . '}'; 253 } 254 255 $branch['branches'] = []; 256 continue; 257 } 258 259 if ($passthroughs === [1]) 260 { 261 // All branches were passthrough, so their parent is passthrough 262 ++$branch['passthrough']; 263 264 continue; 265 } 266 267 // Mixed branches (with/out passthrough) are not supported 268 return []; 269 } 270 } 271 272 continue; 273 } 274 275 // We don't have to record child branches if we know that current branch is passthrough. 276 // If a child branch contains a passthrough, it will be treated as a multiple 277 // passthrough and we will abort 278 if ($branch['passthrough']) 279 { 280 continue; 281 } 282 283 if ($tokens[$i][0] === T_IF 284 || $tokens[$i][0] === T_ELSEIF 285 || $tokens[$i][0] === T_ELSE) 286 { 287 // Remove the statement from the branch's content 288 $branch[$key] = substr($branch[$key], 0, -strlen($tokens[$i][1])); 289 290 // Create a new branch 291 $branch['branches'][] = [ 292 'braces' => $braces, 293 'branches' => [], 294 'head' => '', 295 'parent' => &$branch, 296 'passthrough' => 0, 297 'statement' => '', 298 'tail' => '' 299 ]; 300 301 // Jump to the new branch 302 $branch =& $branch['branches'][count($branch['branches']) - 1]; 303 304 // Record the PHP statement 305 do 306 { 307 $branch['statement'] .= (is_array($tokens[$i])) ? $tokens[$i][1] : $tokens[$i]; 308 } 309 while ($tokens[++$i] !== '{'); 310 311 // Account for the brace in the statement 312 ++$braces; 313 } 314 } 315 while (++$i < $cnt); 316 317 list($head, $tail) = self::buildPHP($branch['branches']); 318 $head = $branch['head'] . $head; 319 $tail .= $branch['tail']; 320 321 // Convert the PHP renderer source to the format used in the Quick renderer 322 self::convertPHP($head, $tail, (bool) $branch['passthrough']); 323 324 // Test whether any method call was left unconverted. If so, we cannot render this template 325 if (preg_match('((?<!-|\\$this)->)', $head . $tail)) 326 { 327 return []; 328 } 329 330 return ($branch['passthrough']) ? [$head, $tail] : [$head]; 331 } 332 333 /** 334 * Convert the two sides of a compiled template to quick rendering 335 * 336 * @param string &$head 337 * @param string &$tail 338 * @param bool $passthrough 339 * @return void 340 */ 341 protected static function convertPHP(&$head, &$tail, $passthrough) 342 { 343 // Test whether the attributes must be saved when rendering the head because they're needed 344 // when rendering the tail 345 $saveAttributes = (bool) preg_match('(\\$node->(?:get|has)Attribute)', $tail); 346 347 // Collect the names of all the attributes so that we can initialize them with a null value 348 // to avoid undefined variable notices. We exclude attributes that seem to be in an if block 349 // that tests its existence beforehand. This last part is not an accurate process as it 350 // would be much more expensive to do it accurately but where it fails the only consequence 351 // is we needlessly add the attribute to the list. There is no difference in functionality 352 preg_match_all( 353 "(\\\$node->getAttribute\\('([^']+)'\\))", 354 preg_replace_callback( 355 '(if\\(\\$node->hasAttribute\\(([^\\)]+)[^}]+)', 356 function ($m) 357 { 358 return str_replace('$node->getAttribute(' . $m[1] . ')', '', $m[0]); 359 }, 360 $head . $tail 361 ), 362 $matches 363 ); 364 $attrNames = array_unique($matches[1]); 365 366 // Replace the source in $head and $tail 367 self::replacePHP($head); 368 self::replacePHP($tail); 369 370 if (!$passthrough && strpos($head, '$node->textContent') !== false) 371 { 372 $head = '$textContent=$this->getQuickTextContent($xml);' . str_replace('$node->textContent', '$textContent', $head); 373 } 374 375 if (!empty($attrNames)) 376 { 377 ksort($attrNames); 378 $head = "\$attributes+=['" . implode("'=>null,'", $attrNames) . "'=>null];" . $head; 379 } 380 381 if ($saveAttributes) 382 { 383 $head .= '$this->attributes[]=$attributes;'; 384 $tail = '$attributes=array_pop($this->attributes);' . $tail; 385 } 386 } 387 388 /** 389 * Replace the PHP code used in a compiled template to be used by the Quick renderer 390 * 391 * @param string &$php 392 * @return void 393 */ 394 protected static function replacePHP(&$php) 395 { 396 // Expression that matches a $node->getAttribute() call and captures its string argument 397 $getAttribute = "\\\$node->getAttribute\\(('[^']+')\\)"; 398 399 // Expression that matches a single-quoted string literal 400 $string = "'(?:[^\\\\']|\\\\.)*+'"; 401 402 $replacements = [ 403 '$this->out' => '$html', 404 405 // An attribute value escaped as ENT_NOQUOTES. We only need to unescape quotes 406 '(htmlspecialchars\\(' . $getAttribute . ',' . ENT_NOQUOTES . '\\))' 407 => "str_replace('"','\"',\$attributes[\$1]??'')", 408 409 // One or several attribute values escaped as ENT_COMPAT can be used as-is 410 '((\\.?)htmlspecialchars\\((' . $getAttribute . '(?:\\.' . $getAttribute . ')*),' . ENT_COMPAT . '\\)(\\.?))' 411 => function ($m) use ($getAttribute) 412 { 413 $replacement = (strpos($m[0], '.') === false) ? '($attributes[$1]??\'\')' : '$attributes[$1]'; 414 415 return $m[1] . preg_replace('(' . $getAttribute . ')', $replacement, $m[2]) . $m[5]; 416 }, 417 418 // Character replacement can be performed directly on the escaped value provided that it 419 // is then escaped as ENT_COMPAT and that replacements do not interfere with the escaping 420 // of the characters &<>" or their representation &<>" 421 '(htmlspecialchars\\(strtr\\(' . $getAttribute . ",('[^\"&\\\\';<>aglmopqtu]+'),('[^\"&\\\\'<>]+')\\)," . ENT_COMPAT . '\\))' 422 => 'strtr($attributes[$1]??\'\',$2,$3)', 423 424 // A comparison between two attributes. No need to unescape 425 '(' . $getAttribute . '(!?=+)' . $getAttribute . ')' 426 => '$attributes[$1]$2$attributes[$3]', 427 428 // A comparison between an attribute and a literal string. Rather than unescape the 429 // attribute value, we escape the literal. This applies to comparisons using XPath's 430 // contains() as well (translated to PHP's strpos()) 431 '(' . $getAttribute . '===(' . $string . '))s' 432 => function ($m) 433 { 434 return '$attributes[' . $m[1] . ']===' . htmlspecialchars($m[2], ENT_COMPAT); 435 }, 436 437 '((' . $string . ')===' . $getAttribute . ')s' 438 => function ($m) 439 { 440 return htmlspecialchars($m[1], ENT_COMPAT) . '===$attributes[' . $m[2] . ']'; 441 }, 442 443 '(strpos\\(' . $getAttribute . ',(' . $string . ')\\)([!=]==(?:0|false)))s' 444 => function ($m) 445 { 446 return 'strpos($attributes[' . $m[1] . "]??''," . htmlspecialchars($m[2], ENT_COMPAT) . ')' . $m[3]; 447 }, 448 449 '(strpos\\((' . $string . '),' . $getAttribute . '\\)([!=]==(?:0|false)))s' 450 => function ($m) 451 { 452 return 'strpos(' . htmlspecialchars($m[1], ENT_COMPAT) . ',$attributes[' . $m[2] . "]??'')" . $m[3]; 453 }, 454 455 '(str_(contains|(?:end|start)s_with)\\(' . $getAttribute . ',(' . $string . ')\\))s' 456 => function ($m) 457 { 458 return 'str_' . $m[1] . '($attributes[' . $m[2] . "]??''," . htmlspecialchars($m[3], ENT_COMPAT) . ')'; 459 }, 460 461 '(str_(contains|(?:end|start)s_with)\\((' . $string . '),' . $getAttribute . '\\))s' 462 => function ($m) 463 { 464 return 'str_' . $m[1] . '(' . htmlspecialchars($m[2], ENT_COMPAT) . ',$attributes[' . $m[3] . "]??'')"; 465 }, 466 467 // An attribute value used in an arithmetic comparison or operation does not need to be 468 // unescaped. The same applies to empty(), isset() and conditionals 469 '(' . $getAttribute . '(?=(?:==|[-+*])\\d+))' => '$attributes[$1]', 470 '(\\b(\\d+(?:==|[-+*]))' . $getAttribute . ')' => '$1$attributes[$2]', 471 '(empty\\(' . $getAttribute . '\\))' => 'empty($attributes[$1])', 472 "(\\\$node->hasAttribute\\(('[^']+')\\))" => 'isset($attributes[$1])', 473 'if($node->attributes->length)' => 'if($this->hasNonNullValues($attributes))', 474 475 // In all other situations, unescape the attribute value before use 476 '(' . $getAttribute . ')' => 'htmlspecialchars_decode($attributes[$1]??\'\')' 477 ]; 478 479 foreach ($replacements as $match => $replace) 480 { 481 if ($replace instanceof Closure) 482 { 483 $php = preg_replace_callback($match, $replace, $php); 484 } 485 elseif ($match[0] === '(') 486 { 487 $php = preg_replace($match, $replace, $php); 488 } 489 else 490 { 491 $php = str_replace($match, $replace, $php); 492 } 493 } 494 } 495 496 /** 497 * Build the source for the two sides of a templates based on the structure extracted from its 498 * original source 499 * 500 * @param array $branches 501 * @return string[] 502 */ 503 protected static function buildPHP(array $branches) 504 { 505 $return = ['', '']; 506 foreach ($branches as $branch) 507 { 508 $return[0] .= $branch['statement'] . '{' . $branch['head']; 509 $return[1] .= $branch['statement'] . '{'; 510 511 if ($branch['branches']) 512 { 513 list($head, $tail) = self::buildPHP($branch['branches']); 514 515 $return[0] .= $head; 516 $return[1] .= $tail; 517 } 518 519 $return[0] .= '}'; 520 $return[1] .= $branch['tail'] . '}'; 521 } 522 523 return $return; 524 } 525 526 /** 527 * Get the unique values for the "passthrough" key of given branches 528 * 529 * @param array $branches 530 * @return integer[] 531 */ 532 protected static function getBranchesPassthrough(array $branches) 533 { 534 $values = []; 535 foreach ($branches as $branch) 536 { 537 $values[] = $branch['passthrough']; 538 } 539 540 // If the last branch isn't an "else", we act as if there was an additional branch with no 541 // passthrough 542 if ($branch['statement'] !== 'else') 543 { 544 $values[] = 0; 545 } 546 547 return array_unique($values); 548 } 549 550 /** 551 * Get a string suitable as a preg_replace() replacement for given PHP code 552 * 553 * @param string $php Original code 554 * @return array|bool Array of [regexp, replacement] if possible, or FALSE otherwise 555 */ 556 protected static function getDynamicRendering($php) 557 { 558 $rendering = ''; 559 560 $literal = "(?<literal>'((?>[^'\\\\]+|\\\\['\\\\])*)')"; 561 $attribute = "(?<attribute>htmlspecialchars\\(\\\$node->getAttribute\\('([^']+)'\\),2\\))"; 562 $value = "(?<value>$literal|$attribute)"; 563 $output = "(?<output>\\\$this->out\\.=$value(?:\\.(?&value))*;)"; 564 565 $copyOfAttribute = "(?<copyOfAttribute>if\\(\\\$node->hasAttribute\\('([^']+)'\\)\\)\\{\\\$this->out\\.=' \\g-1=\"'\\.htmlspecialchars\\(\\\$node->getAttribute\\('\\g-1'\\),2\\)\\.'\"';\\})"; 566 567 $regexp = '(^(' . $output . '|' . $copyOfAttribute . ')*$)'; 568 if (!preg_match($regexp, $php, $m)) 569 { 570 return false; 571 } 572 573 // Attributes that are copied in the replacement 574 $copiedAttributes = []; 575 576 // Attributes whose value is used in the replacement 577 $usedAttributes = []; 578 579 $regexp = '(' . $output . '|' . $copyOfAttribute . ')A'; 580 $offset = 0; 581 while (preg_match($regexp, $php, $m, 0, $offset)) 582 { 583 // Test whether it's normal output or a copy of attribute 584 if ($m['output']) 585 { 586 // 12 === strlen('$this->out.=') 587 $offset += 12; 588 589 while (preg_match('(' . $value . ')A', $php, $m, 0, $offset)) 590 { 591 // Test whether it's a literal or an attribute value 592 if ($m['literal']) 593 { 594 // Unescape the literal 595 $str = stripslashes(substr($m[0], 1, -1)); 596 597 // Escape special characters 598 $rendering .= preg_replace('([\\\\$](?=\\d))', '\\\\$0', $str); 599 } 600 else 601 { 602 $attrName = end($m); 603 604 // Generate a unique ID for this attribute name, we'll use it as a 605 // placeholder until we have the full list of captures and we can replace it 606 // with the capture number 607 if (!isset($usedAttributes[$attrName])) 608 { 609 $usedAttributes[$attrName] = uniqid($attrName, true); 610 } 611 612 $rendering .= $usedAttributes[$attrName]; 613 } 614 615 // Skip the match plus the next . or ; 616 $offset += 1 + strlen($m[0]); 617 } 618 } 619 else 620 { 621 $attrName = end($m); 622 623 if (!isset($copiedAttributes[$attrName])) 624 { 625 $copiedAttributes[$attrName] = uniqid($attrName, true); 626 } 627 628 $rendering .= $copiedAttributes[$attrName]; 629 $offset += strlen($m[0]); 630 } 631 } 632 633 // Gather the names of the attributes used in the replacement either by copy or by value 634 $attrNames = array_keys($copiedAttributes + $usedAttributes); 635 636 // Sort them alphabetically 637 sort($attrNames); 638 639 // Keep a copy of the attribute names to be used in the fillter subpattern 640 $remainingAttributes = array_combine($attrNames, $attrNames); 641 642 // Prepare the final regexp 643 $regexp = '(^[^ ]+'; 644 $index = 0; 645 foreach ($attrNames as $attrName) 646 { 647 // Add a subpattern that matches (and skips) any attribute definition that is not one of 648 // the remaining attributes we're trying to match 649 $regexp .= '(?> (?!' . RegexpBuilder::fromList($remainingAttributes) . '=)[^=]+="[^"]*")*'; 650 unset($remainingAttributes[$attrName]); 651 652 $regexp .= '('; 653 654 if (isset($copiedAttributes[$attrName])) 655 { 656 self::replacePlaceholder($rendering, $copiedAttributes[$attrName], ++$index); 657 } 658 else 659 { 660 $regexp .= '?>'; 661 } 662 663 $regexp .= ' ' . $attrName . '="'; 664 665 if (isset($usedAttributes[$attrName])) 666 { 667 $regexp .= '('; 668 669 self::replacePlaceholder($rendering, $usedAttributes[$attrName], ++$index); 670 } 671 672 $regexp .= '[^"]*'; 673 674 if (isset($usedAttributes[$attrName])) 675 { 676 $regexp .= ')'; 677 } 678 679 $regexp .= '")?'; 680 } 681 682 $regexp .= '.*)s'; 683 684 return [$regexp, $rendering]; 685 } 686 687 /** 688 * Get a string suitable as a str_replace() replacement for given PHP code 689 * 690 * @param string $php Original code 691 * @return bool|string Static replacement if possible, or FALSE otherwise 692 */ 693 protected static function getStaticRendering($php) 694 { 695 if ($php === '') 696 { 697 return ''; 698 } 699 700 $regexp = "(^\\\$this->out\.='((?>[^'\\\\]|\\\\['\\\\])*+)';\$)"; 701 if (preg_match($regexp, $php, $m)) 702 { 703 return stripslashes($m[1]); 704 } 705 706 return false; 707 } 708 709 /** 710 * Get string rendering strategies for given chunks 711 * 712 * @param string $php 713 * @return array 714 */ 715 protected static function getStringRenderings($php) 716 { 717 $chunks = explode('$this->at($node);', $php); 718 if (count($chunks) > 2) 719 { 720 // Can't use string replacements if there are more than one xsl:apply-templates 721 return []; 722 } 723 724 $renderings = []; 725 foreach ($chunks as $k => $chunk) 726 { 727 // Try a static replacement first 728 $rendering = self::getStaticRendering($chunk); 729 if ($rendering !== false) 730 { 731 $renderings[$k] = ['static', $rendering]; 732 } 733 elseif ($k === 0) 734 { 735 // If this is the first chunk, we can try a dynamic replacement. This wouldn't work 736 // for the second chunk because we wouldn't have access to the attribute values 737 $rendering = self::getDynamicRendering($chunk); 738 if ($rendering !== false) 739 { 740 $renderings[$k] = ['dynamic', $rendering]; 741 } 742 } 743 } 744 745 return $renderings; 746 } 747 748 /** 749 * Replace all instances of a uniqid with a PCRE replacement in a string 750 * 751 * @param string &$str PCRE replacement 752 * @param string $uniqid Unique ID 753 * @param integer $index Capture index 754 * @return void 755 */ 756 protected static function replacePlaceholder(&$str, $uniqid, $index) 757 { 758 $str = preg_replace_callback( 759 '(' . preg_quote($uniqid) . '(.))', 760 function ($m) use ($index) 761 { 762 // Replace with $1 where unambiguous and ${1} otherwise 763 if (is_numeric($m[1])) 764 { 765 return '${' . $index . '}' . $m[1]; 766 } 767 else 768 { 769 return '$' . $index . $m[1]; 770 } 771 }, 772 $str 773 ); 774 } 775 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |