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 |
Parser.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 DOMXPath;
013 use RuntimeException;
014 use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
015 use s9e\TextFormatter\Configurator\Helpers\TemplateLoader;
016
017 class Parser extends IRProcessor
018 {
019 /**
020 * @var Normalizer
021 */
022 protected $normalizer;
023
024 /**
025 * @param Normalizer $normalizer
026 * @return void
027 */
028 public function __construct(Normalizer $normalizer)
029 {
030 $this->normalizer = $normalizer;
031 }
032
033 /**
034 * Parse a template into an internal representation
035 *
036 * @param string $template Source template
037 * @return DOMDocument Internal representation
038 */
039 public function parse($template)
040 {
041 $dom = TemplateLoader::load($template);
042
043 $ir = new DOMDocument;
044 $ir->loadXML('<template/>');
045
046 $this->createXPath($dom);
047 $this->parseChildren($ir->documentElement, $dom->documentElement);
048 $this->normalizer->normalize($ir);
049
050 return $ir;
051 }
052
053 /**
054 * Append <output/> elements corresponding to given AVT
055 *
056 * @param DOMElement $parentNode Parent node
057 * @param string $avt Attribute value template
058 * @return void
059 */
060 protected function appendAVT(DOMElement $parentNode, $avt)
061 {
062 foreach (AVTHelper::parse($avt) as $token)
063 {
064 if ($token[0] === 'expression')
065 {
066 $this->appendXPathOutput($parentNode, $token[1]);
067 }
068 else
069 {
070 $this->appendLiteralOutput($parentNode, $token[1]);
071 }
072 }
073 }
074
075 /**
076 * Append an <output/> element with literal content to given node
077 *
078 * @param DOMElement $parentNode Parent node
079 * @param string $content Content to output
080 * @return void
081 */
082 protected function appendLiteralOutput(DOMElement $parentNode, $content)
083 {
084 if ($content === '')
085 {
086 return;
087 }
088
089 $this->appendElement($parentNode, 'output', htmlspecialchars($content, ENT_COMPAT))
090 ->setAttribute('type', 'literal');
091 }
092
093 /**
094 * Append the structure for a <xsl:copy-of/> element to given node
095 *
096 * @param DOMElement $parentNode Parent node
097 * @param string $expr Select expression, which is should only contain attributes
098 * @return void
099 */
100 protected function appendConditionalAttributes(DOMElement $parentNode, $expr)
101 {
102 preg_match_all('(@([-\\w]+))', $expr, $matches);
103 foreach ($matches[1] as $attrName)
104 {
105 // Create a switch element in the IR
106 $switch = $this->appendElement($parentNode, 'switch');
107 $case = $this->appendElement($switch, 'case');
108 $case->setAttribute('test', '@' . $attrName);
109
110 // Append an attribute element
111 $attribute = $this->appendElement($case, 'attribute');
112 $attribute->setAttribute('name', $attrName);
113
114 // Set the attribute's content, which is simply the copied attribute's value
115 $this->appendXPathOutput($attribute, '@' . $attrName);
116 }
117 }
118
119 /**
120 * Append an <output/> element for given XPath expression to given node
121 *
122 * @param DOMElement $parentNode Parent node
123 * @param string $expr XPath expression
124 * @return void
125 */
126 protected function appendXPathOutput(DOMElement $parentNode, $expr)
127 {
128 $this->appendElement($parentNode, 'output', htmlspecialchars(trim($expr), ENT_COMPAT))
129 ->setAttribute('type', 'xpath');
130 }
131
132 /**
133 * Parse all the children of a given element
134 *
135 * @param DOMElement $ir Node in the internal representation that represents the parent node
136 * @param DOMElement $parent Parent node
137 * @return void
138 */
139 protected function parseChildren(DOMElement $ir, DOMElement $parent)
140 {
141 foreach ($parent->childNodes as $child)
142 {
143 switch ($child->nodeType)
144 {
145 case XML_COMMENT_NODE:
146 // Do nothing
147 break;
148
149 case XML_TEXT_NODE:
150 if (trim($child->textContent) !== '')
151 {
152 $this->appendLiteralOutput($ir, $child->textContent);
153 }
154 break;
155
156 case XML_ELEMENT_NODE:
157 $this->parseNode($ir, $child);
158 break;
159
160 default:
161 throw new RuntimeException("Cannot parse node '" . $child->nodeName . "''");
162 }
163 }
164 }
165
166 /**
167 * Parse a given node into the internal representation
168 *
169 * @param DOMElement $ir Node in the internal representation that represents the node's parent
170 * @param DOMElement $node Node to parse
171 * @return void
172 */
173 protected function parseNode(DOMElement $ir, DOMElement $node)
174 {
175 // XSL elements are parsed by the corresponding parseXsl* method
176 if ($node->namespaceURI === self::XMLNS_XSL)
177 {
178 $methodName = 'parseXsl' . str_replace(' ', '', ucwords(str_replace('-', ' ', $node->localName)));
179 if (!method_exists($this, $methodName))
180 {
181 throw new RuntimeException("Element '" . $node->nodeName . "' is not supported");
182 }
183
184 return $this->$methodName($ir, $node);
185 }
186
187 // Create an <element/> with a name attribute equal to given node's name
188 $element = $this->appendElement($ir, 'element');
189 $element->setAttribute('name', $node->nodeName);
190
191 // Append an <attribute/> element for each namespace declaration
192 $xpath = new DOMXPath($node->ownerDocument);
193 foreach ($xpath->query('namespace::*', $node) as $ns)
194 {
195 if ($node->hasAttribute($ns->nodeName))
196 {
197 $irAttribute = $this->appendElement($element, 'attribute');
198 $irAttribute->setAttribute('name', $ns->nodeName);
199 $this->appendLiteralOutput($irAttribute, $ns->nodeValue);
200 }
201 }
202
203 // Append an <attribute/> element for each of this node's attribute
204 foreach ($node->attributes as $attribute)
205 {
206 $irAttribute = $this->appendElement($element, 'attribute');
207 $irAttribute->setAttribute('name', $attribute->nodeName);
208
209 // Append an <output/> element to represent the attribute's value
210 $this->appendAVT($irAttribute, $attribute->value);
211 }
212
213 // Parse the content of this node
214 $this->parseChildren($element, $node);
215 }
216
217 /**
218 * Parse an <xsl:apply-templates/> node into the internal representation
219 *
220 * @param DOMElement $ir Node in the internal representation that represents the node's parent
221 * @param DOMElement $node <xsl:apply-templates/> node
222 * @return void
223 */
224 protected function parseXslApplyTemplates(DOMElement $ir, DOMElement $node)
225 {
226 $applyTemplates = $this->appendElement($ir, 'applyTemplates');
227 if ($node->hasAttribute('select'))
228 {
229 $applyTemplates->setAttribute('select', $node->getAttribute('select'));
230 }
231 }
232
233 /**
234 * Parse an <xsl:attribute/> node into the internal representation
235 *
236 * @param DOMElement $ir Node in the internal representation that represents the node's parent
237 * @param DOMElement $node <xsl:attribute/> node
238 * @return void
239 */
240 protected function parseXslAttribute(DOMElement $ir, DOMElement $node)
241 {
242 $attribute = $this->appendElement($ir, 'attribute');
243 $attribute->setAttribute('name', $node->getAttribute('name'));
244 $this->parseChildren($attribute, $node);
245 }
246
247 /**
248 * Parse an <xsl:choose/> node and its <xsl:when/> and <xsl:otherwise/> children into the
249 * internal representation
250 *
251 * @param DOMElement $ir Node in the internal representation that represents the node's parent
252 * @param DOMElement $node <xsl:choose/> node
253 * @return void
254 */
255 protected function parseXslChoose(DOMElement $ir, DOMElement $node)
256 {
257 $switch = $this->appendElement($ir, 'switch');
258 foreach ($this->query('./xsl:when', $node) as $when)
259 {
260 // Create a <case/> element with the original test condition in @test
261 $case = $this->appendElement($switch, 'case');
262 $case->setAttribute('test', $when->getAttribute('test'));
263 $this->parseChildren($case, $when);
264 }
265
266 // Add the default branch, which is presumed to be last
267 foreach ($this->query('./xsl:otherwise', $node) as $otherwise)
268 {
269 $case = $this->appendElement($switch, 'case');
270 $this->parseChildren($case, $otherwise);
271
272 // There should be only one <xsl:otherwise/> but we'll break anyway
273 break;
274 }
275 }
276
277 /**
278 * Parse an <xsl:comment/> node into the internal representation
279 *
280 * @param DOMElement $ir Node in the internal representation that represents the node's parent
281 * @param DOMElement $node <xsl:comment/> node
282 * @return void
283 */
284 protected function parseXslComment(DOMElement $ir, DOMElement $node)
285 {
286 $comment = $this->appendElement($ir, 'comment');
287 $this->parseChildren($comment, $node);
288 }
289
290 /**
291 * Parse an <xsl:copy-of/> node into the internal representation
292 *
293 * NOTE: only attributes are supported
294 *
295 * @param DOMElement $ir Node in the internal representation that represents the node's parent
296 * @param DOMElement $node <xsl:copy-of/> node
297 * @return void
298 */
299 protected function parseXslCopyOf(DOMElement $ir, DOMElement $node)
300 {
301 $expr = $node->getAttribute('select');
302 if (preg_match('#^@[-\\w]+(?:\\s*\\|\\s*@[-\\w]+)*$#', $expr, $m))
303 {
304 // <xsl:copy-of select="@foo"/>
305 $this->appendConditionalAttributes($ir, $expr);
306 }
307 elseif ($expr === '@*')
308 {
309 // <xsl:copy-of select="@*"/>
310 $this->appendElement($ir, 'copyOfAttributes');
311 }
312 else
313 {
314 throw new RuntimeException("Unsupported <xsl:copy-of/> expression '" . $expr . "'");
315 }
316 }
317
318 /**
319 * Parse an <xsl:element/> node into the internal representation
320 *
321 * @param DOMElement $ir Node in the internal representation that represents the node's parent
322 * @param DOMElement $node <xsl:element/> node
323 * @return void
324 */
325 protected function parseXslElement(DOMElement $ir, DOMElement $node)
326 {
327 $element = $this->appendElement($ir, 'element');
328 $element->setAttribute('name', $node->getAttribute('name'));
329 $this->parseChildren($element, $node);
330 }
331
332 /**
333 * Parse an <xsl:if/> node into the internal representation
334 *
335 * @param DOMElement $ir Node in the internal representation that represents the node's parent
336 * @param DOMElement $node <xsl:if/> node
337 * @return void
338 */
339 protected function parseXslIf(DOMElement $ir, DOMElement $node)
340 {
341 // An <xsl:if/> is represented by a <switch/> with only one <case/>
342 $switch = $this->appendElement($ir, 'switch');
343 $case = $this->appendElement($switch, 'case');
344 $case->setAttribute('test', $node->getAttribute('test'));
345
346 // Parse this branch's content
347 $this->parseChildren($case, $node);
348 }
349
350 /**
351 * Parse an <xsl:text/> node into the internal representation
352 *
353 * @param DOMElement $ir Node in the internal representation that represents the node's parent
354 * @param DOMElement $node <xsl:text/> node
355 * @return void
356 */
357 protected function parseXslText(DOMElement $ir, DOMElement $node)
358 {
359 $this->appendLiteralOutput($ir, $node->textContent);
360 if ($node->getAttribute('disable-output-escaping') === 'yes')
361 {
362 $ir->lastChild->setAttribute('disable-output-escaping', 'yes');
363 }
364 }
365
366 /**
367 * Parse an <xsl:value-of/> node into the internal representation
368 *
369 * @param DOMElement $ir Node in the internal representation that represents the node's parent
370 * @param DOMElement $node <xsl:value-of/> node
371 * @return void
372 */
373 protected function parseXslValueOf(DOMElement $ir, DOMElement $node)
374 {
375 $this->appendXPathOutput($ir, $node->getAttribute('select'));
376 if ($node->getAttribute('disable-output-escaping') === 'yes')
377 {
378 $ir->lastChild->setAttribute('disable-output-escaping', 'yes');
379 }
380 }
381 }