1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtXmlPatterns module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QStringList> |
41 | |
42 | #include "qbuiltintypes_p.h" |
43 | #include "qcommonnamespaces_p.h" |
44 | #include "qparsercontext_p.h" |
45 | #include "qquerytransformparser_p.h" |
46 | #include "qxquerytokenizer_p.h" |
47 | #include "qpatternistlocale_p.h" |
48 | |
49 | #include "qxslttokenizer_p.h" |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | using namespace QPatternist; |
54 | |
55 | Tokenizer::Token SingleTokenContainer::nextToken(XPATHLTYPE *const location) |
56 | { |
57 | if(m_hasDelivered) |
58 | return Tokenizer::Token(T_END_OF_FILE); |
59 | else |
60 | { |
61 | *location = m_location; |
62 | m_hasDelivered = true; |
63 | return m_token; |
64 | } |
65 | } |
66 | |
67 | XSLTTokenizer::XSLTTokenizer(QIODevice *const queryDevice, |
68 | const QUrl &location, |
69 | const ReportContext::Ptr &context, |
70 | const NamePool::Ptr &np) : Tokenizer(location) |
71 | , MaintainingReader<XSLTTokenLookup>(createElementDescriptions(), createStandardAttributes(), context, queryDevice) |
72 | , m_location(location) |
73 | , m_namePool(np) |
74 | /* We initialize after all name constants. */ |
75 | , m_validationAlternatives(createValidationAlternatives()) |
76 | , m_parseInfo(0) |
77 | { |
78 | Q_ASSERT(m_namePool); |
79 | |
80 | pushState(nextState: OutsideDocumentElement); |
81 | } |
82 | |
83 | bool XSLTTokenizer::isAnyAttributeAllowed() const |
84 | { |
85 | return m_processingMode.top() == ForwardCompatible; |
86 | } |
87 | |
88 | void XSLTTokenizer::setParserContext(const ParserContext::Ptr &parseInfo) |
89 | { |
90 | m_parseInfo = parseInfo; |
91 | } |
92 | |
93 | void XSLTTokenizer::validateElement() const |
94 | { |
95 | MaintainingReader<XSLTTokenLookup>::validateElement(elementName: currentElementName()); |
96 | } |
97 | |
98 | QSet<XSLTTokenizer::NodeName> XSLTTokenizer::createStandardAttributes() |
99 | { |
100 | QSet<NodeName> retval; |
101 | enum |
102 | { |
103 | ReservedForAttributes = 6 |
104 | }; |
105 | |
106 | retval.reserve(asize: 6); |
107 | |
108 | retval.insert(value: DefaultCollation); |
109 | retval.insert(value: ExcludeResultPrefixes); |
110 | retval.insert(value: ExtensionElementPrefixes); |
111 | retval.insert(value: UseWhen); |
112 | retval.insert(value: Version); |
113 | retval.insert(value: XpathDefaultNamespace); |
114 | |
115 | Q_ASSERT(retval.count() == ReservedForAttributes); |
116 | |
117 | return retval; |
118 | } |
119 | |
120 | ElementDescription<XSLTTokenLookup>::Hash XSLTTokenizer::createElementDescriptions() |
121 | { |
122 | ElementDescription<XSLTTokenLookup>::Hash result; |
123 | enum |
124 | { |
125 | ReservedForElements = 40 |
126 | }; |
127 | result.reserve(asize: ReservedForElements); |
128 | |
129 | /* xsl:apply-templates */ |
130 | { |
131 | ElementDescription<XSLTTokenLookup> &e = result[ApplyTemplates]; |
132 | e.optionalAttributes.insert(value: Select); |
133 | e.optionalAttributes.insert(value: Mode); |
134 | } |
135 | |
136 | /* xsl:template */ |
137 | { |
138 | ElementDescription<XSLTTokenLookup> &e = result[Template]; |
139 | e.optionalAttributes.insert(value: Match); |
140 | e.optionalAttributes.insert(value: Name); |
141 | e.optionalAttributes.insert(value: Mode); |
142 | e.optionalAttributes.insert(value: Priority); |
143 | e.optionalAttributes.insert(value: As); |
144 | } |
145 | |
146 | /* xsl:text, xsl:choose and xsl:otherwise */ |
147 | { |
148 | ElementDescription<XSLTTokenLookup> &e = result[Text]; |
149 | result.insert(akey: Choose, avalue: e); |
150 | result.insert(akey: Otherwise, avalue: e); |
151 | } |
152 | |
153 | /* xsl:stylesheet */ |
154 | { |
155 | ElementDescription<XSLTTokenLookup> &e = result[Stylesheet]; |
156 | |
157 | e.requiredAttributes.insert(value: Version); |
158 | |
159 | e.optionalAttributes.insert(value: Id); |
160 | e.optionalAttributes.insert(value: ExtensionElementPrefixes); |
161 | e.optionalAttributes.insert(value: ExcludeResultPrefixes); |
162 | e.optionalAttributes.insert(value: XpathDefaultNamespace); |
163 | e.optionalAttributes.insert(value: DefaultValidation); |
164 | e.optionalAttributes.insert(value: DefaultCollation); |
165 | e.optionalAttributes.insert(value: InputTypeAnnotations); |
166 | } |
167 | |
168 | /* xsl:transform */ |
169 | { |
170 | result[Transform] = result[Stylesheet]; |
171 | } |
172 | |
173 | /* xsl:value-of */ |
174 | { |
175 | ElementDescription<XSLTTokenLookup> &e = result[ValueOf]; |
176 | e.optionalAttributes.insert(value: Separator); |
177 | e.optionalAttributes.insert(value: Select); |
178 | } |
179 | |
180 | /* xsl:variable */ |
181 | { |
182 | ElementDescription<XSLTTokenLookup> &e = result[Variable]; |
183 | |
184 | e.requiredAttributes.insert(value: Name); |
185 | |
186 | e.optionalAttributes.insert(value: Select); |
187 | e.optionalAttributes.insert(value: As); |
188 | } |
189 | |
190 | /* xsl:when & xsl:if */ |
191 | { |
192 | ElementDescription<XSLTTokenLookup> &e = result[When]; |
193 | |
194 | e.requiredAttributes.insert(value: Test); |
195 | |
196 | result.insert(akey: If, avalue: e); |
197 | } |
198 | |
199 | /* xsl:sequence, xsl:for-each */ |
200 | { |
201 | ElementDescription<XSLTTokenLookup> &e = result[Sequence]; |
202 | |
203 | e.requiredAttributes.insert(value: Select); |
204 | |
205 | result.insert(akey: ForEach, avalue: e); |
206 | } |
207 | |
208 | /* xsl:comment */ |
209 | { |
210 | ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::Comment]; |
211 | |
212 | e.optionalAttributes.insert(value: Select); |
213 | } |
214 | |
215 | /* xsl:processing-instruction */ |
216 | { |
217 | ElementDescription<XSLTTokenLookup> &e = result[XSLTTokenLookup::ProcessingInstruction]; |
218 | |
219 | e.requiredAttributes.insert(value: Name); |
220 | e.optionalAttributes.insert(value: Select); |
221 | } |
222 | |
223 | /* xsl:document */ |
224 | { |
225 | ElementDescription<XSLTTokenLookup> &e = result[Document]; |
226 | |
227 | e.optionalAttributes.insert(value: Validation); |
228 | e.optionalAttributes.insert(value: Type); |
229 | } |
230 | |
231 | /* xsl:element */ |
232 | { |
233 | ElementDescription<XSLTTokenLookup> &e = result[Element]; |
234 | |
235 | e.requiredAttributes.insert(value: Name); |
236 | |
237 | e.optionalAttributes.insert(value: Namespace); |
238 | e.optionalAttributes.insert(value: InheritNamespaces); |
239 | e.optionalAttributes.insert(value: UseAttributeSets); |
240 | e.optionalAttributes.insert(value: Validation); |
241 | e.optionalAttributes.insert(value: Type); |
242 | } |
243 | |
244 | /* xsl:attribute */ |
245 | { |
246 | ElementDescription<XSLTTokenLookup> &e = result[Attribute]; |
247 | |
248 | e.requiredAttributes.insert(value: Name); |
249 | |
250 | e.optionalAttributes.insert(value: Namespace); |
251 | e.optionalAttributes.insert(value: Select); |
252 | e.optionalAttributes.insert(value: Separator); |
253 | e.optionalAttributes.insert(value: Validation); |
254 | e.optionalAttributes.insert(value: Type); |
255 | } |
256 | |
257 | /* xsl:function */ |
258 | { |
259 | ElementDescription<XSLTTokenLookup> &e = result[Function]; |
260 | |
261 | e.requiredAttributes.insert(value: Name); |
262 | |
263 | e.optionalAttributes.insert(value: As); |
264 | e.optionalAttributes.insert(value: Override); |
265 | } |
266 | |
267 | /* xsl:param */ |
268 | { |
269 | ElementDescription<XSLTTokenLookup> &e = result[Param]; |
270 | |
271 | e.requiredAttributes.insert(value: Name); |
272 | |
273 | e.optionalAttributes.insert(value: Select); |
274 | e.optionalAttributes.insert(value: As); |
275 | e.optionalAttributes.insert(value: Required); |
276 | e.optionalAttributes.insert(value: Tunnel); |
277 | } |
278 | |
279 | /* xsl:namespace */ |
280 | { |
281 | ElementDescription<XSLTTokenLookup> &e = result[Namespace]; |
282 | |
283 | e.requiredAttributes.insert(value: Name); |
284 | e.optionalAttributes.insert(value: Select); |
285 | } |
286 | |
287 | /* xsl:call-template */ |
288 | { |
289 | ElementDescription<XSLTTokenLookup> &e = result[CallTemplate]; |
290 | e.requiredAttributes.insert(value: Name); |
291 | } |
292 | |
293 | /* xsl:perform-sort */ |
294 | { |
295 | ElementDescription<XSLTTokenLookup> &e = result[PerformSort]; |
296 | e.requiredAttributes.insert(value: Select); |
297 | } |
298 | |
299 | /* xsl:sort */ |
300 | { |
301 | ElementDescription<XSLTTokenLookup> &e = result[Sort]; |
302 | |
303 | e.optionalAttributes.reserve(asize: 7); |
304 | e.optionalAttributes.insert(value: Select); |
305 | e.optionalAttributes.insert(value: Lang); |
306 | e.optionalAttributes.insert(value: Order); |
307 | e.optionalAttributes.insert(value: Collation); |
308 | e.optionalAttributes.insert(value: Stable); |
309 | e.optionalAttributes.insert(value: CaseOrder); |
310 | e.optionalAttributes.insert(value: DataType); |
311 | } |
312 | |
313 | /* xsl:import-schema */ |
314 | { |
315 | ElementDescription<XSLTTokenLookup> &e = result[ImportSchema]; |
316 | |
317 | e.optionalAttributes.reserve(asize: 2); |
318 | e.optionalAttributes.insert(value: Namespace); |
319 | e.optionalAttributes.insert(value: SchemaLocation); |
320 | } |
321 | |
322 | /* xsl:message */ |
323 | { |
324 | ElementDescription<XSLTTokenLookup> &e = result[Message]; |
325 | |
326 | e.optionalAttributes.reserve(asize: 2); |
327 | e.optionalAttributes.insert(value: Select); |
328 | e.optionalAttributes.insert(value: Terminate); |
329 | } |
330 | |
331 | /* xsl:copy-of */ |
332 | { |
333 | ElementDescription<XSLTTokenLookup> &e = result[CopyOf]; |
334 | |
335 | e.requiredAttributes.insert(value: Select); |
336 | |
337 | e.optionalAttributes.reserve(asize: 2); |
338 | e.optionalAttributes.insert(value: CopyNamespaces); |
339 | e.optionalAttributes.insert(value: Type); |
340 | e.optionalAttributes.insert(value: Validation); |
341 | } |
342 | |
343 | /* xsl:copy */ |
344 | { |
345 | ElementDescription<XSLTTokenLookup> &e = result[Copy]; |
346 | |
347 | e.optionalAttributes.reserve(asize: 5); |
348 | e.optionalAttributes.insert(value: CopyNamespaces); |
349 | e.optionalAttributes.insert(value: InheritNamespaces); |
350 | e.optionalAttributes.insert(value: UseAttributeSets); |
351 | e.optionalAttributes.insert(value: Type); |
352 | e.optionalAttributes.insert(value: Validation); |
353 | } |
354 | |
355 | /* xsl:output */ |
356 | { |
357 | ElementDescription<XSLTTokenLookup> &e = result[Output]; |
358 | |
359 | e.optionalAttributes.reserve(asize: 17); |
360 | e.optionalAttributes.insert(value: Name); |
361 | e.optionalAttributes.insert(value: Method); |
362 | e.optionalAttributes.insert(value: ByteOrderMark); |
363 | e.optionalAttributes.insert(value: CdataSectionElements); |
364 | e.optionalAttributes.insert(value: DoctypePublic); |
365 | e.optionalAttributes.insert(value: DoctypeSystem); |
366 | e.optionalAttributes.insert(value: Encoding); |
367 | e.optionalAttributes.insert(value: EscapeUriAttributes); |
368 | e.optionalAttributes.insert(value: IncludeContentType); |
369 | e.optionalAttributes.insert(value: Indent); |
370 | e.optionalAttributes.insert(value: MediaType); |
371 | e.optionalAttributes.insert(value: NormalizationForm); |
372 | e.optionalAttributes.insert(value: OmitXmlDeclaration); |
373 | e.optionalAttributes.insert(value: Standalone); |
374 | e.optionalAttributes.insert(value: UndeclarePrefixes); |
375 | e.optionalAttributes.insert(value: UseCharacterMaps); |
376 | e.optionalAttributes.insert(value: Version); |
377 | } |
378 | |
379 | /* xsl:attribute-set */ |
380 | { |
381 | ElementDescription<XSLTTokenLookup> &e = result[AttributeSet]; |
382 | |
383 | e.requiredAttributes.insert(value: Name); |
384 | e.optionalAttributes.insert(value: UseAttributeSets); |
385 | } |
386 | |
387 | /* xsl:include and xsl:import. */ |
388 | { |
389 | ElementDescription<XSLTTokenLookup> &e = result[Include]; |
390 | e.requiredAttributes.insert(value: Href); |
391 | result[Import] = e; |
392 | } |
393 | |
394 | /* xsl:with-param */ |
395 | { |
396 | ElementDescription<XSLTTokenLookup> &e = result[WithParam]; |
397 | e.requiredAttributes.insert(value: Name); |
398 | |
399 | e.optionalAttributes.insert(value: Select); |
400 | e.optionalAttributes.insert(value: As); |
401 | e.optionalAttributes.insert(value: Tunnel); |
402 | } |
403 | |
404 | /* xsl:strip-space */ |
405 | { |
406 | ElementDescription<XSLTTokenLookup> &e = result[StripSpace]; |
407 | e.requiredAttributes.insert(value: Elements); |
408 | |
409 | result.insert(akey: PreserveSpace, avalue: e); |
410 | } |
411 | |
412 | /* xsl:result-document */ |
413 | { |
414 | ElementDescription<XSLTTokenLookup> &e = result[ResultDocument]; |
415 | |
416 | e.optionalAttributes.insert(value: ByteOrderMark); |
417 | e.optionalAttributes.insert(value: CdataSectionElements); |
418 | e.optionalAttributes.insert(value: DoctypePublic); |
419 | e.optionalAttributes.insert(value: DoctypeSystem); |
420 | e.optionalAttributes.insert(value: Encoding); |
421 | e.optionalAttributes.insert(value: EscapeUriAttributes); |
422 | e.optionalAttributes.insert(value: Format); |
423 | e.optionalAttributes.insert(value: Href); |
424 | e.optionalAttributes.insert(value: IncludeContentType); |
425 | e.optionalAttributes.insert(value: Indent); |
426 | e.optionalAttributes.insert(value: MediaType); |
427 | e.optionalAttributes.insert(value: Method); |
428 | e.optionalAttributes.insert(value: NormalizationForm); |
429 | e.optionalAttributes.insert(value: OmitXmlDeclaration); |
430 | e.optionalAttributes.insert(value: OutputVersion); |
431 | e.optionalAttributes.insert(value: Standalone); |
432 | e.optionalAttributes.insert(value: Type); |
433 | e.optionalAttributes.insert(value: UndeclarePrefixes); |
434 | e.optionalAttributes.insert(value: UseCharacterMaps); |
435 | e.optionalAttributes.insert(value: Validation); |
436 | } |
437 | |
438 | /* xsl:key */ |
439 | { |
440 | ElementDescription<XSLTTokenLookup> &e = result[Key]; |
441 | |
442 | e.requiredAttributes.insert(value: Name); |
443 | e.requiredAttributes.insert(value: Match); |
444 | |
445 | e.optionalAttributes.insert(value: Use); |
446 | e.optionalAttributes.insert(value: Collation); |
447 | } |
448 | |
449 | /* xsl:analyze-string */ |
450 | { |
451 | ElementDescription<XSLTTokenLookup> &e = result[AnalyzeString]; |
452 | |
453 | e.requiredAttributes.insert(value: Select); |
454 | e.requiredAttributes.insert(value: Regex); |
455 | |
456 | e.optionalAttributes.insert(value: Flags); |
457 | } |
458 | |
459 | /* xsl:matching-substring */ |
460 | { |
461 | /* We insert a default constructed value. */ |
462 | result[MatchingSubstring]; |
463 | } |
464 | |
465 | /* xsl:non-matching-substring */ |
466 | { |
467 | /* We insert a default constructed value. */ |
468 | result[NonMatchingSubstring]; |
469 | } |
470 | |
471 | Q_ASSERT(result.count() == ReservedForElements); |
472 | |
473 | return result; |
474 | } |
475 | |
476 | QHash<QString, int> XSLTTokenizer::createValidationAlternatives() |
477 | { |
478 | QHash<QString, int> retval; |
479 | |
480 | retval.insert(akey: QLatin1String("preserve" ), avalue: 0); |
481 | retval.insert(akey: QLatin1String("strip" ), avalue: 1); |
482 | retval.insert(akey: QLatin1String("strict" ), avalue: 2); |
483 | retval.insert(akey: QLatin1String("lax" ), avalue: 3); |
484 | |
485 | return retval; |
486 | } |
487 | |
488 | bool XSLTTokenizer::whitespaceToSkip() const |
489 | { |
490 | return m_stripWhitespace.top() && isWhitespace(); |
491 | } |
492 | |
493 | void XSLTTokenizer::unexpectedContent(const ReportContext::ErrorCode code) const |
494 | { |
495 | QString message; |
496 | |
497 | ReportContext::ErrorCode effectiveCode = code; |
498 | |
499 | switch(tokenType()) |
500 | { |
501 | case QXmlStreamReader::StartElement: |
502 | { |
503 | if(isXSLT()) |
504 | { |
505 | switch(currentElementName()) |
506 | { |
507 | case Include: |
508 | effectiveCode = ReportContext::XTSE0170; |
509 | break; |
510 | case Import: |
511 | effectiveCode = ReportContext::XTSE0190; |
512 | break; |
513 | default: |
514 | ; |
515 | } |
516 | } |
517 | |
518 | message = QtXmlPatterns::tr(sourceText: "Element %1 is not allowed at this location." ) |
519 | .arg(a: formatKeyword(keyword: name())); |
520 | break; |
521 | } |
522 | case QXmlStreamReader::Characters: |
523 | { |
524 | if(whitespaceToSkip()) |
525 | return; |
526 | |
527 | message = QtXmlPatterns::tr(sourceText: "Text nodes are not allowed at this location." ); |
528 | break; |
529 | } |
530 | case QXmlStreamReader::Invalid: |
531 | { |
532 | /* It's an issue with well-formedness. */ |
533 | message = escape(input: errorString()); |
534 | break; |
535 | } |
536 | default: |
537 | Q_ASSERT(false); |
538 | } |
539 | |
540 | error(message, code: effectiveCode); |
541 | } |
542 | |
543 | void XSLTTokenizer::checkForParseError() const |
544 | { |
545 | if(hasError()) |
546 | { |
547 | error(message: QtXmlPatterns::tr(sourceText: "Parse error: %1" ).arg(a: escape(input: errorString())), code: ReportContext::XTSE0010); |
548 | } |
549 | } |
550 | |
551 | QString XSLTTokenizer::readElementText() |
552 | { |
553 | QString result; |
554 | |
555 | while(!atEnd()) |
556 | { |
557 | switch(readNext()) |
558 | { |
559 | case QXmlStreamReader::Characters: |
560 | { |
561 | result += text().toString(); |
562 | continue; |
563 | } |
564 | case QXmlStreamReader::Comment: |
565 | case QXmlStreamReader::ProcessingInstruction: |
566 | continue; |
567 | case QXmlStreamReader::EndElement: |
568 | return result; |
569 | default: |
570 | unexpectedContent(); |
571 | } |
572 | } |
573 | |
574 | checkForParseError(); |
575 | return result; |
576 | } |
577 | |
578 | int XSLTTokenizer::commenceScanOnly() |
579 | { |
580 | /* Do nothing, return a dummy value. */ |
581 | return 0; |
582 | } |
583 | |
584 | void XSLTTokenizer::resumeTokenizationFrom(const int position) |
585 | { |
586 | /* Do nothing. */ |
587 | Q_UNUSED(position); |
588 | } |
589 | |
590 | void XSLTTokenizer::handleXSLTVersion(TokenSource::Queue *const to, |
591 | QStack<Token> *const queueOnExit, |
592 | const bool isXSLTElement, |
593 | const QXmlStreamAttributes *atts, |
594 | const bool generateCode, |
595 | const bool setGlobalVersion) |
596 | { |
597 | const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT); |
598 | const QXmlStreamAttributes effectiveAtts(atts ? *atts : attributes()); |
599 | |
600 | if(!effectiveAtts.hasAttribute(namespaceUri: ns, name: QLatin1String("version" ))) |
601 | return; |
602 | |
603 | const QString attribute(effectiveAtts.value(namespaceUri: ns, name: QLatin1String("version" )).toString()); |
604 | const AtomicValue::Ptr number(Decimal::fromLexical(strNumeric: attribute)); |
605 | |
606 | if(number->hasError()) |
607 | { |
608 | error(message: QtXmlPatterns::tr(sourceText: "The value of the XSL-T version attribute " |
609 | "must be a value of type %1, which %2 isn't." ).arg(args: formatType(np: m_namePool, type: BuiltinTypes::xsDecimal), |
610 | args: formatData(data: attribute)), |
611 | code: ReportContext::XTSE0110); |
612 | } |
613 | else |
614 | { |
615 | |
616 | if(generateCode) |
617 | { |
618 | queueToken(token: Token(T_XSLT_VERSION, attribute), ts: to); |
619 | queueToken(token: T_CURLY_LBRACE, ts: to); |
620 | } |
621 | |
622 | const xsDecimal version = number->as<Numeric>()->toDecimal(); |
623 | if(version == 2.0) |
624 | m_processingMode.push(t: NormalProcessing); |
625 | else if(version == 1.0) |
626 | { |
627 | /* See section 3.6 Stylesheet Element discussing this. */ |
628 | warning(message: QtXmlPatterns::tr(sourceText: "Running an XSL-T 1.0 stylesheet with a 2.0 processor." )); |
629 | m_processingMode.push(t: BackwardsCompatible); |
630 | |
631 | if(setGlobalVersion) |
632 | { |
633 | m_parseInfo->staticContext->setCompatModeEnabled(true); |
634 | m_parseInfo->isBackwardsCompat.push(t: true); |
635 | } |
636 | } |
637 | else if(version > 2.0) |
638 | m_processingMode.push(t: ForwardCompatible); |
639 | else if(version < 2.0) |
640 | m_processingMode.push(t: BackwardsCompatible); |
641 | } |
642 | |
643 | if(generateCode) |
644 | queueOnExit->push(t: T_CURLY_RBRACE); |
645 | } |
646 | |
647 | void XSLTTokenizer::handleXMLBase(TokenSource::Queue *const to, |
648 | QStack<Token> *const queueOnExit, |
649 | const bool isInstruction, |
650 | const QXmlStreamAttributes *atts) |
651 | { |
652 | const QXmlStreamAttributes effectiveAtts(atts ? *atts : m_currentAttributes); |
653 | |
654 | if(effectiveAtts.hasAttribute(qualifiedName: QLatin1String("xml:base" ))) |
655 | { |
656 | const QStringRef val(effectiveAtts.value(qualifiedName: QLatin1String("xml:base" ))); |
657 | |
658 | if(!val.isEmpty()) |
659 | { |
660 | if(isInstruction) |
661 | { |
662 | queueToken(token: T_BASEURI, ts: to); |
663 | queueToken(token: Token(T_STRING_LITERAL, val.toString()), ts: to); |
664 | queueToken(token: T_CURLY_LBRACE, ts: to); |
665 | queueOnExit->push(t: T_CURLY_RBRACE); |
666 | } |
667 | else |
668 | { |
669 | queueToken(token: T_DECLARE, ts: to); |
670 | queueToken(token: T_BASEURI, ts: to); |
671 | queueToken(token: T_INTERNAL, ts: to); |
672 | queueToken(token: Token(T_STRING_LITERAL, val.toString()), ts: to); |
673 | queueToken(token: T_SEMI_COLON, ts: to); |
674 | } |
675 | } |
676 | } |
677 | } |
678 | |
679 | void XSLTTokenizer::handleStandardAttributes(const bool isXSLTElement) |
680 | { |
681 | /* We're not necessarily StartElement, that's why we have atts passed in. */ |
682 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
683 | |
684 | if(m_hasHandledStandardAttributes) |
685 | return; |
686 | |
687 | m_hasHandledStandardAttributes = true; |
688 | |
689 | const QString ns(isXSLTElement ? QString() : CommonNamespaces::XSLT); |
690 | const int len = m_currentAttributes.count(); |
691 | |
692 | for(int i = 0; i < len; ++i) |
693 | { |
694 | const QXmlStreamAttribute &att = m_currentAttributes.at(i); |
695 | |
696 | if(att.qualifiedName() == QLatin1String("xml:space" )) |
697 | { |
698 | /* We raise an error if the value is not recognized. |
699 | * |
700 | * Extensible Markup Language (XML) 1.0 (Fourth Edition), 2.10 |
701 | * White Space Handling: |
702 | * |
703 | * 'This specification does not give meaning to any value of |
704 | * xml:space other than "default" and "preserve". It is an error |
705 | * for other values to be specified; the XML processor may report |
706 | * the error or may recover by ignoring the attribute specification |
707 | * or by reporting the (erroneous) value to the application.' */ |
708 | m_stripWhitespace.push(t: readToggleAttribute(attributeName: QLatin1String("xml:space" ), |
709 | isTrue: QLatin1String("default" ), |
710 | isFalse: QLatin1String("preserve" ), |
711 | atts: &m_currentAttributes)); |
712 | } |
713 | |
714 | if(att.namespaceUri() != ns) |
715 | continue; |
716 | |
717 | switch(toToken(value: att.name())) |
718 | { |
719 | case Type: |
720 | case Validation: |
721 | case UseAttributeSets: |
722 | case Version: |
723 | /* These are handled by other function such as |
724 | * handleValidationAttributes() and handleXSLTVersion(). */ |
725 | continue; |
726 | default: |
727 | { |
728 | if(!isXSLTElement) /* validateElement() will take care of it, and we |
729 | * don't want to flag non-standard XSL-T attributes. */ |
730 | { |
731 | error(message: QtXmlPatterns::tr(sourceText: "Unknown XSL-T attribute %1." ) |
732 | .arg(a: formatKeyword(keyword: att.name())), |
733 | code: ReportContext::XTSE0805); |
734 | } |
735 | } |
736 | } |
737 | } |
738 | } |
739 | |
740 | void XSLTTokenizer::handleValidationAttributes(const bool isLRE) const |
741 | { |
742 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
743 | |
744 | const QString ns(isLRE ? QString() : CommonNamespaces::XSLT); |
745 | |
746 | const bool hasValidation = hasAttribute(namespaceURI: ns, localName: QLatin1String("validation" )); |
747 | const bool hasType = hasAttribute(namespaceURI: ns, localName: QLatin1String("type" )); |
748 | |
749 | if(!hasType && !hasValidation) |
750 | return; |
751 | |
752 | if(hasType && hasValidation) |
753 | { |
754 | error(message: QtXmlPatterns::tr(sourceText: "Attribute %1 and %2 are mutually exclusive." ) |
755 | .arg(args: formatKeyword(keyword: QLatin1String("validation" )), |
756 | args: formatKeyword(keyword: QLatin1String("type" ))), |
757 | code: ReportContext::XTSE1505); |
758 | } |
759 | |
760 | /* QXmlStreamReader surely doesn't make this easy. */ |
761 | QXmlStreamAttribute validationAttribute; |
762 | int len = m_currentAttributes.count(); |
763 | |
764 | for(int i = 0; i < len; ++i) |
765 | { |
766 | const QXmlStreamAttribute &at = m_currentAttributes.at(i); |
767 | if(at.name() == QLatin1String("validation" ) && at.namespaceUri() == ns) |
768 | validationAttribute = at; |
769 | } |
770 | |
771 | Q_ASSERT_X(!validationAttribute.name().isNull(), Q_FUNC_INFO, |
772 | "We should always find the attribute." ); |
773 | |
774 | /* We don't care about the return value, we just want to check it's a valid |
775 | * one. */ |
776 | readAlternativeAttribute(alternatives: m_validationAlternatives, |
777 | attr: validationAttribute); |
778 | } |
779 | |
780 | Tokenizer::Token XSLTTokenizer::nextToken(XPATHLTYPE *const sourceLocator) |
781 | { |
782 | Q_UNUSED(sourceLocator); |
783 | |
784 | if(m_tokenSource.isEmpty()) |
785 | { |
786 | switch(m_state.top()) |
787 | { |
788 | case OutsideDocumentElement: |
789 | outsideDocumentElement(); |
790 | break; |
791 | case InsideStylesheetModule: |
792 | insideStylesheetModule(); |
793 | break; |
794 | case InsideSequenceConstructor: |
795 | insideSequenceConstructor(to: &m_tokenSource); |
796 | break; |
797 | } |
798 | |
799 | if(m_tokenSource.isEmpty()) |
800 | { |
801 | *sourceLocator = currentSourceLocator(); |
802 | return Token(T_END_OF_FILE); |
803 | } |
804 | else |
805 | return m_tokenSource.head()->nextToken(sourceLocator); |
806 | } |
807 | else |
808 | { |
809 | do |
810 | { |
811 | const Token candidate(m_tokenSource.head()->nextToken(sourceLocator)); |
812 | if (candidate.type == T_END_OF_FILE) |
813 | m_tokenSource.dequeue(); |
814 | else |
815 | return candidate; |
816 | } |
817 | while(!m_tokenSource.isEmpty()); |
818 | |
819 | /* Now we will resume parsing inside the regular XSL-T(XML) file. */ |
820 | return nextToken(sourceLocator); |
821 | } |
822 | } |
823 | |
824 | bool XSLTTokenizer::isElement(const XSLTTokenLookup::NodeName &name) const |
825 | { |
826 | Q_ASSERT(isXSLT()); |
827 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || |
828 | tokenType() == QXmlStreamReader::EndElement); |
829 | |
830 | return currentElementName() == name; |
831 | } |
832 | |
833 | inline bool XSLTTokenizer::isXSLT() const |
834 | { |
835 | Q_ASSERT_X(tokenType() == QXmlStreamReader::StartElement || |
836 | tokenType() == QXmlStreamReader::EndElement, |
837 | Q_FUNC_INFO, "The current token state must be StartElement or EndElement." ); |
838 | /* Possible optimization: let MaintainingReader set an m_isXSLT which we |
839 | * read. */ |
840 | return namespaceUri() == CommonNamespaces::XSLT; |
841 | } |
842 | |
843 | void XSLTTokenizer::queueOnExit(QStack<Token> &source, |
844 | TokenSource::Queue *const destination) |
845 | { |
846 | while(!source.isEmpty()) |
847 | queueToken(token: source.pop(), ts: destination); |
848 | } |
849 | |
850 | void XSLTTokenizer::outsideDocumentElement() |
851 | { |
852 | while(!atEnd()) |
853 | { |
854 | switch(readNext()) |
855 | { |
856 | case QXmlStreamReader::StartElement: |
857 | { |
858 | /* First, we synthesize one of the built-in templates, |
859 | * see section 6.6 Built-in Template Rules. |
860 | * |
861 | * Note that insideStylesheetModule() can be called multiple |
862 | * times so we can't do it there. */ |
863 | { |
864 | /* Start with the one for text nodes and attributes. |
865 | * declare template matches (text() | @*) mode #all |
866 | * { |
867 | * text{.} |
868 | * }; |
869 | */ |
870 | |
871 | /* declare template matches (text() | @*) */ |
872 | queueToken(token: T_DECLARE, ts: &m_tokenSource); |
873 | queueToken(token: T_TEMPLATE, ts: &m_tokenSource); |
874 | queueToken(token: T_MATCHES, ts: &m_tokenSource); |
875 | queueToken(token: T_LPAREN, ts: &m_tokenSource); |
876 | queueToken(token: T_TEXT, ts: &m_tokenSource); |
877 | queueToken(token: T_LPAREN, ts: &m_tokenSource); |
878 | queueToken(token: T_RPAREN, ts: &m_tokenSource); |
879 | queueToken(token: T_BAR, ts: &m_tokenSource); |
880 | queueToken(token: T_AT_SIGN, ts: &m_tokenSource); |
881 | queueToken(token: T_STAR, ts: &m_tokenSource); |
882 | queueToken(token: T_RPAREN, ts: &m_tokenSource); |
883 | |
884 | /* mode #all */ |
885 | queueToken(token: T_MODE, ts: &m_tokenSource); |
886 | queueToken(token: Token(T_NCNAME, QLatin1String("#all" )), ts: &m_tokenSource); |
887 | queueToken(token: T_CURLY_LBRACE, ts: &m_tokenSource); |
888 | |
889 | /* text{.} { */ |
890 | queueToken(token: T_TEXT, ts: &m_tokenSource); |
891 | queueToken(token: T_CURLY_LBRACE, ts: &m_tokenSource); |
892 | queueToken(token: T_DOT, ts: &m_tokenSource); |
893 | queueToken(token: T_CURLY_RBRACE, ts: &m_tokenSource); |
894 | |
895 | /* }; */ |
896 | queueToken(token: T_CURLY_RBRACE, ts: &m_tokenSource); |
897 | queueToken(token: T_SEMI_COLON, ts: &m_tokenSource); |
898 | } |
899 | |
900 | if(isXSLT() && isStylesheetElement()) |
901 | { |
902 | handleStandardAttributes(isXSLTElement: true); |
903 | QStack<Token> onExitTokens; |
904 | handleXMLBase(to: &m_tokenSource, queueOnExit: &onExitTokens, isInstruction: false); |
905 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: true, atts: 0, generateCode: false, setGlobalVersion: true); |
906 | validateElement(); |
907 | queueNamespaceDeclarations(ts: &m_tokenSource, target: 0, isDeclaration: true); |
908 | |
909 | /* We're a regular stylesheet. */ |
910 | |
911 | pushState(nextState: InsideStylesheetModule); |
912 | insideStylesheetModule(); |
913 | } |
914 | else |
915 | { |
916 | /* We're a simplified stylesheet. */ |
917 | |
918 | if(!hasAttribute(namespaceURI: CommonNamespaces::XSLT, localName: QLatin1String("version" ))) |
919 | { |
920 | error(message: QtXmlPatterns::tr(sourceText: "In a simplified stylesheet module, attribute %1 must be present." ) |
921 | .arg(a: formatKeyword(keyword: QLatin1String("version" ))), |
922 | code: ReportContext::XTSE0010); |
923 | } |
924 | |
925 | QStack<Token> onExitTokens; |
926 | |
927 | /* We synthesize this as exemplified in |
928 | * 3.7 Simplified Stylesheet Modules. */ |
929 | queueToken(token: T_DECLARE, ts: &m_tokenSource); |
930 | queueToken(token: T_TEMPLATE, ts: &m_tokenSource); |
931 | queueToken(token: T_MATCHES, ts: &m_tokenSource); |
932 | queueToken(token: T_LPAREN, ts: &m_tokenSource); |
933 | queueToken(token: T_SLASH, ts: &m_tokenSource); |
934 | queueToken(token: T_RPAREN, ts: &m_tokenSource); |
935 | queueToken(token: T_CURLY_LBRACE, ts: &m_tokenSource); |
936 | pushState(nextState: InsideSequenceConstructor); |
937 | |
938 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: false, atts: 0, generateCode: true); |
939 | handleStandardAttributes(isXSLTElement: false); |
940 | |
941 | insideSequenceConstructor(to: &m_tokenSource, initialAdvance: false); |
942 | |
943 | queueOnExit(source&: onExitTokens, destination: &m_tokenSource); |
944 | queueToken(token: T_CURLY_RBRACE, ts: &m_tokenSource); |
945 | queueToken(token: T_CURLY_RBRACE, ts: &m_tokenSource); |
946 | queueToken(token: T_SEMI_COLON, ts: &m_tokenSource); |
947 | } |
948 | |
949 | queueToken(token: T_APPLY_TEMPLATE, ts: &m_tokenSource); |
950 | queueToken(token: T_LPAREN, ts: &m_tokenSource); |
951 | queueToken(token: T_RPAREN, ts: &m_tokenSource); |
952 | |
953 | break; |
954 | } |
955 | default: |
956 | /* Do nothing. */; |
957 | } |
958 | } |
959 | checkForParseError(); |
960 | } |
961 | |
962 | void XSLTTokenizer::queueToken(const Token &token, |
963 | TokenSource::Queue *const to) |
964 | { |
965 | TokenSource::Queue *const effective = to ? to : &m_tokenSource; |
966 | |
967 | effective->enqueue(t: TokenSource::Ptr(new SingleTokenContainer(token, currentSourceLocator()))); |
968 | } |
969 | |
970 | void XSLTTokenizer::pushState(const State nextState) |
971 | { |
972 | m_state.push(t: nextState); |
973 | } |
974 | |
975 | void XSLTTokenizer::leaveState() |
976 | { |
977 | m_state.pop(); |
978 | } |
979 | |
980 | void XSLTTokenizer::insideTemplate() |
981 | { |
982 | const bool hasPriority = hasAttribute(localName: QLatin1String("priority" )); |
983 | const bool hasMatch = hasAttribute(localName: QLatin1String("match" )); |
984 | const bool hasName = hasAttribute(localName: QLatin1String("name" )); |
985 | const bool hasMode = hasAttribute(localName: QLatin1String("mode" )); |
986 | const bool hasAs = hasAttribute(localName: QLatin1String("as" )); |
987 | |
988 | if(!hasMatch && |
989 | (hasMode || |
990 | hasPriority)) |
991 | { |
992 | error(message: QtXmlPatterns::tr(sourceText: "If element %1 has no attribute %2, it cannot have attribute %3 or %4." ) |
993 | .arg(args: formatKeyword(keyword: QLatin1String("template" )), |
994 | args: formatKeyword(keyword: QLatin1String("match" )), |
995 | args: formatKeyword(keyword: QLatin1String("mode" )), |
996 | args: formatKeyword(keyword: QLatin1String("priority" ))), |
997 | code: ReportContext::XTSE0500); |
998 | } |
999 | else if(!hasMatch && !hasName) |
1000 | { |
1001 | error(message: QtXmlPatterns::tr(sourceText: "Element %1 must have at least one of the attributes %2 or %3." ) |
1002 | .arg(args: formatKeyword(keyword: QLatin1String("template" )), |
1003 | args: formatKeyword(keyword: QLatin1String("name" )), |
1004 | args: formatKeyword(keyword: QLatin1String("match" ))), |
1005 | code: ReportContext::XTSE0500); |
1006 | } |
1007 | |
1008 | queueToken(token: T_DECLARE, to: &m_tokenSource); |
1009 | queueToken(token: T_TEMPLATE, to: &m_tokenSource); |
1010 | |
1011 | if(hasName) |
1012 | { |
1013 | queueToken(token: T_NAME, to: &m_tokenSource); |
1014 | queueToken(token: Token(T_QNAME, readAttribute(localName: QLatin1String("name" ))), to: &m_tokenSource); |
1015 | } |
1016 | |
1017 | if(hasMatch) |
1018 | { |
1019 | queueToken(token: T_MATCHES, to: &m_tokenSource); |
1020 | queueExpression(expr: readAttribute(localName: QLatin1String("match" )), to: &m_tokenSource); |
1021 | } |
1022 | |
1023 | if(hasMode) |
1024 | { |
1025 | const QString modeString(readAttribute(localName: QLatin1String("mode" )).simplified()); |
1026 | |
1027 | if(modeString.isEmpty()) |
1028 | { |
1029 | error(message: QtXmlPatterns::tr(sourceText: "At least one mode must be specified in the %1-attribute on element %2." ) |
1030 | .arg(args: formatKeyword(keyword: QLatin1String("mode" )), |
1031 | args: formatKeyword(keyword: QLatin1String("template" ))), |
1032 | code: ReportContext::XTSE0500); |
1033 | } |
1034 | |
1035 | queueToken(token: T_MODE, to: &m_tokenSource); |
1036 | |
1037 | const QStringList modeList(modeString.split(sep: QLatin1Char(' '))); |
1038 | |
1039 | for(int i = 0; i < modeList.count(); ++i) |
1040 | { |
1041 | const QString &mode = modeList.at(i); |
1042 | |
1043 | queueToken(token: Token(mode.contains(c: QLatin1Char(':')) ? T_QNAME : T_NCNAME, mode), to: &m_tokenSource); |
1044 | |
1045 | if(i < modeList.count() - 1) |
1046 | queueToken(token: T_COMMA, to: &m_tokenSource); |
1047 | } |
1048 | } |
1049 | |
1050 | if(hasPriority) |
1051 | { |
1052 | queueToken(token: T_PRIORITY, to: &m_tokenSource); |
1053 | queueToken(token: Token(T_STRING_LITERAL, readAttribute(localName: QLatin1String("priority" ))), to: &m_tokenSource); |
1054 | } |
1055 | |
1056 | QStack<Token> onExitTokens; |
1057 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
1058 | |
1059 | /* queueParams moves the reader so we need to freeze the attributes. */ |
1060 | const QXmlStreamAttributes atts(m_currentAttributes); |
1061 | handleStandardAttributes(isXSLTElement: true); |
1062 | queueToken(token: T_LPAREN, to: &m_tokenSource); |
1063 | queueParams(parentName: Template, to: &m_tokenSource); |
1064 | queueToken(token: T_RPAREN, to: &m_tokenSource); |
1065 | |
1066 | if(hasAs) |
1067 | { |
1068 | queueToken(token: T_AS, to: &m_tokenSource); |
1069 | queueSequenceType(expr: atts.value(qualifiedName: QLatin1String("as" )).toString()); |
1070 | } |
1071 | |
1072 | queueToken(token: T_CURLY_LBRACE, to: &m_tokenSource); |
1073 | |
1074 | handleXMLBase(to: &m_tokenSource, queueOnExit: &onExitTokens, isInstruction: true, atts: &atts); |
1075 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: true, atts: &atts); |
1076 | pushState(nextState: InsideSequenceConstructor); |
1077 | startStorageOfCurrent(to: &m_tokenSource); |
1078 | insideSequenceConstructor(to: &m_tokenSource, queueOnExit&: onExitTokens, initialAdvance: false); |
1079 | queueOnExit(source&: onExitTokens, destination: &m_tokenSource); |
1080 | } |
1081 | |
1082 | void XSLTTokenizer::queueExpression(const QString &expr, |
1083 | TokenSource::Queue *const to, |
1084 | const bool wrapWithParantheses) |
1085 | { |
1086 | TokenSource::Queue *const effectiveTo = to ? to : &m_tokenSource; |
1087 | |
1088 | if(wrapWithParantheses) |
1089 | queueToken(token: T_LPAREN, to: effectiveTo); |
1090 | |
1091 | effectiveTo->enqueue(t: TokenSource::Ptr(new XQueryTokenizer(expr, queryURI()))); |
1092 | |
1093 | if(wrapWithParantheses) |
1094 | queueToken(token: T_RPAREN, to: effectiveTo); |
1095 | } |
1096 | |
1097 | void XSLTTokenizer::queueAVT(const QString &expr, |
1098 | TokenSource::Queue *const to) |
1099 | { |
1100 | queueToken(token: T_AVT, to); |
1101 | queueToken(token: T_LPAREN, to); |
1102 | to->enqueue(t: TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(), |
1103 | XQueryTokenizer::QuotAttributeContent))); |
1104 | queueToken(token: T_RPAREN, to); |
1105 | } |
1106 | |
1107 | void XSLTTokenizer::queueSequenceType(const QString &expr) |
1108 | { |
1109 | m_tokenSource.enqueue(t: TokenSource::Ptr(new XQueryTokenizer(expr, queryURI(), |
1110 | XQueryTokenizer::ItemType))); |
1111 | } |
1112 | |
1113 | void XSLTTokenizer::commencingExpression(bool &hasWrittenExpression, |
1114 | TokenSource::Queue *const to) |
1115 | { |
1116 | if(hasWrittenExpression) |
1117 | queueToken(token: T_COMMA, to); |
1118 | else |
1119 | hasWrittenExpression = true; |
1120 | } |
1121 | |
1122 | void XSLTTokenizer::queueEmptySequence(TokenSource::Queue *const to) |
1123 | { |
1124 | queueToken(token: T_LPAREN, to); |
1125 | queueToken(token: T_RPAREN, to); |
1126 | } |
1127 | |
1128 | void XSLTTokenizer::insideChoose(TokenSource::Queue *const to) |
1129 | { |
1130 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
1131 | bool hasHandledOtherwise = false; |
1132 | bool hasEncounteredAtLeastOneWhen = false; |
1133 | |
1134 | while(!atEnd()) |
1135 | { |
1136 | switch(readNext()) |
1137 | { |
1138 | case QXmlStreamReader::StartElement: |
1139 | { |
1140 | if(isXSLT()) |
1141 | { |
1142 | QStack<Token> onExitTokens; |
1143 | handleStandardAttributes(isXSLTElement: true); |
1144 | validateElement(); |
1145 | |
1146 | switch(currentElementName()) |
1147 | { |
1148 | case When: |
1149 | { |
1150 | if(hasHandledOtherwise) |
1151 | { |
1152 | error(message: QtXmlPatterns::tr(sourceText: "Element %1 must come last." ) |
1153 | .arg(a: formatKeyword(keyword: QLatin1String("otherwise" ))), |
1154 | code: ReportContext::XTSE0010); |
1155 | } |
1156 | |
1157 | queueToken(token: T_IF, to); |
1158 | queueToken(token: T_LPAREN, to); |
1159 | queueExpression(expr: readAttribute(localName: QLatin1String("test" )), to); |
1160 | queueToken(token: T_RPAREN, to); |
1161 | queueToken(token: T_THEN, to); |
1162 | queueToken(token: T_LPAREN, to); |
1163 | pushState(nextState: InsideSequenceConstructor); |
1164 | insideSequenceConstructor(to); |
1165 | queueToken(token: T_RPAREN, to); |
1166 | Q_ASSERT(tokenType() == QXmlStreamReader::EndElement); |
1167 | queueToken(token: T_ELSE, to); |
1168 | hasEncounteredAtLeastOneWhen = true; |
1169 | queueOnExit(source&: onExitTokens, destination: to); |
1170 | break; |
1171 | } |
1172 | case Otherwise: |
1173 | { |
1174 | if(!hasEncounteredAtLeastOneWhen) |
1175 | { |
1176 | error(message: QtXmlPatterns::tr(sourceText: "At least one %1-element must occur before %2." ) |
1177 | .arg(args: formatKeyword(keyword: QLatin1String("when" )), |
1178 | args: formatKeyword(keyword: QLatin1String("otherwise" ))), |
1179 | code: ReportContext::XTSE0010); |
1180 | } |
1181 | else if(hasHandledOtherwise) |
1182 | { |
1183 | error(message: QtXmlPatterns::tr(sourceText: "Only one %1-element can appear." ) |
1184 | .arg(a: formatKeyword(keyword: QLatin1String("otherwise" ))), |
1185 | code: ReportContext::XTSE0010); |
1186 | } |
1187 | |
1188 | pushState(nextState: InsideSequenceConstructor); |
1189 | queueToken(token: T_LPAREN, to); |
1190 | insideSequenceConstructor(to, initialAdvance: to); |
1191 | queueToken(token: T_RPAREN, to); |
1192 | hasHandledOtherwise = true; |
1193 | queueOnExit(source&: onExitTokens, destination: to); |
1194 | break; |
1195 | } |
1196 | default: |
1197 | unexpectedContent(); |
1198 | } |
1199 | } |
1200 | else |
1201 | unexpectedContent(); |
1202 | break; |
1203 | } |
1204 | case QXmlStreamReader::EndElement: |
1205 | { |
1206 | if(isXSLT()) |
1207 | { |
1208 | switch(currentElementName()) |
1209 | { |
1210 | case Choose: |
1211 | { |
1212 | if(!hasEncounteredAtLeastOneWhen) |
1213 | { |
1214 | error(message: QtXmlPatterns::tr(sourceText: "At least one %1-element must occur inside %2." ) |
1215 | .arg(args: formatKeyword(keyword: QLatin1String("when" )), |
1216 | args: formatKeyword(keyword: QLatin1String("choose" ))), |
1217 | code: ReportContext::XTSE0010); |
1218 | } |
1219 | |
1220 | if(!hasHandledOtherwise) |
1221 | queueEmptySequence(to); |
1222 | return; |
1223 | } |
1224 | case Otherwise: |
1225 | continue; |
1226 | default: |
1227 | unexpectedContent(); |
1228 | } |
1229 | } |
1230 | else |
1231 | unexpectedContent(); |
1232 | break; |
1233 | } |
1234 | case QXmlStreamReader::Comment: |
1235 | case QXmlStreamReader::ProcessingInstruction: |
1236 | continue; |
1237 | case QXmlStreamReader::Characters: |
1238 | { |
1239 | /* We ignore regardless of what xml:space says, see step 4 in |
1240 | * 4.2 Stripping Whitespace from the Stylesheet. */ |
1241 | if(isWhitespace()) |
1242 | continue; |
1243 | Q_FALLTHROUGH(); |
1244 | } |
1245 | default: |
1246 | unexpectedContent(); |
1247 | break; |
1248 | } |
1249 | } |
1250 | checkForParseError(); |
1251 | } |
1252 | |
1253 | bool XSLTTokenizer::queueSelectOrSequenceConstructor(const ReportContext::ErrorCode code, |
1254 | const bool emptynessAllowed, |
1255 | TokenSource::Queue *const to, |
1256 | const QXmlStreamAttributes *const attsP, |
1257 | const bool queueEmptyOnEmpty) |
1258 | { |
1259 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || attsP); |
1260 | const NodeName elementName(currentElementName()); |
1261 | const QXmlStreamAttributes atts(attsP ? *attsP : m_currentAttributes); |
1262 | |
1263 | if(atts.hasAttribute(qualifiedName: QLatin1String("select" ))) |
1264 | { |
1265 | queueExpression(expr: atts.value(qualifiedName: QLatin1String("select" )).toString(), to); |
1266 | |
1267 | /* First, verify that we don't have a body. */ |
1268 | if(skipSubTree(exitOnContent: true)) |
1269 | { |
1270 | error(message: QtXmlPatterns::tr(sourceText: "When attribute %1 is present on %2, a sequence " |
1271 | "constructor cannot be used." ).arg(args: formatKeyword(keyword: QLatin1String("select" )), |
1272 | args: formatKeyword(keyword: toString(token: elementName))), |
1273 | code); |
1274 | } |
1275 | |
1276 | return true; |
1277 | } |
1278 | else |
1279 | { |
1280 | pushState(nextState: InsideSequenceConstructor); |
1281 | if(!insideSequenceConstructor(to, initialAdvance: true, queueEmptyOnEmpty) && !emptynessAllowed) |
1282 | { |
1283 | error(message: QtXmlPatterns::tr(sourceText: "Element %1 must have either a %2-attribute " |
1284 | "or a sequence constructor." ).arg(args: formatKeyword(keyword: toString(token: elementName)), |
1285 | args: formatKeyword(keyword: QLatin1String("select" ))), |
1286 | code); |
1287 | |
1288 | } |
1289 | |
1290 | return false; |
1291 | } |
1292 | } |
1293 | |
1294 | void XSLTTokenizer::queueSimpleContentConstructor(const ReportContext::ErrorCode code, |
1295 | const bool emptynessAllowed, |
1296 | TokenSource::Queue *const to, |
1297 | const bool selectOnlyFirst) |
1298 | { |
1299 | queueToken(token: T_INTERNAL_NAME, to); |
1300 | queueToken(token: Token(T_NCNAME, QLatin1String("generic-string-join" )), to); |
1301 | queueToken(token: T_LPAREN, to); |
1302 | |
1303 | /* We have to read the attribute before calling |
1304 | * queueSelectOrSequenceConstructor(), since it advances the reader. */ |
1305 | const bool hasSeparator = m_currentAttributes.hasAttribute(qualifiedName: QLatin1String("separator" )); |
1306 | const QString separatorAVT(m_currentAttributes.value(qualifiedName: QLatin1String("separator" )).toString()); |
1307 | |
1308 | queueToken(token: T_LPAREN, to); |
1309 | const bool viaSelectAttribute = queueSelectOrSequenceConstructor(code, emptynessAllowed, to); |
1310 | queueToken(token: T_RPAREN, to); |
1311 | |
1312 | if(selectOnlyFirst) |
1313 | { |
1314 | queueToken(token: T_LBRACKET, to); |
1315 | queueToken(token: Token(T_NUMBER, QChar::fromLatin1(c: '1')), to); |
1316 | queueToken(token: T_RBRACKET, to); |
1317 | } |
1318 | |
1319 | queueToken(token: T_COMMA, to); |
1320 | |
1321 | if(hasSeparator) |
1322 | queueAVT(expr: separatorAVT, to); |
1323 | else |
1324 | { |
1325 | /* The default value depends on whether the value is from @select, or from |
1326 | * the sequence constructor. */ |
1327 | queueToken(token: Token(T_STRING_LITERAL, viaSelectAttribute ? QString(QLatin1Char(' ')) |
1328 | : QString()), |
1329 | to); |
1330 | } |
1331 | |
1332 | queueToken(token: T_RPAREN, to); |
1333 | } |
1334 | |
1335 | void XSLTTokenizer::queueTextConstructor(QString &chars, |
1336 | bool &hasWrittenExpression, |
1337 | TokenSource::Queue *const to) |
1338 | { |
1339 | if(!chars.isEmpty()) |
1340 | { |
1341 | commencingExpression(hasWrittenExpression, to); |
1342 | queueToken(token: T_TEXT, to); |
1343 | queueToken(token: T_CURLY_LBRACE, to); |
1344 | queueToken(token: Token(T_STRING_LITERAL, chars), to); |
1345 | queueToken(token: T_CURLY_RBRACE, to); |
1346 | chars.clear(); |
1347 | } |
1348 | } |
1349 | |
1350 | void XSLTTokenizer::queueVariableDeclaration(const VariableType variableType, |
1351 | TokenSource::Queue *const to) |
1352 | { |
1353 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
1354 | |
1355 | if(variableType == VariableInstruction) |
1356 | { |
1357 | queueToken(token: T_LET, to); |
1358 | queueToken(token: T_INTERNAL, to); |
1359 | } |
1360 | else if(variableType == VariableDeclaration || variableType == GlobalParameter) |
1361 | { |
1362 | queueToken(token: T_DECLARE, to); |
1363 | queueToken(token: T_VARIABLE, to); |
1364 | queueToken(token: T_INTERNAL, to); |
1365 | } |
1366 | |
1367 | queueToken(token: T_DOLLAR, to); |
1368 | |
1369 | queueExpression(expr: readAttribute(localName: QLatin1String("name" )), to, wrapWithParantheses: false); |
1370 | |
1371 | const bool hasAs = m_currentAttributes.hasAttribute(qualifiedName: QLatin1String("as" )); |
1372 | if(hasAs) |
1373 | { |
1374 | queueToken(token: T_AS, to); |
1375 | queueSequenceType(expr: m_currentAttributes.value(qualifiedName: QLatin1String("as" )).toString()); |
1376 | } |
1377 | |
1378 | if(variableType == FunctionParameter) |
1379 | { |
1380 | skipBodyOfParam(code: ReportContext::XTSE0760); |
1381 | return; |
1382 | } |
1383 | |
1384 | /* We must do this here, because queueSelectOrSequenceConstructor() |
1385 | * advances the reader. */ |
1386 | const bool hasSelect = hasAttribute(localName: QLatin1String("select" )); |
1387 | const bool isRequired = hasAttribute(localName: QLatin1String("required" )) ? attributeYesNo(localName: QLatin1String("required" )) : false; |
1388 | |
1389 | TokenSource::Queue storage; |
1390 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE0620, emptynessAllowed: true, to: &storage, attsP: 0, queueEmptyOnEmpty: false); |
1391 | |
1392 | /* XSL-T has some wicked rules, see |
1393 | * 9.3 Values of Variables and Parameters. */ |
1394 | |
1395 | const bool hasQueuedContent = !storage.isEmpty(); |
1396 | |
1397 | /* The syntax for global parameters is: |
1398 | * |
1399 | * declare variable $var external := 'defaultValue'; |
1400 | */ |
1401 | if(variableType == GlobalParameter) |
1402 | queueToken(token: T_EXTERNAL, to); |
1403 | |
1404 | if(isRequired) |
1405 | { |
1406 | if(hasQueuedContent) |
1407 | { |
1408 | error(message: QtXmlPatterns::tr(sourceText: "When a parameter is required, a default value " |
1409 | "cannot be supplied through a %1-attribute or " |
1410 | "a sequence constructor." ).arg(a: formatKeyword(keyword: QLatin1String("select" ))), |
1411 | code: ReportContext::XTSE0010); |
1412 | } |
1413 | } |
1414 | else |
1415 | { |
1416 | if(hasQueuedContent) |
1417 | { |
1418 | queueToken(token: T_ASSIGN, to); |
1419 | |
1420 | if(!hasSelect && !hasAs && !hasQueuedContent) |
1421 | queueToken(token: Token(T_STRING_LITERAL, QString()), to); |
1422 | else if(hasAs || hasSelect) |
1423 | queueToken(token: T_LPAREN, to); |
1424 | else |
1425 | { |
1426 | queueToken(token: T_DOCUMENT, to); |
1427 | queueToken(token: T_INTERNAL, to); |
1428 | queueToken(token: T_CURLY_LBRACE, to); |
1429 | } |
1430 | } |
1431 | else |
1432 | { |
1433 | if(!hasAs) |
1434 | { |
1435 | queueToken(token: T_ASSIGN, to); |
1436 | queueToken(token: Token(T_STRING_LITERAL, QString()), to); |
1437 | } |
1438 | else if(variableType == VariableDeclaration || variableType == VariableInstruction) |
1439 | { |
1440 | queueToken(token: T_ASSIGN, to); |
1441 | queueEmptySequence(to); |
1442 | } |
1443 | } |
1444 | |
1445 | /* storage has tokens if hasSelect or hasQueuedContent is true. */ |
1446 | if(hasSelect | hasQueuedContent) |
1447 | *to += storage; |
1448 | |
1449 | if(hasQueuedContent) |
1450 | { |
1451 | if(!hasSelect && !hasAs && !hasQueuedContent) |
1452 | queueToken(token: Token(T_STRING_LITERAL, QString()), to); |
1453 | else if(hasAs || hasSelect) |
1454 | queueToken(token: T_RPAREN, to); |
1455 | else |
1456 | queueToken(token: T_CURLY_RBRACE, to); |
1457 | } |
1458 | } |
1459 | |
1460 | if(variableType == VariableInstruction) |
1461 | queueToken(token: T_RETURN, to); |
1462 | else if(variableType == VariableDeclaration || variableType == GlobalParameter) |
1463 | queueToken(token: T_SEMI_COLON, to); |
1464 | } |
1465 | |
1466 | void XSLTTokenizer::startStorageOfCurrent(TokenSource::Queue *const to) |
1467 | { |
1468 | queueToken(token: T_CURRENT, to); |
1469 | queueToken(token: T_CURLY_LBRACE, to); |
1470 | } |
1471 | |
1472 | void XSLTTokenizer::endStorageOfCurrent(TokenSource::Queue *const to) |
1473 | { |
1474 | queueToken(token: T_CURLY_RBRACE, to); |
1475 | } |
1476 | |
1477 | void XSLTTokenizer::queueNamespaceDeclarations(TokenSource::Queue *const to, |
1478 | QStack<Token> *const queueOnExit, |
1479 | const bool isDeclaration) |
1480 | { |
1481 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
1482 | Q_ASSERT_X(isDeclaration || queueOnExit, |
1483 | Q_FUNC_INFO, |
1484 | "If isDeclaration is false, queueOnExit must be passed." ); |
1485 | |
1486 | const QXmlStreamNamespaceDeclarations nss(namespaceDeclarations()); |
1487 | |
1488 | for(int i = 0; i < nss.count(); ++i) |
1489 | { |
1490 | const QXmlStreamNamespaceDeclaration &at = nss.at(i); |
1491 | queueToken(token: T_DECLARE, to); |
1492 | queueToken(token: T_NAMESPACE, to); |
1493 | queueToken(token: Token(T_NCNAME, at.prefix().toString()), to); |
1494 | queueToken(token: T_G_EQ, to); |
1495 | queueToken(token: Token(T_STRING_LITERAL, at.namespaceUri().toString()), to); |
1496 | |
1497 | if(isDeclaration) |
1498 | { |
1499 | queueToken(token: T_INTERNAL, to); |
1500 | queueToken(token: T_SEMI_COLON, to); |
1501 | } |
1502 | else |
1503 | { |
1504 | queueToken(token: T_CURLY_LBRACE, to); |
1505 | queueOnExit->push(t: T_CURLY_RBRACE); |
1506 | } |
1507 | } |
1508 | } |
1509 | |
1510 | bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to, |
1511 | const bool initialAdvance, |
1512 | const bool queueEmptyOnEmpty) |
1513 | { |
1514 | QStack<Token> onExitTokens; |
1515 | return insideSequenceConstructor(to, queueOnExit&: onExitTokens, initialAdvance, queueEmptyOnEmpty); |
1516 | } |
1517 | |
1518 | bool XSLTTokenizer::insideSequenceConstructor(TokenSource::Queue *const to, |
1519 | QStack<Token> &onExitTokens, |
1520 | const bool initialAdvance, |
1521 | const bool queueEmptyOnEmpty) |
1522 | { |
1523 | bool effectiveInitialAdvance = initialAdvance; |
1524 | bool hasWrittenExpression = false; |
1525 | |
1526 | /* Buffer which all text nodes, that might be split up by comments, |
1527 | * processing instructions and CDATA sections, are appended to. */ |
1528 | QString characters; |
1529 | |
1530 | while(!atEnd()) |
1531 | { |
1532 | if(effectiveInitialAdvance) |
1533 | readNext(); |
1534 | else |
1535 | effectiveInitialAdvance = true; |
1536 | |
1537 | switch(tokenType()) |
1538 | { |
1539 | case QXmlStreamReader::StartElement: |
1540 | { |
1541 | queueTextConstructor(chars&: characters, hasWrittenExpression, to); |
1542 | handleXMLBase(to, queueOnExit: &onExitTokens); |
1543 | |
1544 | pushState(nextState: InsideSequenceConstructor); |
1545 | |
1546 | commencingExpression(hasWrittenExpression, to); |
1547 | |
1548 | if(isXSLT()) |
1549 | { |
1550 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: true); |
1551 | handleStandardAttributes(isXSLTElement: true); |
1552 | validateElement(); |
1553 | |
1554 | queueNamespaceDeclarations(to, queueOnExit: &onExitTokens); |
1555 | |
1556 | switch(currentElementName()) |
1557 | { |
1558 | case If: |
1559 | { |
1560 | queueToken(token: T_IF, to); |
1561 | queueToken(token: T_LPAREN, to); |
1562 | |
1563 | queueExpression(expr: readAttribute(localName: QLatin1String("test" )), to); |
1564 | queueToken(token: T_RPAREN, to); |
1565 | queueToken(token: T_THEN, to); |
1566 | |
1567 | queueToken(token: T_LPAREN, to); |
1568 | pushState(nextState: InsideSequenceConstructor); |
1569 | insideSequenceConstructor(to); |
1570 | |
1571 | break; |
1572 | } |
1573 | case Choose: |
1574 | { |
1575 | insideChoose(to); |
1576 | break; |
1577 | } |
1578 | case ValueOf: |
1579 | { |
1580 | /* We generate a computed text node constructor. */ |
1581 | queueToken(token: T_TEXT, to); |
1582 | queueToken(token: T_CURLY_LBRACE, to); |
1583 | |
1584 | queueSimpleContentConstructor(code: ReportContext::XTSE0870, emptynessAllowed: true, to, |
1585 | selectOnlyFirst: !hasAttribute(localName: QLatin1String("separator" )) && m_processingMode.top() == BackwardsCompatible); |
1586 | queueToken(token: T_CURLY_RBRACE, to); |
1587 | break; |
1588 | } |
1589 | case Sequence: |
1590 | { |
1591 | queueExpression(expr: readAttribute(localName: QLatin1String("select" )), to); |
1592 | parseFallbacksOnly(); |
1593 | break; |
1594 | } |
1595 | case Text: |
1596 | { |
1597 | queueToken(token: T_TEXT, to); |
1598 | queueToken(token: T_CURLY_LBRACE, to); |
1599 | |
1600 | queueToken(token: Token(T_STRING_LITERAL, readElementText()), to); |
1601 | queueToken(token: T_CURLY_RBRACE, to); |
1602 | break; |
1603 | } |
1604 | case Variable: |
1605 | { |
1606 | queueVariableDeclaration(variableType: VariableInstruction, to); |
1607 | |
1608 | /* We wrap the children in parantheses since we may |
1609 | * queue several expressions using the comma operator, |
1610 | * and in that case the let-binding is only in-scope |
1611 | * for the first expression. */ |
1612 | queueToken(token: T_LPAREN, to); |
1613 | |
1614 | /* We don't want a comma outputted, we're expecting an |
1615 | * expression now. */ |
1616 | hasWrittenExpression = false; |
1617 | |
1618 | onExitTokens.push(t: T_RPAREN); |
1619 | |
1620 | break; |
1621 | } |
1622 | case CallTemplate: |
1623 | { |
1624 | queueToken(token: T_CALL_TEMPLATE, to); |
1625 | queueToken(token: Token(T_QNAME, readAttribute(localName: QLatin1String("name" ))), to); |
1626 | queueToken(token: T_LPAREN, to); |
1627 | queueWithParams(parentName: CallTemplate, to); |
1628 | queueToken(token: T_RPAREN, to); |
1629 | break; |
1630 | } |
1631 | case ForEach: |
1632 | { |
1633 | queueExpression(expr: readAttribute(localName: QLatin1String("select" )), to); |
1634 | queueToken(token: T_MAP, to); |
1635 | pushState(nextState: InsideSequenceConstructor); |
1636 | |
1637 | TokenSource::Queue sorts; |
1638 | queueSorting(oneSortRequired: false, to: &sorts); |
1639 | |
1640 | |
1641 | if(sorts.isEmpty()) |
1642 | { |
1643 | startStorageOfCurrent(to); |
1644 | insideSequenceConstructor(to, initialAdvance: false); |
1645 | endStorageOfCurrent(to); |
1646 | } |
1647 | else |
1648 | { |
1649 | queueToken(token: T_SORT, to); |
1650 | *to += sorts; |
1651 | queueToken(token: T_RETURN, to); |
1652 | startStorageOfCurrent(to); |
1653 | insideSequenceConstructor(to, initialAdvance: false); |
1654 | endStorageOfCurrent(to); |
1655 | queueToken(token: T_END_SORT, to); |
1656 | } |
1657 | |
1658 | break; |
1659 | } |
1660 | case XSLTTokenLookup::Comment: |
1661 | { |
1662 | queueToken(token: T_COMMENT, to); |
1663 | queueToken(token: T_INTERNAL, to); |
1664 | queueToken(token: T_CURLY_LBRACE, to); |
1665 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE0940, emptynessAllowed: true, to); |
1666 | queueToken(token: T_CURLY_RBRACE, to); |
1667 | break; |
1668 | } |
1669 | case CopyOf: |
1670 | { |
1671 | queueExpression(expr: readAttribute(localName: QLatin1String("select" )), to); |
1672 | // TODO |
1673 | |
1674 | if(readNext() == QXmlStreamReader::EndElement) |
1675 | break; |
1676 | else |
1677 | { |
1678 | error(message: QtXmlPatterns::tr(sourceText: "Element %1 cannot have children." ).arg(a: formatKeyword(keyword: QLatin1String("copy-of" ))), |
1679 | code: ReportContext::XTSE0010); |
1680 | } |
1681 | break; |
1682 | } |
1683 | case AnalyzeString: |
1684 | { |
1685 | // TODO |
1686 | skipSubTree(); |
1687 | break; |
1688 | } |
1689 | case ResultDocument: |
1690 | { |
1691 | // TODO |
1692 | pushState(nextState: InsideSequenceConstructor); |
1693 | insideSequenceConstructor(to); |
1694 | break; |
1695 | } |
1696 | case Copy: |
1697 | { |
1698 | /* We translate: |
1699 | * <xsl:copy>expr</xsl:copy> |
1700 | * into: |
1701 | * |
1702 | * let $body := expr |
1703 | * return |
1704 | * if(self::element()) then |
1705 | * element internal {node-name()} {$body} |
1706 | * else if(self::document-node()) then |
1707 | * document internal {$body} |
1708 | * else (: This includes comments, processing-instructions, |
1709 | * attributes, and comments. :) |
1710 | * . |
1711 | * |
1712 | * TODO node identity is the same as the old node. |
1713 | * TODO namespace bindings are lost when elements are constructed |
1714 | */ |
1715 | |
1716 | /* let $body := expr */ |
1717 | queueToken(token: T_LET, to); |
1718 | queueToken(token: T_INTERNAL, to); |
1719 | queueToken(token: T_DOLLAR, to); |
1720 | queueToken(token: Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
1721 | queueToken(token: T_ASSIGN, to); |
1722 | queueToken(token: T_LPAREN, to); |
1723 | pushState(nextState: InsideSequenceConstructor); |
1724 | /* Don't queue an empty sequence, we want the dot. */ |
1725 | insideSequenceConstructor(to); |
1726 | queueToken(token: T_RPAREN, to); |
1727 | queueToken(token: T_RETURN, to); |
1728 | |
1729 | /* if(self::element()) then */ |
1730 | queueToken(token: T_IF, to); |
1731 | queueToken(token: T_LPAREN, to); |
1732 | queueToken(token: T_SELF, to); |
1733 | queueToken(token: T_COLONCOLON, to); |
1734 | queueToken(token: T_ELEMENT, to); |
1735 | queueToken(token: T_LPAREN, to); |
1736 | queueToken(token: T_RPAREN, to); |
1737 | queueToken(token: T_RPAREN, to); |
1738 | queueToken(token: T_THEN, to); |
1739 | |
1740 | /* element internal {node-name()} {$body} */ |
1741 | queueToken(token: T_ELEMENT, to); |
1742 | queueToken(token: T_INTERNAL, to); |
1743 | queueToken(token: T_CURLY_LBRACE, to); |
1744 | queueToken(token: Token(T_NCNAME, QLatin1String("node-name" )), to); // TODO what if the default ns changes? |
1745 | queueToken(token: T_LPAREN, to); |
1746 | queueToken(token: T_DOT, to); |
1747 | queueToken(token: T_RPAREN, to); |
1748 | queueToken(token: T_CURLY_RBRACE, to); |
1749 | queueToken(token: T_CURLY_LBRACE, to); |
1750 | queueToken(token: T_DOLLAR, to); |
1751 | queueToken(token: Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
1752 | queueToken(token: T_CURLY_RBRACE, to); |
1753 | |
1754 | /* else if(self::document-node()) then */ |
1755 | queueToken(token: T_ELSE, to); |
1756 | queueToken(token: T_IF, to); |
1757 | queueToken(token: T_LPAREN, to); |
1758 | queueToken(token: T_SELF, to); |
1759 | queueToken(token: T_COLONCOLON, to); |
1760 | queueToken(token: T_DOCUMENT_NODE, to); |
1761 | queueToken(token: T_LPAREN, to); |
1762 | queueToken(token: T_RPAREN, to); |
1763 | queueToken(token: T_RPAREN, to); |
1764 | queueToken(token: T_THEN, to); |
1765 | |
1766 | /* document internal {$body} */ |
1767 | queueToken(token: T_DOCUMENT, to); |
1768 | queueToken(token: T_INTERNAL, to); |
1769 | queueToken(token: T_CURLY_LBRACE, to); |
1770 | queueToken(token: T_DOLLAR, to); |
1771 | queueToken(token: Token(T_NCNAME, QString(QLatin1Char('b'))), to); // TODO we need an internal name |
1772 | queueToken(token: T_CURLY_RBRACE, to); |
1773 | |
1774 | /* else . */ |
1775 | queueToken(token: T_ELSE, to); |
1776 | queueToken(token: T_DOT, to); |
1777 | |
1778 | break; |
1779 | } |
1780 | case XSLTTokenLookup::ProcessingInstruction: |
1781 | { |
1782 | queueToken(token: T_PROCESSING_INSTRUCTION, to); |
1783 | queueToken(token: T_CURLY_LBRACE, to); |
1784 | queueAVT(expr: readAttribute(localName: QLatin1String("name" )), to); |
1785 | queueToken(token: T_CURLY_RBRACE, to); |
1786 | queueToken(token: T_CURLY_LBRACE, to); |
1787 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE0880, emptynessAllowed: true, to); |
1788 | queueToken(token: T_CURLY_RBRACE, to); |
1789 | break; |
1790 | } |
1791 | case Document: |
1792 | { |
1793 | handleValidationAttributes(isLRE: false); |
1794 | |
1795 | // TODO base-URI |
1796 | queueToken(token: T_DOCUMENT, to); |
1797 | queueToken(token: T_INTERNAL, to); |
1798 | queueToken(token: T_CURLY_LBRACE, to); |
1799 | pushState(nextState: InsideSequenceConstructor); |
1800 | insideSequenceConstructor(to); |
1801 | queueToken(token: T_CURLY_RBRACE, to); |
1802 | break; |
1803 | } |
1804 | case Element: |
1805 | { |
1806 | handleValidationAttributes(isLRE: false); |
1807 | |
1808 | // TODO base-URI |
1809 | queueToken(token: T_ELEMENT, to); |
1810 | queueToken(token: T_INTERNAL, to); |
1811 | |
1812 | /* The name. */ |
1813 | queueToken(token: T_CURLY_LBRACE, to); |
1814 | // TODO only strings allowed, not qname values. |
1815 | queueAVT(expr: readAttribute(localName: QLatin1String("name" )), to); |
1816 | queueToken(token: T_CURLY_RBRACE, to); |
1817 | |
1818 | /* The sequence constructor. */ |
1819 | queueToken(token: T_CURLY_LBRACE, to); |
1820 | pushState(nextState: InsideSequenceConstructor); |
1821 | insideSequenceConstructor(to); |
1822 | queueToken(token: T_CURLY_RBRACE, to); |
1823 | break; |
1824 | } |
1825 | case Attribute: |
1826 | { |
1827 | handleValidationAttributes(isLRE: false); |
1828 | |
1829 | // TODO base-URI |
1830 | queueToken(token: T_ATTRIBUTE, to); |
1831 | queueToken(token: T_INTERNAL, to); |
1832 | |
1833 | /* The name. */ |
1834 | queueToken(token: T_CURLY_LBRACE, to); |
1835 | // TODO only strings allowed, not qname values. |
1836 | queueAVT(expr: readAttribute(localName: QLatin1String("name" )), to); |
1837 | queueToken(token: T_CURLY_RBRACE, to); |
1838 | |
1839 | /* The sequence constructor. */ |
1840 | queueToken(token: T_CURLY_LBRACE, to); |
1841 | queueSimpleContentConstructor(code: ReportContext::XTSE0840, |
1842 | emptynessAllowed: true, to); |
1843 | queueToken(token: T_CURLY_RBRACE, to); |
1844 | break; |
1845 | } |
1846 | case Namespace: |
1847 | { |
1848 | queueToken(token: T_NAMESPACE, to); |
1849 | |
1850 | /* The name. */ |
1851 | queueToken(token: T_CURLY_LBRACE, to); |
1852 | queueAVT(expr: readAttribute(localName: QLatin1String("name" )), to); |
1853 | queueToken(token: T_CURLY_RBRACE, to); |
1854 | |
1855 | /* The sequence constructor. */ |
1856 | queueToken(token: T_CURLY_LBRACE, to); |
1857 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE0910, |
1858 | emptynessAllowed: false, to); |
1859 | queueToken(token: T_CURLY_RBRACE, to); |
1860 | break; |
1861 | } |
1862 | case PerformSort: |
1863 | { |
1864 | /* For: |
1865 | * <xsl:perform-sort select="$in"> |
1866 | * <xsl:sort select="@key"/> |
1867 | * </xsl:perform-sort> |
1868 | * |
1869 | * we generate: |
1870 | * |
1871 | * $in map sort order by @key |
1872 | * return . |
1873 | * end_sort |
1874 | */ |
1875 | |
1876 | /* In XQuery, the sort keys appear after the expression |
1877 | * supplying the initial sequence, while in |
1878 | * xsl:perform-sort, if a sequence constructor is used, |
1879 | * they appear in the opposite order. Hence, we need to |
1880 | * reorder it. */ |
1881 | |
1882 | /* We store the attributes of xsl:perform-sort, before |
1883 | * queueSorting() advances the reader. */ |
1884 | const QXmlStreamAttributes atts(m_currentAttributes); |
1885 | |
1886 | TokenSource::Queue sorts; |
1887 | queueSorting(oneSortRequired: true, to: &sorts); |
1888 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE1040, |
1889 | emptynessAllowed: true, |
1890 | to, |
1891 | attsP: &atts); |
1892 | /* queueSelectOrSequenceConstructor() positions us on EndElement. */ |
1893 | effectiveInitialAdvance = false; |
1894 | queueToken(token: T_MAP, to); |
1895 | queueToken(token: T_SORT, to); |
1896 | *to += sorts; |
1897 | queueToken(token: T_RETURN, to); |
1898 | queueToken(token: T_DOT, to); |
1899 | queueToken(token: T_END_SORT, to); |
1900 | |
1901 | break; |
1902 | } |
1903 | case Message: |
1904 | { |
1905 | // TODO |
1906 | queueEmptySequence(to); |
1907 | skipSubTree(); |
1908 | break; |
1909 | } |
1910 | case ApplyTemplates: |
1911 | { |
1912 | if(hasAttribute(localName: QLatin1String("select" ))) |
1913 | queueExpression(expr: readAttribute(localName: QLatin1String("select" )), to); |
1914 | else |
1915 | { |
1916 | queueToken(token: T_CHILD, to); |
1917 | queueToken(token: T_COLONCOLON, to); |
1918 | queueToken(token: T_NODE, to); |
1919 | queueToken(token: T_LPAREN, to); |
1920 | queueToken(token: T_RPAREN, to); |
1921 | } |
1922 | |
1923 | bool hasMode = hasAttribute(localName: QLatin1String("mode" )); |
1924 | QString mode; |
1925 | |
1926 | if(hasMode) |
1927 | mode = readAttribute(localName: QLatin1String("mode" )).trimmed(); |
1928 | |
1929 | queueToken(token: T_FOR_APPLY_TEMPLATE, to); |
1930 | |
1931 | TokenSource::Queue sorts; |
1932 | queueSorting(oneSortRequired: false, to: &sorts, speciallyTreatWhitespace: true); |
1933 | |
1934 | if(!sorts.isEmpty()) |
1935 | { |
1936 | queueToken(token: T_SORT, to); |
1937 | *to += sorts; |
1938 | queueToken(token: T_RETURN, to); |
1939 | } |
1940 | |
1941 | queueToken(token: T_APPLY_TEMPLATE, to); |
1942 | |
1943 | if(hasMode) |
1944 | { |
1945 | queueToken(token: T_MODE, to); |
1946 | queueToken(token: Token(mode.startsWith(c: QLatin1Char('#')) ? T_NCNAME : T_QNAME, mode), to); |
1947 | } |
1948 | |
1949 | queueToken(token: T_LPAREN, to); |
1950 | queueWithParams(parentName: ApplyTemplates, to, initialAdvance: false); |
1951 | queueToken(token: T_RPAREN, to); |
1952 | |
1953 | if(!sorts.isEmpty()) |
1954 | queueToken(token: T_END_SORT, to); |
1955 | |
1956 | break; |
1957 | } |
1958 | default: |
1959 | unexpectedContent(); |
1960 | } |
1961 | } |
1962 | else |
1963 | { |
1964 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: true); |
1965 | handleStandardAttributes(isXSLTElement: false); |
1966 | handleValidationAttributes(isLRE: false); |
1967 | |
1968 | /* We're generating an element constructor. */ |
1969 | queueNamespaceDeclarations(to, queueOnExit: &onExitTokens); // TODO same in the isXSLT() branch |
1970 | queueToken(token: T_ELEMENT, to); |
1971 | queueToken(token: T_INTERNAL, to); |
1972 | queueToken(token: Token(T_QNAME, qualifiedName().toString()), to); |
1973 | queueToken(token: T_CURLY_LBRACE, to); |
1974 | const int len = m_currentAttributes.count(); |
1975 | |
1976 | for(int i = 0; i < len; ++i) |
1977 | { |
1978 | const QXmlStreamAttribute &at = m_currentAttributes.at(i); |
1979 | |
1980 | /* We don't want to generate constructors for XSL-T attributes. */ |
1981 | if(at.namespaceUri() == CommonNamespaces::XSLT) |
1982 | continue; |
1983 | |
1984 | queueToken(token: T_ATTRIBUTE, to); |
1985 | queueToken(token: T_INTERNAL, to); |
1986 | |
1987 | queueToken(token: Token(at.prefix().isEmpty() ? T_NCNAME : T_QNAME, at.qualifiedName().toString()), to); |
1988 | queueToken(token: T_CURLY_LBRACE, to); |
1989 | queueAVT(expr: at.value().toString(), to); |
1990 | queueToken(token: T_CURLY_RBRACE, to); |
1991 | queueToken(token: T_COMMA, to); |
1992 | } |
1993 | |
1994 | pushState(nextState: InsideSequenceConstructor); |
1995 | insideSequenceConstructor(to); |
1996 | Q_ASSERT(tokenType() == QXmlStreamReader::EndElement || hasError()); |
1997 | } |
1998 | |
1999 | continue; |
2000 | } |
2001 | case QXmlStreamReader::EndElement: |
2002 | { |
2003 | queueTextConstructor(chars&: characters, hasWrittenExpression, to); |
2004 | leaveState(); |
2005 | |
2006 | if(!hasWrittenExpression && queueEmptyOnEmpty) |
2007 | queueEmptySequence(to); |
2008 | |
2009 | queueOnExit(source&: onExitTokens, destination: to); |
2010 | |
2011 | if(isXSLT()) |
2012 | { |
2013 | Q_ASSERT(!isElement(Sequence)); |
2014 | |
2015 | switch(currentElementName()) |
2016 | { |
2017 | /* Fallthrough all these. */ |
2018 | case When: |
2019 | case Choose: |
2020 | case ForEach: |
2021 | case Otherwise: |
2022 | case PerformSort: |
2023 | case Message: |
2024 | case ResultDocument: |
2025 | case Copy: |
2026 | case CallTemplate: |
2027 | case Text: |
2028 | case ValueOf: |
2029 | { |
2030 | hasWrittenExpression = true; |
2031 | break; |
2032 | } |
2033 | case If: |
2034 | { |
2035 | queueToken(token: T_RPAREN, to); |
2036 | queueToken(token: T_ELSE, to); |
2037 | queueEmptySequence(to); |
2038 | break; |
2039 | } |
2040 | case Function: |
2041 | { |
2042 | queueToken(token: T_CURLY_RBRACE, to); |
2043 | queueToken(token: T_SEMI_COLON, to); |
2044 | break; |
2045 | } |
2046 | case Template: |
2047 | { |
2048 | endStorageOfCurrent(to: &m_tokenSource); |
2049 | /* TODO, fallthrough to Function. */ |
2050 | queueToken(token: T_CURLY_RBRACE, to); |
2051 | queueToken(token: T_SEMI_COLON, to); |
2052 | break; |
2053 | } |
2054 | default: |
2055 | ; |
2056 | } |
2057 | } |
2058 | else |
2059 | { |
2060 | /* We're closing a direct element constructor. */ |
2061 | hasWrittenExpression = true; |
2062 | queueToken(token: T_CURLY_RBRACE, to); |
2063 | } |
2064 | |
2065 | return hasWrittenExpression; |
2066 | } |
2067 | case QXmlStreamReader::ProcessingInstruction: |
2068 | case QXmlStreamReader::Comment: |
2069 | /* We do nothing, we just ignore them. */ |
2070 | continue; |
2071 | case QXmlStreamReader::Characters: |
2072 | { |
2073 | if(whitespaceToSkip()) |
2074 | continue; |
2075 | else |
2076 | { |
2077 | characters += text().toString(); |
2078 | continue; |
2079 | } |
2080 | } |
2081 | default: |
2082 | ; |
2083 | } |
2084 | } |
2085 | |
2086 | leaveState(); |
2087 | return hasWrittenExpression; |
2088 | } |
2089 | |
2090 | bool XSLTTokenizer::isStylesheetElement() const |
2091 | { |
2092 | Q_ASSERT(isXSLT()); |
2093 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement || |
2094 | tokenType() == QXmlStreamReader::EndElement); |
2095 | |
2096 | const NodeName name = currentElementName(); |
2097 | return name == Stylesheet || name == Transform; |
2098 | } |
2099 | |
2100 | void XSLTTokenizer::skipBodyOfParam(const ReportContext::ErrorCode code) |
2101 | { |
2102 | Q_ASSERT(isXSLT()); |
2103 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
2104 | const NodeName name(currentElementName()); |
2105 | |
2106 | if(skipSubTree()) |
2107 | { |
2108 | error(message: QtXmlPatterns::tr(sourceText: "Element %1 cannot have a sequence constructor." ) |
2109 | .arg(a: formatKeyword(keyword: toString(token: name))), |
2110 | code); |
2111 | } |
2112 | } |
2113 | |
2114 | void XSLTTokenizer::queueWithParams(const XSLTTokenLookup::NodeName parentName, |
2115 | TokenSource::Queue *const to, |
2116 | const bool initialAdvance) |
2117 | { |
2118 | Q_ASSERT(parentName == ApplyTemplates || parentName == CallTemplate); |
2119 | |
2120 | bool effectiveInitialAdvance = initialAdvance; |
2121 | bool hasQueuedParam = false; |
2122 | |
2123 | while(!atEnd()) |
2124 | { |
2125 | if(effectiveInitialAdvance) |
2126 | readNext(); |
2127 | else |
2128 | effectiveInitialAdvance = true; |
2129 | |
2130 | switch(tokenType()) |
2131 | { |
2132 | case QXmlStreamReader::StartElement: |
2133 | { |
2134 | if(hasQueuedParam) |
2135 | queueToken(token: T_COMMA, to); |
2136 | |
2137 | if(isXSLT() && isElement(name: WithParam)) |
2138 | { |
2139 | if(hasAttribute(localName: QLatin1String("tunnel" )) && attributeYesNo(localName: QLatin1String("tunnel" ))) |
2140 | queueToken(token: T_TUNNEL, to); |
2141 | |
2142 | queueVariableDeclaration(variableType: WithParamVariable, to); |
2143 | hasQueuedParam = true; |
2144 | continue; |
2145 | } |
2146 | else |
2147 | unexpectedContent(); |
2148 | Q_FALLTHROUGH(); |
2149 | } |
2150 | case QXmlStreamReader::EndElement: |
2151 | { |
2152 | if(isElement(name: parentName)) |
2153 | return; |
2154 | else |
2155 | continue; |
2156 | } |
2157 | case QXmlStreamReader::ProcessingInstruction: |
2158 | case QXmlStreamReader::Comment: |
2159 | continue; |
2160 | case QXmlStreamReader::Characters: |
2161 | if(whitespaceToSkip()) |
2162 | continue; |
2163 | else |
2164 | return; |
2165 | default: |
2166 | unexpectedContent(); |
2167 | } |
2168 | } |
2169 | unexpectedContent(); |
2170 | } |
2171 | |
2172 | void XSLTTokenizer::queueParams(const XSLTTokenLookup::NodeName parentName, |
2173 | TokenSource::Queue *const to) |
2174 | { |
2175 | bool hasQueuedParam = false; |
2176 | |
2177 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
2178 | |
2179 | while(!atEnd()) |
2180 | { |
2181 | switch(readNext()) |
2182 | { |
2183 | case QXmlStreamReader::StartElement: |
2184 | { |
2185 | if(isXSLT() && isElement(name: Param)) |
2186 | { |
2187 | if(hasQueuedParam) |
2188 | queueToken(token: T_COMMA, to); |
2189 | |
2190 | validateElement(); |
2191 | |
2192 | if(parentName == Function && m_currentAttributes.hasAttribute(qualifiedName: QLatin1String("select" ))) |
2193 | { |
2194 | error(message: QtXmlPatterns::tr(sourceText: "The attribute %1 cannot appear on %2, when it is a child of %3." ) |
2195 | .arg(args: formatKeyword(keyword: QLatin1String("select" )), |
2196 | args: formatKeyword(keyword: QLatin1String("param" )), |
2197 | args: formatKeyword(keyword: QLatin1String("function" ))), |
2198 | code: ReportContext::XTSE0760); |
2199 | } |
2200 | |
2201 | if(parentName == Function && m_currentAttributes.hasAttribute(qualifiedName: QLatin1String("required" ))) |
2202 | { |
2203 | error(message: QtXmlPatterns::tr(sourceText: "The attribute %1 cannot appear on %2, when it is a child of %3." ) |
2204 | .arg(args: formatKeyword(keyword: QLatin1String("required" )), |
2205 | args: formatKeyword(keyword: QLatin1String("param" )), |
2206 | args: formatKeyword(keyword: QLatin1String("function" ))), |
2207 | code: ReportContext::XTSE0010); |
2208 | } |
2209 | |
2210 | const bool hasTunnel = m_currentAttributes.hasAttribute(qualifiedName: QLatin1String("tunnel" )); |
2211 | const bool isTunnel = hasTunnel ? attributeYesNo(localName: QLatin1String("tunnel" )) : false; |
2212 | |
2213 | if(isTunnel) |
2214 | { |
2215 | if(parentName == Function) |
2216 | { |
2217 | /* See W3C public report 5650: http://www.w3.org/Bugs/Public/show_bug.cgi?id=5650 */ |
2218 | error(message: QtXmlPatterns::tr(sourceText: "A parameter in a function cannot be declared to be a tunnel." ), |
2219 | code: ReportContext::XTSE0010); |
2220 | } |
2221 | else |
2222 | queueToken(token: T_TUNNEL, to); |
2223 | } |
2224 | |
2225 | hasQueuedParam = true; |
2226 | queueVariableDeclaration(variableType: parentName == Function ? FunctionParameter : TemplateParameter, to); |
2227 | continue; |
2228 | } |
2229 | else |
2230 | return; |
2231 | } |
2232 | case QXmlStreamReader::Characters: |
2233 | { |
2234 | if(whitespaceToSkip()) |
2235 | continue; |
2236 | Q_FALLTHROUGH(); |
2237 | } |
2238 | case QXmlStreamReader::EndElement: |
2239 | return; |
2240 | default: |
2241 | ; |
2242 | } |
2243 | } |
2244 | } |
2245 | |
2246 | bool XSLTTokenizer::skipSubTree(const bool exitOnContent) |
2247 | { |
2248 | bool hasContent = false; |
2249 | int depth = 0; |
2250 | |
2251 | while(!atEnd()) |
2252 | { |
2253 | switch(readNext()) |
2254 | { |
2255 | case QXmlStreamReader::Characters: |
2256 | { |
2257 | if(whitespaceToSkip()) |
2258 | continue; |
2259 | else |
2260 | { |
2261 | hasContent = true; |
2262 | if(exitOnContent) |
2263 | return true; |
2264 | |
2265 | break; |
2266 | } |
2267 | } |
2268 | case QXmlStreamReader::StartElement: |
2269 | { |
2270 | hasContent = true; |
2271 | if(exitOnContent) |
2272 | return true; |
2273 | |
2274 | ++depth; |
2275 | break; |
2276 | } |
2277 | case QXmlStreamReader::EndElement: |
2278 | { |
2279 | --depth; |
2280 | break; |
2281 | } |
2282 | default: |
2283 | continue; |
2284 | } |
2285 | |
2286 | if(depth == -1) |
2287 | return hasContent; |
2288 | } |
2289 | |
2290 | checkForParseError(); |
2291 | return hasContent; |
2292 | } |
2293 | |
2294 | void XSLTTokenizer::parseFallbacksOnly() |
2295 | { |
2296 | Q_ASSERT(isXSLT()); |
2297 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
2298 | |
2299 | skipSubTree(); |
2300 | Q_ASSERT(tokenType() == QXmlStreamReader::EndElement); |
2301 | } |
2302 | |
2303 | void XSLTTokenizer::insideAttributeSet() |
2304 | { |
2305 | while(!atEnd()) |
2306 | { |
2307 | switch(readNext()) |
2308 | { |
2309 | case QXmlStreamReader::StartElement: |
2310 | { |
2311 | if(isXSLT() && isElement(name: AttributeSet)) |
2312 | { |
2313 | // TODO |
2314 | skipSubTree(); |
2315 | } |
2316 | else |
2317 | unexpectedContent(); |
2318 | } |
2319 | case QXmlStreamReader::EndElement: |
2320 | return; |
2321 | case QXmlStreamReader::ProcessingInstruction: |
2322 | case QXmlStreamReader::Comment: |
2323 | continue; |
2324 | case QXmlStreamReader::Characters: |
2325 | if(whitespaceToSkip()) |
2326 | continue; |
2327 | Q_FALLTHROUGH(); |
2328 | default: |
2329 | unexpectedContent(); |
2330 | } |
2331 | } |
2332 | unexpectedContent(); |
2333 | } |
2334 | |
2335 | void XSLTTokenizer::insideStylesheetModule() |
2336 | { |
2337 | while(!atEnd()) |
2338 | { |
2339 | switch(readNext()) |
2340 | { |
2341 | case QXmlStreamReader::StartElement: |
2342 | { |
2343 | if(isXSLT()) |
2344 | { |
2345 | handleStandardAttributes(isXSLTElement: true); |
2346 | handleXSLTVersion(to: 0, queueOnExit: 0, isXSLTElement: true, atts: 0, generateCode: false); |
2347 | validateElement(); |
2348 | |
2349 | /* Handle the various declarations. */ |
2350 | switch(currentElementName()) |
2351 | { |
2352 | case Template: |
2353 | insideTemplate(); |
2354 | break; |
2355 | case Function: |
2356 | insideFunction(); |
2357 | break; |
2358 | case Variable: |
2359 | queueVariableDeclaration(variableType: VariableDeclaration, to: &m_tokenSource); |
2360 | break; |
2361 | case Param: |
2362 | queueVariableDeclaration(variableType: GlobalParameter, to: &m_tokenSource); |
2363 | break; |
2364 | case ImportSchema: |
2365 | { |
2366 | error(message: QtXmlPatterns::tr(sourceText: "This processor is not Schema-aware and " |
2367 | "therefore %1 cannot be used." ).arg(a: formatKeyword(keyword: toString(token: ImportSchema))), |
2368 | code: ReportContext::XTSE1660); |
2369 | break; |
2370 | } |
2371 | case Output: |
2372 | { |
2373 | // TODO |
2374 | skipSubTree(); |
2375 | break; |
2376 | } |
2377 | case StripSpace: |
2378 | case PreserveSpace: |
2379 | { |
2380 | // TODO @elements |
2381 | skipSubTree(exitOnContent: true); |
2382 | readNext(); |
2383 | |
2384 | if(!isEndElement()) |
2385 | unexpectedContent(); |
2386 | break; |
2387 | } |
2388 | case Include: |
2389 | { |
2390 | // TODO |
2391 | if(skipSubTree(exitOnContent: true)) |
2392 | unexpectedContent(); |
2393 | break; |
2394 | } |
2395 | case Import: |
2396 | { |
2397 | // TODO |
2398 | if(skipSubTree(exitOnContent: true)) |
2399 | unexpectedContent(); |
2400 | break; |
2401 | } |
2402 | case Key: |
2403 | { |
2404 | // TODO |
2405 | skipSubTree(); |
2406 | break; |
2407 | } |
2408 | case AttributeSet: |
2409 | insideAttributeSet(); |
2410 | break; |
2411 | default: |
2412 | if(m_processingMode.top() != ForwardCompatible) |
2413 | unexpectedContent(); |
2414 | } |
2415 | } |
2416 | else |
2417 | { |
2418 | /* We have a user-defined data element. See section 3.6.2. */ |
2419 | |
2420 | if(namespaceUri().isEmpty()) |
2421 | { |
2422 | error(message: QtXmlPatterns::tr(sourceText: "Top level stylesheet elements must be " |
2423 | "in a non-null namespace, which %1 isn't." ).arg(a: formatKeyword(keyword: name())), |
2424 | code: ReportContext::XTSE0130); |
2425 | } |
2426 | else |
2427 | skipSubTree(); |
2428 | } |
2429 | break; |
2430 | } |
2431 | case QXmlStreamReader::Characters: |
2432 | { |
2433 | /* Regardless of xml:space, we skip whitespace, see step 4 in |
2434 | * 4.2 Stripping Whitespace from the Stylesheet. */ |
2435 | if(isWhitespace()) |
2436 | continue; |
2437 | |
2438 | unexpectedContent(code: ReportContext::XTSE0120); |
2439 | break; |
2440 | } |
2441 | case QXmlStreamReader::EndElement: |
2442 | { |
2443 | if(isXSLT()) |
2444 | leaveState(); |
2445 | |
2446 | break; |
2447 | } |
2448 | default: |
2449 | ; |
2450 | } |
2451 | } |
2452 | checkForParseError(); |
2453 | } |
2454 | |
2455 | bool XSLTTokenizer::readToggleAttribute(const QString &localName, |
2456 | const QString &isTrue, |
2457 | const QString &isFalse, |
2458 | const QXmlStreamAttributes *const attsP) const |
2459 | { |
2460 | const QXmlStreamAttributes atts(attsP ? *attsP : m_currentAttributes); |
2461 | Q_ASSERT(atts.hasAttribute(localName)); |
2462 | const QString value(atts.value(qualifiedName: localName).toString()); |
2463 | |
2464 | if(value == isTrue) |
2465 | return true; |
2466 | else if(value == isFalse) |
2467 | return false; |
2468 | else |
2469 | { |
2470 | error(message: QtXmlPatterns::tr(sourceText: "The value for attribute %1 on element %2 must either " |
2471 | "be %3 or %4, not %5." ).arg(args: formatKeyword(keyword: localName), |
2472 | args: formatKeyword(keyword: name()), |
2473 | args: formatData(data: isTrue), |
2474 | args: formatData(data: isFalse), |
2475 | args: formatData(data: value)), |
2476 | code: ReportContext::XTSE0020); |
2477 | /* Silences a compiler warning. */ |
2478 | return false; |
2479 | } |
2480 | } |
2481 | |
2482 | int XSLTTokenizer::readAlternativeAttribute(const QHash<QString, int> &alternatives, |
2483 | const QXmlStreamAttribute &attr) const |
2484 | { |
2485 | const QString value(attr.value().toString().trimmed()); |
2486 | |
2487 | if(alternatives.contains(akey: value)) |
2488 | return alternatives[value]; |
2489 | |
2490 | error(message: QtXmlPatterns::tr(sourceText: "Attribute %1 cannot have the value %2." ) |
2491 | .arg(args: formatKeyword(keyword: attr.name().toString()), |
2492 | args: formatData(data: attr.value().toString())), |
2493 | code: ReportContext::XTSE0020); |
2494 | return 0; /* Silence compiler warning. */ |
2495 | } |
2496 | |
2497 | bool XSLTTokenizer::attributeYesNo(const QString &localName) const |
2498 | { |
2499 | return readToggleAttribute(localName, isTrue: QLatin1String("yes" ), isFalse: QLatin1String("no" )); |
2500 | } |
2501 | |
2502 | void XSLTTokenizer::queueSorting(const bool oneSortRequired, |
2503 | TokenSource::Queue *const to, |
2504 | const bool speciallyTreatWhitespace) |
2505 | { |
2506 | Q_ASSERT(tokenType() == QXmlStreamReader::StartElement); |
2507 | |
2508 | const NodeName elementName(currentElementName()); |
2509 | bool hasQueuedOneSort = false; |
2510 | |
2511 | while(!atEnd()) |
2512 | { |
2513 | switch(readNext()) |
2514 | { |
2515 | case QXmlStreamReader::EndElement: |
2516 | { |
2517 | /* Let's say we have no sequence constructor, but only |
2518 | * ignorable space. In that case we will actually loop |
2519 | * infinitely if we don't have this check. */ |
2520 | if(isXSLT()) |
2521 | { |
2522 | switch(currentElementName()) |
2523 | { |
2524 | case PerformSort: |
2525 | case ForEach: |
2526 | case ApplyTemplates: |
2527 | return; |
2528 | default: |
2529 | ; |
2530 | } |
2531 | } |
2532 | continue; |
2533 | } |
2534 | case QXmlStreamReader::StartElement: |
2535 | { |
2536 | if(isXSLT() && isElement(name: Sort)) |
2537 | { |
2538 | if(hasQueuedOneSort) |
2539 | queueToken(token: T_COMMA, to); |
2540 | |
2541 | /* sorts are by default stable. */ |
2542 | if(hasAttribute(localName: QLatin1String("stable" ))) |
2543 | { |
2544 | if(hasQueuedOneSort) |
2545 | { |
2546 | error(message: QtXmlPatterns::tr(sourceText: "The attribute %1 can only appear on " |
2547 | "the first %2 element." ).arg(args: formatKeyword(keyword: QLatin1String("stable" )), |
2548 | args: formatKeyword(keyword: QLatin1String("sort" ))), |
2549 | code: ReportContext::XTSE0020); |
2550 | } |
2551 | |
2552 | if(attributeYesNo(localName: QLatin1String("stable" ))) |
2553 | queueToken(token: T_STABLE, to); |
2554 | } |
2555 | |
2556 | if(!hasQueuedOneSort) |
2557 | { |
2558 | queueToken(token: T_ORDER, to); |
2559 | queueToken(token: T_BY, to); |
2560 | } |
2561 | |
2562 | /* We store a copy such that we can use them after |
2563 | * queueSelectOrSequenceConstructor() advances the reader. */ |
2564 | const QXmlStreamAttributes atts(m_currentAttributes); |
2565 | |
2566 | const int before = to->count(); |
2567 | |
2568 | // TODO This doesn't work as is. @data-type can be an AVT. |
2569 | if(atts.hasAttribute(qualifiedName: QLatin1String("data-type" ))) |
2570 | { |
2571 | if(readToggleAttribute(localName: QLatin1String("data-type" ), |
2572 | isTrue: QLatin1String("text" ), |
2573 | isFalse: QLatin1String("number" ), |
2574 | attsP: &atts)) |
2575 | queueToken(token: Token(T_NCNAME, QLatin1String("string" )), to); |
2576 | else |
2577 | queueToken(token: Token(T_NCNAME, QLatin1String("number" )), to); |
2578 | } |
2579 | /* We queue these parantheses for the sake of the function |
2580 | * call for attribute data-type. In the case we don't have |
2581 | * such an attribute, the parantheses are just redundant. */ |
2582 | queueToken(token: T_LPAREN, to); |
2583 | queueSelectOrSequenceConstructor(code: ReportContext::XTSE1015, |
2584 | emptynessAllowed: true, |
2585 | to, |
2586 | attsP: 0, |
2587 | queueEmptyOnEmpty: false); |
2588 | /* If neither a select attribute or a sequence constructor is supplied, |
2589 | * we're supposed to use the context item. */ |
2590 | queueToken(token: T_RPAREN, to); |
2591 | if(before == to->count()) |
2592 | queueToken(token: T_DOT, to); |
2593 | |
2594 | // TODO case-order |
2595 | // TODO lang |
2596 | |
2597 | // TODO This doesn't work as is. @order can be an AVT, and so can case-order and lang. |
2598 | if(atts.hasAttribute(qualifiedName: QLatin1String("order" )) && readToggleAttribute(localName: QLatin1String("order" ), |
2599 | isTrue: QLatin1String("descending" ), |
2600 | isFalse: QLatin1String("ascending" ), |
2601 | attsP: &atts)) |
2602 | { |
2603 | queueToken(token: T_DESCENDING, to); |
2604 | } |
2605 | else |
2606 | { |
2607 | /* This is the default. */ |
2608 | queueToken(token: T_ASCENDING, to); |
2609 | } |
2610 | |
2611 | if(atts.hasAttribute(qualifiedName: QLatin1String("collation" ))) |
2612 | { |
2613 | queueToken(token: T_INTERNAL, to); |
2614 | queueToken(token: T_COLLATION, to); |
2615 | queueAVT(expr: atts.value(qualifiedName: QLatin1String("collation" )).toString(), to); |
2616 | } |
2617 | |
2618 | hasQueuedOneSort = true; |
2619 | continue; |
2620 | } |
2621 | else |
2622 | break; |
2623 | } |
2624 | case QXmlStreamReader::Characters: |
2625 | { |
2626 | if(speciallyTreatWhitespace && isWhitespace()) |
2627 | continue; |
2628 | |
2629 | if (whitespaceToSkip()) |
2630 | continue; |
2631 | |
2632 | /* We have an instruction which is a text node, we're done. */ |
2633 | break; |
2634 | } |
2635 | case QXmlStreamReader::ProcessingInstruction: |
2636 | case QXmlStreamReader::Comment: |
2637 | continue; |
2638 | default: |
2639 | unexpectedContent(); |
2640 | |
2641 | }; |
2642 | if(oneSortRequired && !hasQueuedOneSort) |
2643 | { |
2644 | error(message: QtXmlPatterns::tr(sourceText: "At least one %1 element must appear as child of %2." ) |
2645 | .arg(args: formatKeyword(keyword: QLatin1String("sort" )), args: formatKeyword(keyword: toString(token: elementName))), |
2646 | code: ReportContext::XTSE0010); |
2647 | } |
2648 | else |
2649 | return; |
2650 | } |
2651 | checkForParseError(); |
2652 | } |
2653 | |
2654 | void XSLTTokenizer::insideFunction() |
2655 | { |
2656 | queueToken(token: T_DECLARE, to: &m_tokenSource); |
2657 | queueToken(token: T_FUNCTION, to: &m_tokenSource); |
2658 | queueToken(token: T_INTERNAL, to: &m_tokenSource); |
2659 | queueToken(token: Token(T_QNAME, readAttribute(localName: QLatin1String("name" ))), to: &m_tokenSource); |
2660 | queueToken(token: T_LPAREN, to: &m_tokenSource); |
2661 | const QString expectedType(hasAttribute(localName: QLatin1String("as" )) ? readAttribute(localName: QLatin1String("as" )): QString()); |
2662 | |
2663 | if(hasAttribute(localName: QLatin1String("override" ))) |
2664 | { |
2665 | /* We currently have no external functions, so we don't pass it on currently. */ |
2666 | attributeYesNo(localName: QLatin1String("override" )); |
2667 | } |
2668 | |
2669 | queueParams(parentName: Function, to: &m_tokenSource); |
2670 | |
2671 | queueToken(token: T_RPAREN, to: &m_tokenSource); |
2672 | |
2673 | if(!expectedType.isNull()) |
2674 | { |
2675 | queueToken(token: T_AS, to: &m_tokenSource); |
2676 | queueSequenceType(expr: expectedType); |
2677 | } |
2678 | |
2679 | QStack<Token> onExitTokens; |
2680 | handleXMLBase(to: &m_tokenSource, queueOnExit: &onExitTokens, isInstruction: true, atts: &m_currentAttributes); |
2681 | handleXSLTVersion(to: &m_tokenSource, queueOnExit: &onExitTokens, isXSLTElement: true); |
2682 | queueToken(token: T_CURLY_LBRACE, to: &m_tokenSource); |
2683 | |
2684 | pushState(nextState: InsideSequenceConstructor); |
2685 | insideSequenceConstructor(to: &m_tokenSource, onExitTokens, initialAdvance: false); |
2686 | /* We don't queue CURLY_RBRACE, because it's done in |
2687 | * insideSequenceConstructor(). */ |
2688 | } |
2689 | |
2690 | XPATHLTYPE XSLTTokenizer::currentSourceLocator() const |
2691 | { |
2692 | XPATHLTYPE retval; |
2693 | retval.first_line = lineNumber(); |
2694 | retval.first_column = columnNumber(); |
2695 | return retval; |
2696 | } |
2697 | |
2698 | QT_END_NAMESPACE |
2699 | |