Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Normalizer.php
001 <?php
002
003 /**
004 * @package s9e\TextFormatter
005 * @copyright Copyright (c) 2010-2022 The s9e authors
006 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
007 */
008 namespace s9e\TextFormatter\Configurator\Helpers\TemplateParser;
009
010 use DOMDocument;
011 use DOMElement;
012 use DOMNode;
013 use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
014
015 class Normalizer extends IRProcessor
016 {
017 /**
018 * @var Optimizer
019 */
020 protected $optimizer;
021
022 /**
023 * @var string Regexp that matches the names of all void elements
024 * @link http://www.w3.org/TR/html-markup/syntax.html#void-elements
025 */
026 public $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di';
027
028 /**
029 * @param Optimizer $optimizer
030 * @return void
031 */
032 public function __construct(Optimizer $optimizer)
033 {
034 $this->optimizer = $optimizer;
035 }
036
037 /**
038 * Normalize an IR
039 *
040 * @param DOMDocument $ir
041 * @return void
042 */
043 public function normalize(DOMDocument $ir)
044 {
045 $this->createXPath($ir);
046 $this->addDefaultCase();
047 $this->addElementIds();
048 $this->addCloseTagElements($ir);
049 $this->markVoidElements();
050 $this->optimizer->optimize($ir);
051 $this->markConditionalCloseTagElements();
052 $this->setOutputContext();
053 $this->markBranchTables();
054 $this->markBooleanAttributes();
055 }
056
057 /**
058 * Add <closeTag/> elements everywhere an open start tag should be closed
059 *
060 * @param DOMDocument $ir
061 * @return void
062 */
063 protected function addCloseTagElements(DOMDocument $ir)
064 {
065 $exprs = [
066 '//applyTemplates[not(ancestor::attribute)]',
067 '//comment',
068 '//element',
069 '//output[not(ancestor::attribute)]'
070 ];
071 foreach ($this->query(implode('|', $exprs)) as $node)
072 {
073 $parentElementId = $this->getParentElementId($node);
074 if (isset($parentElementId))
075 {
076 $node->parentNode
077 ->insertBefore($ir->createElement('closeTag'), $node)
078 ->setAttribute('id', $parentElementId);
079 }
080
081 // Append a <closeTag/> to <element/> nodes to ensure that empty elements get closed
082 if ($node->nodeName === 'element')
083 {
084 $id = $node->getAttribute('id');
085 $this->appendElement($node, 'closeTag')->setAttribute('id', $id);
086 }
087 }
088 }
089
090 /**
091 * Add an empty default <case/> to <switch/> nodes that don't have one
092 *
093 * @return void
094 */
095 protected function addDefaultCase()
096 {
097 foreach ($this->query('//switch[not(case[not(@test)])]') as $switch)
098 {
099 $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 }