[ 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\Helpers\TemplateParser; 9 10 use DOMDocument; 11 use DOMElement; 12 use DOMNode; 13 use s9e\TextFormatter\Configurator\Helpers\XPathHelper; 14 15 class Normalizer extends IRProcessor 16 { 17 /** 18 * @var Optimizer 19 */ 20 protected $optimizer; 21 22 /** 23 * @var string Regexp that matches the names of all void elements 24 * @link http://www.w3.org/TR/html-markup/syntax.html#void-elements 25 */ 26 public $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di'; 27 28 /** 29 * @param Optimizer $optimizer 30 * @return void 31 */ 32 public function __construct(Optimizer $optimizer) 33 { 34 $this->optimizer = $optimizer; 35 } 36 37 /** 38 * Normalize an IR 39 * 40 * @param DOMDocument $ir 41 * @return void 42 */ 43 public function normalize(DOMDocument $ir) 44 { 45 $this->createXPath($ir); 46 $this->addDefaultCase(); 47 $this->addElementIds(); 48 $this->addCloseTagElements($ir); 49 $this->markVoidElements(); 50 $this->optimizer->optimize($ir); 51 $this->markConditionalCloseTagElements(); 52 $this->setOutputContext(); 53 $this->markBranchTables(); 54 $this->markBooleanAttributes(); 55 } 56 57 /** 58 * Add <closeTag/> elements everywhere an open start tag should be closed 59 * 60 * @param DOMDocument $ir 61 * @return void 62 */ 63 protected function addCloseTagElements(DOMDocument $ir) 64 { 65 $exprs = [ 66 '//applyTemplates[not(ancestor::attribute)]', 67 '//comment', 68 '//element', 69 '//output[not(ancestor::attribute)]' 70 ]; 71 foreach ($this->query(implode('|', $exprs)) as $node) 72 { 73 $parentElementId = $this->getParentElementId($node); 74 if (isset($parentElementId)) 75 { 76 $node->parentNode 77 ->insertBefore($ir->createElement('closeTag'), $node) 78 ->setAttribute('id', $parentElementId); 79 } 80 81 // Append a <closeTag/> to <element/> nodes to ensure that empty elements get closed 82 if ($node->nodeName === 'element') 83 { 84 $id = $node->getAttribute('id'); 85 $this->appendElement($node, 'closeTag')->setAttribute('id', $id); 86 } 87 } 88 } 89 90 /** 91 * Add an empty default <case/> to <switch/> nodes that don't have one 92 * 93 * @return void 94 */ 95 protected function addDefaultCase() 96 { 97 foreach ($this->query('//switch[not(case[not(@test)])]') as $switch) 98 { 99 $this->appendElement($switch, 'case'); 100 } 101 } 102 103 /** 104 * Add an id attribute to <element/> nodes 105 * 106 * @return void 107 */ 108 protected function addElementIds() 109 { 110 $id = 0; 111 foreach ($this->query('//element') as $element) 112 { 113 $element->setAttribute('id', ++$id); 114 } 115 } 116 117 /** 118 * Get the context type for given output element 119 * 120 * @param DOMNode $output 121 * @return string 122 */ 123 protected function getOutputContext(DOMNode $output) 124 { 125 $contexts = [ 126 'boolean(ancestor::attribute)' => 'attribute', 127 '@disable-output-escaping="yes"' => 'raw', 128 'count(ancestor::element[@name="script"])' => 'raw' 129 ]; 130 foreach ($contexts as $expr => $context) 131 { 132 if ($this->evaluate($expr, $output)) 133 { 134 return $context; 135 } 136 } 137 138 return 'text'; 139 } 140 141 /** 142 * Get the ID of the closest "element" ancestor 143 * 144 * @param DOMNode $node Context node 145 * @return string|null 146 */ 147 protected function getParentElementId(DOMNode $node) 148 { 149 $parentNode = $node->parentNode; 150 while (isset($parentNode)) 151 { 152 if ($parentNode->nodeName === 'element') 153 { 154 return $parentNode->getAttribute('id'); 155 } 156 $parentNode = $parentNode->parentNode; 157 } 158 } 159 160 /** 161 * Mark switch elements that are used as branch tables 162 * 163 * If a switch is used for a series of equality tests against the same attribute or variable, the 164 * attribute/variable is stored within the switch as "branch-key" and the values it is compared 165 * against are stored JSON-encoded in the case as "branch-values". It can be used to create 166 * optimized branch tables 167 * 168 * @return void 169 */ 170 protected function markBranchTables() 171 { 172 // Iterate over switch elements that have at least two case children with a test attribute 173 foreach ($this->query('//switch[case[2][@test]]') as $switch) 174 { 175 $this->markSwitchTable($switch); 176 } 177 } 178 179 /** 180 * Mark given switch element if it's used as a branch table 181 * 182 * @param DOMElement $switch 183 * @return void 184 */ 185 protected function markSwitchTable(DOMElement $switch) 186 { 187 $cases = []; 188 $maps = []; 189 foreach ($this->query('./case[@test]', $switch) as $i => $case) 190 { 191 $map = XPathHelper::parseEqualityExpr($case->getAttribute('test')); 192 if ($map === false) 193 { 194 return; 195 } 196 $maps += $map; 197 $cases[$i] = [$case, end($map)]; 198 } 199 if (count($maps) !== 1) 200 { 201 return; 202 } 203 204 $switch->setAttribute('branch-key', key($maps)); 205 foreach ($cases as list($case, $values)) 206 { 207 sort($values); 208 $case->setAttribute('branch-values', serialize($values)); 209 } 210 } 211 212 /** 213 * Mark conditional <closeTag/> nodes 214 * 215 * @return void 216 */ 217 protected function markConditionalCloseTagElements() 218 { 219 foreach ($this->query('//closeTag') as $closeTag) 220 { 221 $id = $closeTag->getAttribute('id'); 222 223 // For each <switch/> ancestor, look for a <closeTag/> and that is either a sibling or 224 // the descendant of a sibling, and that matches the id 225 $query = 'ancestor::switch/' 226 . 'following-sibling::*/' 227 . 'descendant-or-self::closeTag[@id = "' . $id . '"]'; 228 foreach ($this->query($query, $closeTag) as $following) 229 { 230 // Mark following <closeTag/> nodes to indicate that the status of this tag must 231 // be checked before it is closed 232 $following->setAttribute('check', ''); 233 234 // Mark the current <closeTag/> to indicate that it must set a flag to indicate 235 // that its tag has been closed 236 $closeTag->setAttribute('set', ''); 237 } 238 } 239 } 240 241 /** 242 * Mark boolean attributes 243 * 244 * The test is case-sensitive and only covers attribute that are minimized by libxslt 245 * 246 * @return void 247 */ 248 protected function markBooleanAttributes(): void 249 { 250 $attrNames = ['checked', 'compact', 'declare', 'defer', 'disabled', 'ismap', 'multiple', 'nohref', 'noresize', 'noshade', 'nowrap', 'readonly', 'selected']; 251 foreach ($this->query('//attribute') as $attribute) 252 { 253 if (in_array($attribute->getAttribute('name'), $attrNames, true)) 254 { 255 $attribute->setAttribute('boolean', 'yes'); 256 } 257 } 258 } 259 260 /** 261 * Mark void elements 262 * 263 * @return void 264 */ 265 protected function markVoidElements() 266 { 267 foreach ($this->query('//element') as $element) 268 { 269 // Test whether this element is (maybe) void 270 $elName = $element->getAttribute('name'); 271 if (strpos($elName, '{') !== false) 272 { 273 // Dynamic element names must be checked at runtime 274 $element->setAttribute('void', 'maybe'); 275 } 276 elseif (preg_match($this->voidRegexp, $elName)) 277 { 278 // Static element names can be checked right now 279 $element->setAttribute('void', 'yes'); 280 } 281 } 282 } 283 284 /** 285 * Fill in output context 286 * 287 * @return void 288 */ 289 protected function setOutputContext() 290 { 291 foreach ($this->query('//output') as $output) 292 { 293 $output->setAttribute('escape', $this->getOutputContext($output)); 294 } 295 } 296 }
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 |