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 "qxsdvalidatinginstancereader_p.h" |
41 | |
42 | #include "qabstractdatetime_p.h" |
43 | #include "qacceltreeresourceloader_p.h" |
44 | #include "qbase64binary_p.h" |
45 | #include "qboolean_p.h" |
46 | #include "qcommonnamespaces_p.h" |
47 | #include "qderivedinteger_p.h" |
48 | #include "qduration_p.h" |
49 | #include "qgenericstaticcontext_p.h" |
50 | #include "qhexbinary_p.h" |
51 | #include "qnamespaceresolver_p.h" |
52 | #include "qpatternplatform_p.h" |
53 | #include "qqnamevalue_p.h" |
54 | #include "qsourcelocationreflection_p.h" |
55 | #include "qvaluefactory_p.h" |
56 | #include "qxmlnamepool.h" |
57 | #include "qxmlquery_p.h" |
58 | #include "qxmlschema_p.h" |
59 | #include "qxsdschemahelper_p.h" |
60 | #include "qxsdschemamerger_p.h" |
61 | #include "qxsdstatemachine_p.h" |
62 | #include "qxsdstatemachinebuilder_p.h" |
63 | #include "qxsdtypechecker_p.h" |
64 | |
65 | #include "qxsdschemadebugger_p.h" |
66 | |
67 | #include <QtCore/QFile> |
68 | #include <QtXmlPatterns/QXmlQuery> |
69 | #include <QtXmlPatterns/QXmlResultItems> |
70 | |
71 | QT_BEGIN_NAMESPACE |
72 | |
73 | using namespace QPatternist; |
74 | |
75 | namespace QPatternist |
76 | { |
77 | template <> |
78 | template <> |
79 | bool XsdStateMachine<XsdTerm::Ptr>::inputEqualsTransition<QXmlName>(QXmlName name, XsdTerm::Ptr term) const |
80 | { |
81 | if (term->isElement()) { |
82 | return (XsdElement::Ptr(term)->name(namePool: m_namePool) == name); |
83 | } else if (term->isWildcard()) { |
84 | // wildcards using XsdWildcard::absentNamespace, so we have to fix that here |
85 | if (name.namespaceURI() == StandardNamespaces::empty) { |
86 | name.setNamespaceURI(m_namePool->allocateNamespace(uri: XsdWildcard::absentNamespace())); |
87 | } |
88 | |
89 | return XsdSchemaHelper::wildcardAllowsExpandedName(name, wildcard: XsdWildcard::Ptr(term), namePool: m_namePool); |
90 | } |
91 | |
92 | return false; |
93 | } |
94 | } |
95 | |
96 | XsdValidatingInstanceReader::XsdValidatingInstanceReader(XsdValidatedXmlNodeModel *model, const QUrl &documentUri, const XsdSchemaContext::Ptr &context) |
97 | : XsdInstanceReader(model, context) |
98 | , m_model(model) |
99 | , m_namePool(m_context->namePool()) |
100 | , m_xsiNilName(m_namePool->allocateQName(uri: CommonNamespaces::XSI, localName: QLatin1String("nil" ))) |
101 | , m_xsiTypeName(m_namePool->allocateQName(uri: CommonNamespaces::XSI, localName: QLatin1String("type" ))) |
102 | , m_xsiSchemaLocationName(m_namePool->allocateQName(uri: CommonNamespaces::XSI, localName: QLatin1String("schemaLocation" ))) |
103 | , m_xsiNoNamespaceSchemaLocationName(m_namePool->allocateQName(uri: CommonNamespaces::XSI, localName: QLatin1String("noNamespaceSchemaLocation" ))) |
104 | , m_documentUri(documentUri) |
105 | { |
106 | m_idRefsType = m_context->schemaTypeFactory()->createSchemaType(name: m_namePool->allocateQName(uri: CommonNamespaces::WXS, localName: QLatin1String("IDREFS" ))); |
107 | } |
108 | |
109 | void XsdValidatingInstanceReader::addSchema(const XsdSchema::Ptr &schema, const QUrl &locationUrl) |
110 | { |
111 | if (!m_mergedSchemas.contains(akey: locationUrl)) { |
112 | m_mergedSchemas.insert(akey: locationUrl, avalue: QStringList() << schema->targetNamespace()); |
113 | } else { |
114 | QStringList &targetNamespaces = m_mergedSchemas[locationUrl]; |
115 | if (targetNamespaces.contains(str: schema->targetNamespace())) |
116 | return; |
117 | |
118 | targetNamespaces.append(t: schema->targetNamespace()); |
119 | } |
120 | |
121 | const XsdSchemaMerger merger(m_schema, schema); |
122 | m_schema = merger.mergedSchema(); |
123 | /* |
124 | XsdSchemaDebugger dbg(m_namePool); |
125 | dbg.dumpSchema(m_schema); |
126 | */ |
127 | } |
128 | |
129 | bool XsdValidatingInstanceReader::read() |
130 | { |
131 | while (!atEnd()) { |
132 | readNext(); |
133 | |
134 | if (isEndElement()) |
135 | return true; |
136 | |
137 | if (isStartElement()) { |
138 | const QXmlName elementName = name(); |
139 | const QXmlItem currentItem = item(); |
140 | bool hasStateMachine = false; |
141 | XsdElement::Ptr processedElement; |
142 | |
143 | if (!validate(hasStateMachine, element&: processedElement)) |
144 | return false; |
145 | |
146 | read(); |
147 | |
148 | if (processedElement) { // for wildcard with 'skip' we have no element |
149 | m_model->setAssignedElement(index: currentItem.toNodeModelIndex(), element: processedElement); |
150 | |
151 | // check identity constraints after all child nodes have been |
152 | // validated, so that we know there assigned types |
153 | validateIdentityConstraint(element: processedElement, currentItem); |
154 | } |
155 | |
156 | if (!m_stateMachines.isEmpty() && hasStateMachine) { |
157 | if (!m_stateMachines.top().inEndState()) { |
158 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 is missing child element." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: elementName)))); |
159 | return false; |
160 | } |
161 | m_stateMachines.pop(); |
162 | } |
163 | } |
164 | } |
165 | |
166 | // final validations |
167 | |
168 | // check IDREF occurrences |
169 | const QStringList ids = m_model->idIdRefBindingIds(); |
170 | for (const QString &id : qAsConst(t&: m_idRefs)) { |
171 | if (!ids.contains(str: id)) { |
172 | error(msg: QtXmlPatterns::tr(sourceText: "There is one IDREF value with no corresponding ID: %1." ).arg(a: formatKeyword(keyword: id))); |
173 | return false; |
174 | } |
175 | } |
176 | |
177 | return true; |
178 | } |
179 | |
180 | void XsdValidatingInstanceReader::error(const QString &msg) const |
181 | { |
182 | m_context.data()->error(message: msg, errorCode: XsdSchemaContext::XSDError, sourceLocation: sourceLocation()); |
183 | } |
184 | |
185 | bool XsdValidatingInstanceReader::loadSchema(const QString &targetNamespace, const QUrl &location) |
186 | { |
187 | const AutoPtr<QNetworkReply> reply(AccelTreeResourceLoader::load(uri: location, networkManager: m_context->networkAccessManager(), |
188 | context: m_context, handling: AccelTreeResourceLoader::ContinueOnError)); |
189 | if (!reply) |
190 | return true; |
191 | |
192 | // we have to create a separated schema context here, that however shares the type factory |
193 | XsdSchemaContext::Ptr context(new XsdSchemaContext(m_namePool)); |
194 | context->m_schemaTypeFactory = m_context->m_schemaTypeFactory; |
195 | |
196 | QXmlSchemaPrivate schema(context); |
197 | schema.load(source: reply.data(), documentUri: location, targetNamespace); |
198 | if (!schema.isValid()) { |
199 | error(msg: QtXmlPatterns::tr(sourceText: "Loaded schema file is invalid." )); |
200 | return false; |
201 | } |
202 | |
203 | addSchema(schema: schema.m_schemaParserContext->schema(), locationUrl: location); |
204 | |
205 | return true; |
206 | } |
207 | |
208 | bool XsdValidatingInstanceReader::validate(bool &hasStateMachine, XsdElement::Ptr &processedElement) |
209 | { |
210 | // first check if a custom schema is defined |
211 | if (hasAttribute(name: m_xsiSchemaLocationName)) { |
212 | const QString schemaLocation = attribute(name: m_xsiSchemaLocationName); |
213 | const QStringList parts = schemaLocation.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
214 | if ((parts.count()%2) == 1) { |
215 | error(msg: QtXmlPatterns::tr(sourceText: "%1 contains invalid data." ).arg(a: formatKeyword(np: m_namePool, name: m_xsiSchemaLocationName))); |
216 | return false; |
217 | } |
218 | |
219 | for (int i = 0; i < parts.count(); i += 2) { |
220 | const QString identifier = QString::fromLatin1(str: "%1 %2" ).arg(a: parts.at(i)).arg(a: parts.at(i: i + 1)); |
221 | if (m_processedSchemaLocations.contains(value: identifier)) |
222 | continue; |
223 | else |
224 | m_processedSchemaLocations.insert(value: identifier); |
225 | |
226 | // check constraint 4) from http://www.w3.org/TR/xmlschema-1/#schema-loc (only valid for XML Schema 1.0?) |
227 | if (m_processedNamespaces.contains(value: parts.at(i))) { |
228 | error(msg: QtXmlPatterns::tr(sourceText: "xsi:schemaLocation namespace %1 has already appeared earlier in the instance document." ).arg(a: formatKeyword(keyword: parts.at(i)))); |
229 | return false; |
230 | } |
231 | |
232 | QUrl url(parts.at(i: i + 1)); |
233 | if (url.isRelative()) { |
234 | Q_ASSERT(m_documentUri.isValid()); |
235 | |
236 | url = m_documentUri.resolved(relative: url); |
237 | } |
238 | |
239 | loadSchema(targetNamespace: parts.at(i), location: url); |
240 | } |
241 | } |
242 | |
243 | if (hasAttribute(name: m_xsiNoNamespaceSchemaLocationName)) { |
244 | const QString schemaLocation = attribute(name: m_xsiNoNamespaceSchemaLocationName); |
245 | |
246 | if (!m_processedSchemaLocations.contains(value: schemaLocation)) { |
247 | m_processedSchemaLocations.insert(value: schemaLocation); |
248 | |
249 | if (m_processedNamespaces.contains(value: QString())) { |
250 | error(msg: QtXmlPatterns::tr(sourceText: "xsi:noNamespaceSchemaLocation cannot appear after the first no-namespace element or attribute." )); |
251 | return false; |
252 | } |
253 | |
254 | QUrl url(schemaLocation); |
255 | if (url.isRelative()) { |
256 | Q_ASSERT(m_documentUri.isValid()); |
257 | |
258 | url = m_documentUri.resolved(relative: url); |
259 | } |
260 | |
261 | loadSchema(targetNamespace: QString(), location: url); |
262 | } |
263 | } |
264 | |
265 | m_processedNamespaces.insert(value: m_namePool->stringForNamespace(code: name().namespaceURI())); |
266 | |
267 | if (!m_schema) { |
268 | error(msg: QtXmlPatterns::tr(sourceText: "No schema defined for validation." )); |
269 | return false; |
270 | } |
271 | |
272 | // check if we are 'inside' a type definition |
273 | if (m_stateMachines.isEmpty()) { |
274 | // find out the type of the top-level element |
275 | XsdElement::Ptr element = elementByName(name: name()); |
276 | if (!element) { |
277 | if (!hasAttribute(name: m_xsiTypeName)) { |
278 | error(msg: QtXmlPatterns::tr(sourceText: "No definition for element %1 available." ).arg(a: formatKeyword(np: m_namePool, name: name()))); |
279 | return false; |
280 | } |
281 | |
282 | // This instance document has an element with no definition in the schema |
283 | // but an explicitly given type, that is fine according to the spec. |
284 | // We will create an element definition manually here and continue the |
285 | // normal validation process |
286 | element = XsdElement::Ptr(new XsdElement()); |
287 | element->setName(name()); |
288 | element->setIsAbstract(false); |
289 | element->setIsNillable(hasAttribute(name: m_xsiNilName)); |
290 | |
291 | const QString type = qNameAttribute(attributeName: m_xsiTypeName); |
292 | const QXmlName typeName = convertToQName(name: type); |
293 | |
294 | const SchemaType::Ptr elementType = typeByName(name: typeName); |
295 | if (!elementType) { |
296 | error(msg: QtXmlPatterns::tr(sourceText: "Specified type %1 is not known to the schema." ).arg(a: formatType(np: m_namePool, name: typeName))); |
297 | return false; |
298 | } |
299 | element->setType(elementType); |
300 | } |
301 | |
302 | // rememeber the element we process |
303 | processedElement = element; |
304 | |
305 | if (!validateElement(declaration: element, hasStateMachine)) { |
306 | return false; |
307 | } |
308 | |
309 | } else { |
310 | if (!m_stateMachines.top().proceed<QXmlName>(input: name())) { |
311 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 is not defined in this scope." ).arg(a: formatKeyword(np: m_namePool, name: name()))); |
312 | return false; |
313 | } |
314 | |
315 | const XsdTerm::Ptr term = m_stateMachines.top().lastTransition(); |
316 | if (term->isElement()) { |
317 | const XsdElement::Ptr element(term); |
318 | |
319 | // rememeber the element we process |
320 | processedElement = element; |
321 | |
322 | if (!validateElement(declaration: element, hasStateMachine)) |
323 | return false; |
324 | |
325 | } else { |
326 | const XsdWildcard::Ptr wildcard(term); |
327 | if (wildcard->processContents() != XsdWildcard::Skip) { |
328 | XsdElement::Ptr elementDeclaration = elementByName(name: name()); |
329 | if (!elementDeclaration) { |
330 | if (hasAttribute(name: m_xsiTypeName)) { |
331 | // This instance document has an element with no definition in the schema |
332 | // but an explicitly given type, that is fine according to the spec. |
333 | // We will create an element definition manually here and continue the |
334 | // normal validation process |
335 | elementDeclaration = XsdElement::Ptr(new XsdElement()); |
336 | elementDeclaration->setName(name()); |
337 | elementDeclaration->setIsAbstract(false); |
338 | elementDeclaration->setIsNillable(hasAttribute(name: m_xsiNilName)); |
339 | |
340 | const QString type = qNameAttribute(attributeName: m_xsiTypeName); |
341 | const QXmlName typeName = convertToQName(name: type); |
342 | |
343 | const SchemaType::Ptr elementType = typeByName(name: typeName); |
344 | if (!elementType) { |
345 | error(msg: QtXmlPatterns::tr(sourceText: "Specified type %1 is not known to the schema." ).arg(a: formatType(np: m_namePool, name: typeName))); |
346 | return false; |
347 | } |
348 | elementDeclaration->setType(elementType); |
349 | } |
350 | } |
351 | |
352 | if (!elementDeclaration) { |
353 | if (wildcard->processContents() == XsdWildcard::Strict) { |
354 | error(msg: QtXmlPatterns::tr(sourceText: "Declaration for element %1 does not exist." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: name())))); |
355 | return false; |
356 | } else { |
357 | // in this case we put a state machine for the xs:anyType on the statemachine stack, |
358 | // so we accept every content of this element |
359 | |
360 | createAndPushStateMachine(particle: anyType()->contentType()->particle()); |
361 | hasStateMachine = true; |
362 | } |
363 | } else { |
364 | if (!validateElement(declaration: elementDeclaration, hasStateMachine)) { |
365 | if (wildcard->processContents() == XsdWildcard::Strict) { |
366 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains invalid content." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: name())))); |
367 | return false; |
368 | } |
369 | } |
370 | |
371 | // rememeber the type of that element node |
372 | m_model->setAssignedType(index: item().toNodeModelIndex(), type: elementDeclaration->type()); |
373 | } |
374 | } else { // wildcard process contents type is Skip |
375 | // in this case we put a state machine for the xs:anyType on the statemachine stack, |
376 | // so we accept every content of this element |
377 | |
378 | const XsdWildcard::Ptr wildcard(new XsdWildcard()); |
379 | wildcard->namespaceConstraint()->setVariety(XsdWildcard::NamespaceConstraint::Any); |
380 | wildcard->setProcessContents(XsdWildcard::Skip); |
381 | |
382 | const XsdParticle::Ptr outerParticle(new XsdParticle()); |
383 | outerParticle->setMinimumOccurs(1); |
384 | outerParticle->setMaximumOccurs(1); |
385 | |
386 | const XsdParticle::Ptr innerParticle(new XsdParticle()); |
387 | innerParticle->setMinimumOccurs(0); |
388 | innerParticle->setMaximumOccursUnbounded(true); |
389 | innerParticle->setTerm(wildcard); |
390 | |
391 | const XsdModelGroup::Ptr outerModelGroup(new XsdModelGroup()); |
392 | outerModelGroup->setCompositor(XsdModelGroup::SequenceCompositor); |
393 | outerModelGroup->setParticles(XsdParticle::List() << innerParticle); |
394 | outerParticle->setTerm(outerModelGroup); |
395 | |
396 | createAndPushStateMachine(particle: outerParticle); |
397 | hasStateMachine = true; |
398 | } |
399 | } |
400 | } |
401 | |
402 | return true; |
403 | } |
404 | |
405 | void XsdValidatingInstanceReader::createAndPushStateMachine(const XsdParticle::Ptr &particle) |
406 | { |
407 | XsdStateMachine<XsdTerm::Ptr> stateMachine(m_namePool); |
408 | |
409 | XsdStateMachineBuilder builder(&stateMachine, m_namePool, XsdStateMachineBuilder::ValidatingMode); |
410 | const XsdStateMachine<XsdTerm::Ptr>::StateId endState = builder.reset(); |
411 | const XsdStateMachine<XsdTerm::Ptr>::StateId startState = builder.buildParticle(particle, endState); |
412 | builder.addStartState(state: startState); |
413 | |
414 | /* |
415 | QString fileName = QString("/tmp/foo_%1.dot").arg(m_namePool->displayName(complexType->name(m_namePool))); |
416 | QString pngFileName = QString("/tmp/foo_%1.png").arg(m_namePool->displayName(complexType->name(m_namePool))); |
417 | QFile file(fileName); |
418 | file.open(QIODevice::WriteOnly); |
419 | stateMachine.outputGraph(&file, "Hello"); |
420 | file.close(); |
421 | ::system(QString("dot -Tpng %1 -o%2").arg(fileName).arg(pngFileName).toLatin1().data()); |
422 | */ |
423 | |
424 | stateMachine = stateMachine.toDFA(); |
425 | |
426 | m_stateMachines.push(t: stateMachine); |
427 | } |
428 | |
429 | bool XsdValidatingInstanceReader::validateElement(const XsdElement::Ptr &declaration, bool &hasStateMachine) |
430 | { |
431 | // http://www.w3.org/TR/xmlschema11-1/#d0e10998 |
432 | |
433 | bool isNilled = false; |
434 | |
435 | // 1 tested already, 'declaration' corresponds D |
436 | |
437 | // 2 |
438 | if (declaration->isAbstract()) { |
439 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 is declared as abstract." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
440 | return false; |
441 | } |
442 | |
443 | // 3 |
444 | if (!declaration->isNillable()) { |
445 | if (hasAttribute(name: m_xsiNilName)) { |
446 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 is not nillable." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
447 | return false; // 3.1 |
448 | } |
449 | } else { |
450 | if (hasAttribute(name: m_xsiNilName)) { |
451 | const QString value = attribute(name: m_xsiNilName); |
452 | const Boolean::Ptr nil = Boolean::fromLexical(val: value); |
453 | if (nil->hasError()) { |
454 | error(msg: QtXmlPatterns::tr(sourceText: "Attribute %1 contains invalid data: %2" ).arg(a: formatKeyword(keyword: QLatin1String("nil." ))).arg(a: formatData(data: value))); |
455 | return false; |
456 | } |
457 | |
458 | // 3.2.3 |
459 | if (nil->as<Boolean>()->value() == true) { |
460 | // 3.2.3.1 |
461 | if (hasChildElement() || hasChildText()) { |
462 | error(msg: QtXmlPatterns::tr(sourceText: "Element contains content although it is nillable." )); |
463 | return false; |
464 | } |
465 | |
466 | // 3.2.3.2 |
467 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdElement::ValueConstraint::Fixed) { |
468 | error(msg: QtXmlPatterns::tr(sourceText: "Fixed value constraint not allowed if element is nillable." )); |
469 | return false; |
470 | } |
471 | } |
472 | |
473 | isNilled = nil->as<Boolean>()->value(); |
474 | } |
475 | } |
476 | |
477 | SchemaType::Ptr finalElementType = declaration->type(); |
478 | |
479 | // 4 |
480 | if (hasAttribute(name: m_xsiTypeName)) { |
481 | const QString type = qNameAttribute(attributeName: m_xsiTypeName); |
482 | const QXmlName typeName = convertToQName(name: type); |
483 | |
484 | const SchemaType::Ptr elementType = typeByName(name: typeName); |
485 | // 4.1 |
486 | if (!elementType) { |
487 | error(msg: QtXmlPatterns::tr(sourceText: "Specified type %1 is not known to the schema." ).arg(a: formatType(np: m_namePool, name: typeName))); |
488 | return false; |
489 | } |
490 | |
491 | // 4.2 |
492 | SchemaType::DerivationConstraints constraints; |
493 | if (declaration->disallowedSubstitutions() & NamedSchemaComponent::ExtensionConstraint) |
494 | constraints |= SchemaType::ExtensionConstraint; |
495 | if (declaration->disallowedSubstitutions() & NamedSchemaComponent::RestrictionConstraint) |
496 | constraints |= SchemaType::RestrictionConstraint; |
497 | |
498 | if (!XsdSchemaHelper::isValidlySubstitutable(type: elementType, otherType: declaration->type(), constraints)) { |
499 | if (declaration->type()->name(np: m_namePool) != BuiltinTypes::xsAnyType->name(np: m_namePool)) { // xs:anyType is a valid substitutable type here |
500 | error(msg: QtXmlPatterns::tr(sourceText: "Specified type %1 is not validly substitutable with element type %2." ).arg(a: formatType(np: m_namePool, type: elementType)).arg(a: formatType(np: m_namePool, type: declaration->type()))); |
501 | return false; |
502 | } |
503 | } |
504 | |
505 | finalElementType = elementType; |
506 | } |
507 | |
508 | if (!validateElementType(declaration, type: finalElementType, isNilled, hasStateMachine)) |
509 | return false; |
510 | |
511 | return true; |
512 | } |
513 | |
514 | bool XsdValidatingInstanceReader::validateElementType(const XsdElement::Ptr &declaration, const SchemaType::Ptr &type, bool isNilled, bool &hasStateMachine) |
515 | { |
516 | // @see http://www.w3.org/TR/xmlschema11-1/#d0e11749 |
517 | |
518 | // 1 checked already |
519 | |
520 | // 2 |
521 | if (type->isComplexType() && type->isDefinedBySchema()) { |
522 | if (XsdComplexType::Ptr(type)->isAbstract()) { |
523 | error(msg: QtXmlPatterns::tr(sourceText: "Complex type %1 is not allowed to be abstract." ).arg(a: formatType(np: m_namePool, type))); |
524 | return false; |
525 | } |
526 | } |
527 | |
528 | // 3 |
529 | if (type->isSimpleType()) |
530 | return validateElementSimpleType(declaration, type, isNilled); // 3.1 |
531 | else |
532 | return validateElementComplexType(declaration, type, isNilled, hasStateMachine); // 3.2 |
533 | } |
534 | |
535 | bool XsdValidatingInstanceReader::validateElementSimpleType(const XsdElement::Ptr &declaration, const SchemaType::Ptr &type, bool isNilled) |
536 | { |
537 | // @see http://www.w3.org/TR/xmlschema11-1/#d0e11749 |
538 | |
539 | // 3.1.1 |
540 | const QSet<QXmlName> allowedAttributes(QSet<QXmlName>() << m_xsiNilName << m_xsiTypeName << m_xsiSchemaLocationName << m_xsiNoNamespaceSchemaLocationName); |
541 | QSet<QXmlName> elementAttributes = attributeNames(); |
542 | elementAttributes.subtract(other: allowedAttributes); |
543 | if (!elementAttributes.isEmpty()) { |
544 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains not allowed attributes." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
545 | return false; |
546 | } |
547 | |
548 | // 3.1.2 |
549 | if (hasChildElement()) { |
550 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains not allowed child element." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
551 | return false; |
552 | } |
553 | |
554 | // 3.1.3 |
555 | if (!isNilled) { |
556 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type, context: m_context); |
557 | |
558 | QString actualValue; |
559 | if (hasChildText()) { |
560 | actualValue = XsdTypeChecker::normalizedValue(value: text(), facets); |
561 | } else { |
562 | if (declaration->valueConstraint()) |
563 | actualValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
564 | } |
565 | |
566 | QString errorMsg; |
567 | AnySimpleType::Ptr boundType; |
568 | |
569 | const XsdTypeChecker checker(m_context, namespaceBindings(index: item().toNodeModelIndex()), sourceLocation()); |
570 | if (!checker.isValidString(normalizedString: actualValue, type, errorMsg, boundType: &boundType)) { |
571 | error(msg: QtXmlPatterns::tr(sourceText: "Content of element %1 does not match its type definition: %2." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))).arg(a: errorMsg)); |
572 | return false; |
573 | } |
574 | |
575 | // additional check |
576 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdElement::ValueConstraint::Fixed) { |
577 | const QString actualConstraintValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
578 | if (!text().isEmpty() && !checker.valuesAreEqual(value: actualValue, otherValue: actualConstraintValue, type)) { |
579 | error(msg: QtXmlPatterns::tr(sourceText: "Content of element %1 does not match defined value constraint." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
580 | return false; |
581 | } |
582 | } |
583 | } |
584 | |
585 | // 4 checked in validateElement already |
586 | |
587 | // rememeber the type of that element node |
588 | m_model->setAssignedType(index: item().toNodeModelIndex(), type); |
589 | |
590 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type, context: m_context); |
591 | const QString actualValue = XsdTypeChecker::normalizedValue(value: text(), facets); |
592 | |
593 | if (BuiltinTypes::xsID->wxsTypeMatches(other: type)) { |
594 | addIdIdRefBinding(id: actualValue, binding: declaration); |
595 | } |
596 | |
597 | if (m_idRefsType->wxsTypeMatches(other: type)) { |
598 | const QStringList idRefs = actualValue.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
599 | for (int i = 0; i < idRefs.count(); ++i) { |
600 | m_idRefs.insert(value: idRefs.at(i)); |
601 | } |
602 | } else if (BuiltinTypes::xsIDREF->wxsTypeMatches(other: type)) { |
603 | m_idRefs.insert(value: actualValue); |
604 | } |
605 | |
606 | return true; |
607 | } |
608 | |
609 | static bool hasIDAttributeUse(const XsdAttributeUse::List &uses) |
610 | { |
611 | const int count = uses.count(); |
612 | for (int i = 0; i < count; ++i) { |
613 | if (BuiltinTypes::xsID->wxsTypeMatches(other: uses.at(i)->attribute()->type())) |
614 | return true; |
615 | } |
616 | |
617 | return false; |
618 | } |
619 | |
620 | bool XsdValidatingInstanceReader::validateElementComplexType(const XsdElement::Ptr &declaration, const SchemaType::Ptr &type, bool isNilled, bool &hasStateMachine) |
621 | { |
622 | // @see http://www.w3.org/TR/xmlschema11-1/#cvc-complex-type |
623 | |
624 | // 1 |
625 | if (!isNilled) { |
626 | XsdComplexType::Ptr complexType; |
627 | |
628 | if (type->isDefinedBySchema()) { |
629 | complexType = XsdComplexType::Ptr(type); |
630 | } else { |
631 | if (type->name(np: m_namePool) == BuiltinTypes::xsAnyType->name(np: m_namePool)) |
632 | complexType = anyType(); |
633 | } |
634 | |
635 | if (complexType) { |
636 | // 1.1 |
637 | if (complexType->contentType()->variety() == XsdComplexType::ContentType::Empty) { |
638 | if (hasChildText() || hasChildElement()) { |
639 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains not allowed child content." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
640 | return false; |
641 | } |
642 | } |
643 | |
644 | // 1.2 |
645 | if (complexType->contentType()->variety() == XsdComplexType::ContentType::Simple) { |
646 | if (hasChildElement()) { |
647 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains not allowed child element." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
648 | return false; |
649 | } |
650 | |
651 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type: complexType->contentType()->simpleType(), context: m_context); |
652 | QString actualValue; |
653 | if (hasChildText()) { |
654 | actualValue = XsdTypeChecker::normalizedValue(value: text(), facets); |
655 | } else { |
656 | if (declaration->valueConstraint()) |
657 | actualValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
658 | } |
659 | |
660 | QString errorMsg; |
661 | AnySimpleType::Ptr boundType; |
662 | const XsdTypeChecker checker(m_context, namespaceBindings(index: item().toNodeModelIndex()), sourceLocation()); |
663 | if (!checker.isValidString(normalizedString: actualValue, type: complexType->contentType()->simpleType(), errorMsg, boundType: &boundType)) { |
664 | error(msg: QtXmlPatterns::tr(sourceText: "Content of element %1 does not match its type definition: %2." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))).arg(a: errorMsg)); |
665 | return false; |
666 | } |
667 | |
668 | // additional check |
669 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdElement::ValueConstraint::Fixed) { |
670 | if (!checker.valuesAreEqual(value: actualValue, otherValue: declaration->valueConstraint()->value(), type: boundType)) { |
671 | error(msg: QtXmlPatterns::tr(sourceText: "Content of element %1 does not match defined value constraint." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
672 | return false; |
673 | } |
674 | } |
675 | } |
676 | |
677 | // 1.3 |
678 | if (complexType->contentType()->variety() == XsdComplexType::ContentType::ElementOnly) { |
679 | if (!text().simplified().isEmpty()) { |
680 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains not allowed text content." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
681 | return false; |
682 | } |
683 | } |
684 | |
685 | // 1.4 |
686 | if (complexType->contentType()->variety() == XsdComplexType::ContentType::ElementOnly || |
687 | complexType->contentType()->variety() == XsdComplexType::ContentType::Mixed) { |
688 | |
689 | if (complexType->contentType()->particle()) { |
690 | createAndPushStateMachine(particle: complexType->contentType()->particle()); |
691 | hasStateMachine = true; |
692 | } |
693 | |
694 | // additional check |
695 | if (complexType->contentType()->variety() == XsdComplexType::ContentType::Mixed) { |
696 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdElement::ValueConstraint::Fixed) { |
697 | if (hasChildElement()) { |
698 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 cannot contain other elements, as it has a fixed content." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
699 | return false; |
700 | } |
701 | |
702 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type: complexType->contentType()->simpleType(), context: m_context); |
703 | QString actualValue; |
704 | if (hasChildText()) { |
705 | actualValue = XsdTypeChecker::normalizedValue(value: text(), facets); |
706 | } else { |
707 | if (declaration->valueConstraint()) |
708 | actualValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
709 | } |
710 | |
711 | if (actualValue != declaration->valueConstraint()->value()) { |
712 | error(msg: QtXmlPatterns::tr(sourceText: "Content of element %1 does not match defined value constraint." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
713 | return false; |
714 | } |
715 | } |
716 | } |
717 | } |
718 | } |
719 | } |
720 | |
721 | if (type->isDefinedBySchema()) { |
722 | const XsdComplexType::Ptr complexType(type); |
723 | |
724 | // create a lookup hash for faster access |
725 | QHash<QXmlName, XsdAttributeUse::Ptr> attributeUseHash; |
726 | { |
727 | const XsdAttributeUse::List attributeUses = complexType->attributeUses(); |
728 | for (int i = 0; i < attributeUses.count(); ++i) |
729 | attributeUseHash.insert(akey: attributeUses.at(i)->attribute()->name(namePool: m_namePool), avalue: attributeUses.at(i)); |
730 | } |
731 | |
732 | const QSet<QXmlName> attributes(attributeNames()); |
733 | |
734 | // 3 |
735 | for (auto it = attributeUseHash.cbegin(), end = attributeUseHash.cend(); it != end; ++it) { |
736 | if (it.value()->isRequired()) { |
737 | if (!attributes.contains(value: it.key())) { |
738 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 is missing required attribute %2." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))) |
739 | .arg(a: formatKeyword(keyword: m_namePool->displayName(qName: it.key())))); |
740 | return false; |
741 | } |
742 | } |
743 | } |
744 | |
745 | bool hasIDAttribute = hasIDAttributeUse(uses: complexType->attributeUses()); |
746 | |
747 | // 2 |
748 | for (const QXmlName &attributeName : attributes) { |
749 | |
750 | // skip builtin attributes |
751 | if (attributeName == m_xsiNilName || |
752 | attributeName == m_xsiTypeName || |
753 | attributeName == m_xsiSchemaLocationName || |
754 | attributeName == m_xsiNoNamespaceSchemaLocationName) |
755 | continue; |
756 | |
757 | // 2.1 |
758 | if (attributeUseHash.contains(akey: attributeName) && (attributeUseHash.value(akey: attributeName)->useType() != XsdAttributeUse::ProhibitedUse)) { |
759 | if (!validateAttribute(declaration: attributeUseHash.value(akey: attributeName), value: attribute(name: attributeName))) |
760 | return false; |
761 | } else { // 2.2 |
762 | if (complexType->attributeWildcard()) { |
763 | const XsdWildcard::Ptr wildcard(complexType->attributeWildcard()); |
764 | if (!validateAttributeWildcard(attributeName, wildcard)) { |
765 | error(msg: QtXmlPatterns::tr(sourceText: "Attribute %1 does not match the attribute wildcard." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: attributeName)))); |
766 | return false; |
767 | } |
768 | |
769 | if (wildcard->processContents() != XsdWildcard::Skip) { |
770 | const XsdAttribute::Ptr attributeDeclaration = attributeByName(name: attributeName); |
771 | |
772 | if (!attributeDeclaration) { |
773 | if (wildcard->processContents() == XsdWildcard::Strict) { |
774 | error(msg: QtXmlPatterns::tr(sourceText: "Declaration for attribute %1 does not exist." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: attributeName)))); |
775 | return false; |
776 | } |
777 | } else { |
778 | if (BuiltinTypes::xsID->wxsTypeMatches(other: attributeDeclaration->type())) { |
779 | if (hasIDAttribute) { |
780 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains two attributes of type %2." ) |
781 | .arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))) |
782 | .arg(a: formatKeyword(keyword: "ID" ))); |
783 | return false; |
784 | } |
785 | |
786 | hasIDAttribute = true; |
787 | } |
788 | |
789 | if (!validateAttribute(declaration: attributeDeclaration, value: attribute(name: attributeName))) { |
790 | if (wildcard->processContents() == XsdWildcard::Strict) { |
791 | error(msg: QtXmlPatterns::tr(sourceText: "Attribute %1 contains invalid content." ).arg(a: formatKeyword(keyword: m_namePool->displayName(qName: attributeName)))); |
792 | return false; |
793 | } |
794 | } |
795 | } |
796 | } |
797 | } else { |
798 | error(msg: QtXmlPatterns::tr(sourceText: "Element %1 contains unknown attribute %2." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))) |
799 | .arg(a: formatKeyword(keyword: m_namePool->displayName(qName: attributeName)))); |
800 | return false; |
801 | } |
802 | } |
803 | } |
804 | } |
805 | |
806 | // 4 |
807 | // so what?... |
808 | |
809 | // 5 |
810 | // hmm... |
811 | |
812 | // 6 |
813 | // TODO: check assertions |
814 | |
815 | // 7 |
816 | // TODO: check type table restrictions |
817 | |
818 | // rememeber the type of that element node |
819 | m_model->setAssignedType(index: item().toNodeModelIndex(), type); |
820 | |
821 | return true; |
822 | } |
823 | |
824 | bool XsdValidatingInstanceReader::validateAttribute(const XsdAttributeUse::Ptr &declaration, const QString &value) |
825 | { |
826 | const AnySimpleType::Ptr attributeType = declaration->attribute()->type(); |
827 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type: attributeType, context: m_context); |
828 | |
829 | const QString actualValue = XsdTypeChecker::normalizedValue(value, facets); |
830 | |
831 | QString errorMsg; |
832 | AnySimpleType::Ptr boundType; |
833 | |
834 | const QXmlNodeModelIndex index = attributeItem(name: declaration->attribute()->name(namePool: m_namePool)).toNodeModelIndex(); |
835 | |
836 | const XsdTypeChecker checker(m_context, namespaceBindings(index), sourceLocation()); |
837 | if (!checker.isValidString(normalizedString: actualValue, type: attributeType, errorMsg, boundType: &boundType)) { |
838 | error(msg: QtXmlPatterns::tr(sourceText: "Content of attribute %1 does not match its type definition: %2." ).arg(a: formatKeyword(keyword: declaration->attribute()->displayName(namePool: m_namePool))).arg(a: errorMsg)); |
839 | return false; |
840 | } |
841 | |
842 | // @see http://www.w3.org/TR/xmlschema11-1/#cvc-au |
843 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdAttributeUse::ValueConstraint::Fixed) { |
844 | const QString actualConstraintValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
845 | if (!checker.valuesAreEqual(value: actualValue, otherValue: actualConstraintValue, type: attributeType)) { |
846 | error(msg: QtXmlPatterns::tr(sourceText: "Content of attribute %1 does not match defined value constraint." ).arg(a: formatKeyword(keyword: declaration->attribute()->displayName(namePool: m_namePool)))); |
847 | return false; |
848 | } |
849 | } |
850 | |
851 | if (BuiltinTypes::xsID->wxsTypeMatches(other: declaration->attribute()->type())) { |
852 | addIdIdRefBinding(id: actualValue, binding: declaration->attribute()); |
853 | } |
854 | |
855 | if (m_idRefsType->wxsTypeMatches(other: declaration->attribute()->type())) { |
856 | const QStringList idRefs = actualValue.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
857 | for (int i = 0; i < idRefs.count(); ++i) |
858 | m_idRefs.insert(value: idRefs.at(i)); |
859 | } else if (BuiltinTypes::xsIDREF->wxsTypeMatches(other: declaration->attribute()->type())) { |
860 | m_idRefs.insert(value: actualValue); |
861 | } |
862 | |
863 | m_model->setAssignedType(index, type: declaration->attribute()->type()); |
864 | m_model->setAssignedAttribute(index, attribute: declaration->attribute()); |
865 | |
866 | return true; |
867 | } |
868 | |
869 | //TODO: merge that with the method above |
870 | bool XsdValidatingInstanceReader::validateAttribute(const XsdAttribute::Ptr &declaration, const QString &value) |
871 | { |
872 | const AnySimpleType::Ptr attributeType = declaration->type(); |
873 | const XsdFacet::Hash facets = XsdTypeChecker::mergedFacetsForType(type: attributeType, context: m_context); |
874 | |
875 | const QString actualValue = XsdTypeChecker::normalizedValue(value, facets); |
876 | |
877 | QString errorMsg; |
878 | AnySimpleType::Ptr boundType; |
879 | |
880 | const QXmlNodeModelIndex index = attributeItem(name: declaration->name(namePool: m_namePool)).toNodeModelIndex(); |
881 | |
882 | const XsdTypeChecker checker(m_context, namespaceBindings(index), sourceLocation()); |
883 | if (!checker.isValidString(normalizedString: actualValue, type: attributeType, errorMsg, boundType: &boundType)) { |
884 | error(msg: QtXmlPatterns::tr(sourceText: "Content of attribute %1 does not match its type definition: %2." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool))).arg(a: errorMsg)); |
885 | return false; |
886 | } |
887 | |
888 | // @see http://www.w3.org/TR/xmlschema11-1/#cvc-au |
889 | if (declaration->valueConstraint() && declaration->valueConstraint()->variety() == XsdAttribute::ValueConstraint::Fixed) { |
890 | const QString actualConstraintValue = XsdTypeChecker::normalizedValue(value: declaration->valueConstraint()->value(), facets); |
891 | if (!checker.valuesAreEqual(value: actualValue, otherValue: actualConstraintValue, type: attributeType)) { |
892 | error(msg: QtXmlPatterns::tr(sourceText: "Content of attribute %1 does not match defined value constraint." ).arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
893 | return false; |
894 | } |
895 | } |
896 | |
897 | if (BuiltinTypes::xsID->wxsTypeMatches(other: declaration->type())) { |
898 | addIdIdRefBinding(id: actualValue, binding: declaration); |
899 | } |
900 | |
901 | if (m_idRefsType->wxsTypeMatches(other: declaration->type())) { |
902 | const QStringList idRefs = actualValue.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
903 | for (int i = 0; i < idRefs.count(); ++i) |
904 | m_idRefs.insert(value: idRefs.at(i)); |
905 | } else if (BuiltinTypes::xsIDREF->wxsTypeMatches(other: declaration->type())) { |
906 | m_idRefs.insert(value: actualValue); |
907 | } |
908 | |
909 | m_model->setAssignedType(index, type: declaration->type()); |
910 | m_model->setAssignedAttribute(index, attribute: declaration); |
911 | |
912 | return true; |
913 | } |
914 | |
915 | bool XsdValidatingInstanceReader::validateAttributeWildcard(const QXmlName &attributeName, const XsdWildcard::Ptr &wildcard) |
916 | { |
917 | // @see http://www.w3.org/TR/xmlschema11-1/#cvc-wildcard |
918 | |
919 | // wildcards using XsdWildcard::absentNamespace, so we have to fix that here |
920 | QXmlName name(attributeName); |
921 | if (name.namespaceURI() == StandardNamespaces::empty) { |
922 | name.setNamespaceURI(m_namePool->allocateNamespace(uri: XsdWildcard::absentNamespace())); |
923 | } |
924 | |
925 | return XsdSchemaHelper::wildcardAllowsExpandedName(name, wildcard, namePool: m_namePool); |
926 | } |
927 | |
928 | bool XsdValidatingInstanceReader::validateIdentityConstraint(const XsdElement::Ptr &element, const QXmlItem ¤tItem) |
929 | { |
930 | const XsdIdentityConstraint::List constraints = element->identityConstraints(); |
931 | |
932 | for (int i = 0; i < constraints.count(); ++i) { |
933 | const XsdIdentityConstraint::Ptr constraint = constraints.at(i); |
934 | |
935 | TargetNode::Set targetNodeSet, qualifiedNodeSet; |
936 | selectNodeSets(element, currentItem, constraint, targetNodeSet, qualifiedNodeSet); |
937 | |
938 | if (constraint->category() == XsdIdentityConstraint::Unique) { |
939 | if (!validateUniqueIdentityConstraint(element, constraint, qualifiedNodeSet)) |
940 | return false; |
941 | } else if (constraint->category() == XsdIdentityConstraint::Key) { |
942 | if (!validateKeyIdentityConstraint(element, constraint, targetNodeSet, qualifiedNodeSet)) |
943 | return false; |
944 | } |
945 | } |
946 | |
947 | // we do the keyref check in a separated run to make sure that all keys are available |
948 | for (int i = 0; i < constraints.count(); ++i) { |
949 | const XsdIdentityConstraint::Ptr constraint = constraints.at(i); |
950 | if (constraint->category() == XsdIdentityConstraint::KeyReference) { |
951 | TargetNode::Set targetNodeSet, qualifiedNodeSet; |
952 | selectNodeSets(element, currentItem, constraint, targetNodeSet, qualifiedNodeSet); |
953 | |
954 | if (!validateKeyRefIdentityConstraint(element, constraint, qualifiedNodeSet)) |
955 | return false; |
956 | } |
957 | } |
958 | |
959 | return true; |
960 | } |
961 | |
962 | bool XsdValidatingInstanceReader::validateUniqueIdentityConstraint(const XsdElement::Ptr&, const XsdIdentityConstraint::Ptr &constraint, const TargetNode::Set &qualifiedNodeSet) |
963 | { |
964 | // @see http://www.w3.org/TR/xmlschema11-1/#d0e32243 |
965 | |
966 | // 4.1 |
967 | const XsdSchemaSourceLocationReflection reflection(sourceLocation()); |
968 | |
969 | for (auto it = qualifiedNodeSet.cbegin(), end = qualifiedNodeSet.cend(); it != end; ++it) { |
970 | for (auto jt = qualifiedNodeSet.cbegin(); jt != it; ++jt) { |
971 | if (it->fieldsAreEqual(other: *jt, namePool: m_namePool, context: m_context, reflection: &reflection)) { |
972 | error(msg: QtXmlPatterns::tr(sourceText: "Non-unique value found for constraint %1." ).arg(a: formatKeyword(keyword: constraint->displayName(namePool: m_namePool)))); |
973 | return false; |
974 | } |
975 | } |
976 | } |
977 | |
978 | m_idcKeys.insert(akey: constraint->name(namePool: m_namePool), avalue: qualifiedNodeSet); |
979 | |
980 | return true; |
981 | } |
982 | |
983 | bool XsdValidatingInstanceReader::validateKeyIdentityConstraint(const XsdElement::Ptr &element, const XsdIdentityConstraint::Ptr &constraint, const TargetNode::Set &targetNodeSet, const TargetNode::Set &qualifiedNodeSet) |
984 | { |
985 | // @see http://www.w3.org/TR/xmlschema11-1/#d0e32243 |
986 | |
987 | // 4.2 |
988 | const XsdSchemaSourceLocationReflection reflection(sourceLocation()); |
989 | |
990 | // 4.2.1 |
991 | if (targetNodeSet.count() != qualifiedNodeSet.count()) { |
992 | error(msg: QtXmlPatterns::tr(sourceText: "Key constraint %1 contains absent fields." ).arg(a: formatKeyword(keyword: constraint->displayName(namePool: m_namePool)))); |
993 | return false; |
994 | } |
995 | |
996 | // 4.2.2 |
997 | if (!validateUniqueIdentityConstraint(element, constraint, qualifiedNodeSet)) |
998 | return false; |
999 | |
1000 | // 4.2.3 |
1001 | for (const TargetNode node : qualifiedNodeSet) { |
1002 | const QVector<QXmlItem> fieldItems = node.fieldItems(); |
1003 | for (int i = 0; i < fieldItems.count(); ++i) { |
1004 | const QXmlNodeModelIndex index = fieldItems.at(i).toNodeModelIndex(); |
1005 | if (m_model->kind(ni: index) == QXmlNodeModelIndex::Element) { |
1006 | const XsdElement::Ptr declaration = m_model->assignedElement(index); |
1007 | if (declaration && declaration->isNillable()) { |
1008 | error(msg: QtXmlPatterns::tr(sourceText: "Key constraint %1 contains references nillable element %2." ) |
1009 | .arg(a: formatKeyword(keyword: constraint->displayName(namePool: m_namePool))) |
1010 | .arg(a: formatKeyword(keyword: declaration->displayName(namePool: m_namePool)))); |
1011 | return false; |
1012 | } |
1013 | } |
1014 | } |
1015 | } |
1016 | |
1017 | m_idcKeys.insert(akey: constraint->name(namePool: m_namePool), avalue: qualifiedNodeSet); |
1018 | |
1019 | return true; |
1020 | } |
1021 | |
1022 | bool XsdValidatingInstanceReader::validateKeyRefIdentityConstraint(const XsdElement::Ptr&, const XsdIdentityConstraint::Ptr &constraint, const TargetNode::Set &qualifiedNodeSet) |
1023 | { |
1024 | // @see http://www.w3.org/TR/xmlschema11-1/#d0e32243 |
1025 | |
1026 | // 4.3 |
1027 | const XsdSchemaSourceLocationReflection reflection(sourceLocation()); |
1028 | |
1029 | const TargetNode::Set keySet = m_idcKeys.value(akey: constraint->referencedKey()->name(namePool: m_namePool)); |
1030 | |
1031 | for (const TargetNode &node : qualifiedNodeSet) { |
1032 | |
1033 | bool foundMatching = false; |
1034 | |
1035 | for (const TargetNode &keyNode : keySet) { |
1036 | if (node.fieldsAreEqual(other: keyNode, namePool: m_namePool, context: m_context, reflection: &reflection)) { |
1037 | foundMatching = true; |
1038 | break; |
1039 | } |
1040 | } |
1041 | |
1042 | if (!foundMatching) { |
1043 | error(msg: QtXmlPatterns::tr(sourceText: "No referenced value found for key reference %1." ).arg(a: formatKeyword(keyword: constraint->displayName(namePool: m_namePool)))); |
1044 | return false; |
1045 | } |
1046 | } |
1047 | |
1048 | return true; |
1049 | } |
1050 | |
1051 | QXmlQuery XsdValidatingInstanceReader::createXQuery(const QList<QXmlName> &namespaceBindings, const QXmlItem &contextNode, const QString &queryString) const |
1052 | { |
1053 | // create a public name pool from our name pool |
1054 | QXmlNamePool namePool(m_namePool.data()); |
1055 | |
1056 | // the QXmlQuery shall work with the same name pool as we do |
1057 | QXmlQuery query(namePool); |
1058 | |
1059 | // add additional namespace bindings |
1060 | QXmlQueryPrivate *queryPrivate = query.d; |
1061 | |
1062 | for (int i = 0; i < namespaceBindings.count(); ++i) { |
1063 | if (namespaceBindings.at(i).prefix() != StandardPrefixes::empty) |
1064 | queryPrivate->addAdditionalNamespaceBinding(binding: namespaceBindings.at(i)); |
1065 | } |
1066 | |
1067 | // set the context node for that query and the query string |
1068 | query.setFocus(contextNode); |
1069 | query.setQuery(sourceCode: queryString, documentURI: m_documentUri); |
1070 | |
1071 | return query; |
1072 | } |
1073 | |
1074 | bool XsdValidatingInstanceReader::selectNodeSets(const XsdElement::Ptr&, const QXmlItem ¤tItem, const XsdIdentityConstraint::Ptr &constraint, TargetNode::Set &targetNodeSet, TargetNode::Set &qualifiedNodeSet) |
1075 | { |
1076 | // at first select all target nodes |
1077 | const XsdXPathExpression::Ptr selector = constraint->selector(); |
1078 | const XsdXPathExpression::List fields = constraint->fields(); |
1079 | |
1080 | QXmlQuery query = createXQuery(namespaceBindings: selector->namespaceBindings(), contextNode: currentItem, queryString: selector->expression()); |
1081 | |
1082 | QXmlResultItems resultItems; |
1083 | query.evaluateTo(result: &resultItems); |
1084 | |
1085 | // now we iterate over all target nodes and select the fields for each node |
1086 | QXmlItem item(resultItems.next()); |
1087 | while (!item.isNull()) { |
1088 | |
1089 | TargetNode targetNode(item); |
1090 | |
1091 | for (int i = 0; i < fields.count(); ++i) { |
1092 | const XsdXPathExpression::Ptr field = fields.at(i); |
1093 | QXmlQuery fieldQuery = createXQuery(namespaceBindings: field->namespaceBindings(), contextNode: item, queryString: field->expression()); |
1094 | |
1095 | QXmlResultItems fieldResultItems; |
1096 | fieldQuery.evaluateTo(result: &fieldResultItems); |
1097 | |
1098 | // copy result into vetor for better testing... |
1099 | QVector<QXmlItem> fieldVector; |
1100 | QXmlItem fieldItem(fieldResultItems.next()); |
1101 | while (!fieldItem.isNull()) { |
1102 | fieldVector.append(t: fieldItem); |
1103 | fieldItem = fieldResultItems.next(); |
1104 | } |
1105 | |
1106 | if (fieldVector.count() > 1) { |
1107 | error(msg: QtXmlPatterns::tr(sourceText: "More than one value found for field %1." ).arg(a: formatData(data: field->expression()))); |
1108 | return false; |
1109 | } |
1110 | |
1111 | if (fieldVector.count() == 1) { |
1112 | fieldItem = fieldVector.first(); |
1113 | |
1114 | const QXmlNodeModelIndex index = fieldItem.toNodeModelIndex(); |
1115 | const SchemaType::Ptr type = m_model->assignedType(index); |
1116 | Q_ASSERT(type); |
1117 | |
1118 | bool typeOk = true; |
1119 | if (type->isComplexType()) { |
1120 | if (type->isDefinedBySchema()) { |
1121 | if (XsdComplexType::Ptr(type)->contentType()->variety() != XsdComplexType::ContentType::Simple) |
1122 | typeOk = false; |
1123 | } else { |
1124 | typeOk = false; |
1125 | } |
1126 | } |
1127 | if (!typeOk) { |
1128 | error(msg: QtXmlPatterns::tr(sourceText: "Field %1 has no simple type." ).arg(a: formatData(data: field->expression()))); |
1129 | return false; |
1130 | } |
1131 | |
1132 | SchemaType::Ptr targetType = type; |
1133 | QString value = m_model->stringValue(n: fieldItem.toNodeModelIndex()); |
1134 | |
1135 | if (type->isDefinedBySchema()) { |
1136 | if (type->isSimpleType()) |
1137 | targetType = XsdSimpleType::Ptr(type)->primitiveType(); |
1138 | else |
1139 | targetType = XsdComplexType::Ptr(type)->contentType()->simpleType(); |
1140 | |
1141 | if (!targetType) { |
1142 | // QTBUG-77620: pattern type within a union doesn't get |
1143 | // its primitive type set. FIXME: find root cause and |
1144 | // fix that, so we can remove this (and an XFAIL). |
1145 | error(msg: QtXmlPatterns::tr(sourceText: "Field %1 is missing its simple type." ) |
1146 | .arg(a: formatData(data: field->expression()))); |
1147 | return false; |
1148 | } |
1149 | } else { |
1150 | if (BuiltinTypes::xsAnySimpleType->name(np: m_namePool) == type->name(np: m_namePool)) { |
1151 | targetType = BuiltinTypes::xsString; |
1152 | value = QLatin1String("___anySimpleType_value" ); |
1153 | } |
1154 | } |
1155 | |
1156 | // if it is xs:QName derived type, we normalize the name content |
1157 | // and do a string comparison |
1158 | if (BuiltinTypes::xsQName->wxsTypeMatches(other: type)) { |
1159 | targetType = BuiltinTypes::xsString; |
1160 | |
1161 | const QXmlName qName = convertToQName(name: value.trimmed()); |
1162 | value = QString::fromLatin1(str: "%1:%2" ).arg(a: m_namePool->stringForNamespace(code: qName.namespaceURI())).arg(a: m_namePool->stringForLocalName(code: qName.localName())); |
1163 | } |
1164 | |
1165 | targetNode.addField(item: fieldItem, data: value, type: targetType); |
1166 | } else { |
1167 | // we add an empty entry here, that makes comparison easier later on |
1168 | targetNode.addField(item: QXmlItem(), data: QString(), type: SchemaType::Ptr()); |
1169 | } |
1170 | } |
1171 | |
1172 | targetNodeSet.insert(value: targetNode); |
1173 | |
1174 | item = resultItems.next(); |
1175 | } |
1176 | |
1177 | // copy all items from target node set to qualified node set, that have no empty fields |
1178 | for (const TargetNode &node : qAsConst(t&: targetNodeSet)) { |
1179 | if (node.emptyFieldsCount() == 0) |
1180 | qualifiedNodeSet.insert(value: node); |
1181 | } |
1182 | |
1183 | return true; |
1184 | } |
1185 | |
1186 | XsdElement::Ptr XsdValidatingInstanceReader::elementByName(const QXmlName &name) const |
1187 | { |
1188 | return m_schema->element(name); |
1189 | } |
1190 | |
1191 | XsdAttribute::Ptr XsdValidatingInstanceReader::attributeByName(const QXmlName &name) const |
1192 | { |
1193 | return m_schema->attribute(name); |
1194 | } |
1195 | |
1196 | SchemaType::Ptr XsdValidatingInstanceReader::typeByName(const QXmlName &name) const |
1197 | { |
1198 | const SchemaType::Ptr type = m_schema->type(name); |
1199 | if (type) |
1200 | return type; |
1201 | |
1202 | return m_context->schemaTypeFactory()->createSchemaType(name); |
1203 | } |
1204 | |
1205 | void XsdValidatingInstanceReader::addIdIdRefBinding(const QString &id, const NamedSchemaComponent::Ptr &binding) |
1206 | { |
1207 | if (!m_model->idIdRefBindings(id).isEmpty()) { |
1208 | error(msg: QtXmlPatterns::tr(sourceText: "ID value '%1' is not unique." ).arg(a: formatKeyword(keyword: id))); |
1209 | return; |
1210 | } |
1211 | |
1212 | m_model->addIdIdRefBinding(id, binding); |
1213 | } |
1214 | |
1215 | QString XsdValidatingInstanceReader::qNameAttribute(const QXmlName &attributeName) |
1216 | { |
1217 | const QString value = attribute(name: attributeName).simplified(); |
1218 | if (!XPathHelper::isQName(qName: value)) { |
1219 | error(msg: QtXmlPatterns::tr(sourceText: "'%1' attribute contains invalid QName content: %2." ).arg(a: m_namePool->displayName(qName: attributeName)).arg(a: formatData(data: value))); |
1220 | return QString(); |
1221 | } else { |
1222 | return value; |
1223 | } |
1224 | } |
1225 | |
1226 | XsdComplexType::Ptr XsdValidatingInstanceReader::anyType() |
1227 | { |
1228 | if (m_anyType) |
1229 | return m_anyType; |
1230 | |
1231 | const XsdWildcard::Ptr wildcard(new XsdWildcard()); |
1232 | wildcard->namespaceConstraint()->setVariety(XsdWildcard::NamespaceConstraint::Any); |
1233 | wildcard->setProcessContents(XsdWildcard::Lax); |
1234 | |
1235 | const XsdParticle::Ptr outerParticle(new XsdParticle()); |
1236 | outerParticle->setMinimumOccurs(1); |
1237 | outerParticle->setMaximumOccurs(1); |
1238 | |
1239 | const XsdParticle::Ptr innerParticle(new XsdParticle()); |
1240 | innerParticle->setMinimumOccurs(0); |
1241 | innerParticle->setMaximumOccursUnbounded(true); |
1242 | innerParticle->setTerm(wildcard); |
1243 | |
1244 | const XsdModelGroup::Ptr outerModelGroup(new XsdModelGroup()); |
1245 | outerModelGroup->setCompositor(XsdModelGroup::SequenceCompositor); |
1246 | outerModelGroup->setParticles(XsdParticle::List() << innerParticle); |
1247 | outerParticle->setTerm(outerModelGroup); |
1248 | |
1249 | m_anyType = XsdComplexType::Ptr(new XsdComplexType()); |
1250 | m_anyType->setName(BuiltinTypes::xsAnyType->name(np: m_namePool)); |
1251 | m_anyType->setDerivationMethod(XsdComplexType::DerivationRestriction); |
1252 | m_anyType->contentType()->setVariety(XsdComplexType::ContentType::Mixed); |
1253 | m_anyType->contentType()->setParticle(outerParticle); |
1254 | m_anyType->setAttributeWildcard(wildcard); |
1255 | m_anyType->setIsAbstract(false); |
1256 | |
1257 | return m_anyType; |
1258 | } |
1259 | |
1260 | QT_END_NAMESPACE |
1261 | |