|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtXmlPatterns module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qxsdparticlechecker_p.h" |
|
43 |
|
44 #include "qxsdelement_p.h" |
|
45 #include "qxsdmodelgroup_p.h" |
|
46 #include "qxsdschemahelper_p.h" |
|
47 #include "qxsdstatemachine_p.h" |
|
48 #include "qxsdstatemachinebuilder_p.h" |
|
49 #include "qxsdtypechecker_p.h" |
|
50 |
|
51 #include <QtCore/QFile> |
|
52 |
|
53 QT_BEGIN_NAMESPACE |
|
54 |
|
55 using namespace QPatternist; |
|
56 |
|
57 namespace QPatternist |
|
58 { |
|
59 /** |
|
60 * This template specialization is picked up by XsdStateMachine and allows us |
|
61 * to print nice edge labels. |
|
62 */ |
|
63 template <> |
|
64 QString XsdStateMachine<XsdTerm::Ptr>::transitionTypeToString(XsdTerm::Ptr term) const |
|
65 { |
|
66 if (!term) |
|
67 return QLatin1String("(empty)"); |
|
68 |
|
69 if (term->isElement()) { |
|
70 return XsdElement::Ptr(term)->displayName(m_namePool); |
|
71 } else if (term->isWildcard()) { |
|
72 const XsdWildcard::Ptr wildcard(term); |
|
73 return QLatin1String("(wildcard)"); |
|
74 } else { |
|
75 return QString(); |
|
76 } |
|
77 } |
|
78 } |
|
79 |
|
80 /** |
|
81 * This method is used by the isUPAConform method to check whether @p term and @p otherTerm |
|
82 * are the same resp. match each other. |
|
83 */ |
|
84 static bool termMatches(const XsdTerm::Ptr &term, const XsdTerm::Ptr &otherTerm, const NamePool::Ptr &namePool) |
|
85 { |
|
86 if (term->isElement()) { |
|
87 const XsdElement::Ptr element(term); |
|
88 |
|
89 if (otherTerm->isElement()) { |
|
90 // both, the term and the other term are elements |
|
91 |
|
92 const XsdElement::Ptr otherElement(otherTerm); |
|
93 |
|
94 // if they have the same name they match |
|
95 if (element->name(namePool) == otherElement->name(namePool)) |
|
96 return true; |
|
97 |
|
98 } else if (otherTerm->isWildcard()) { |
|
99 // the term is an element and the other term a wildcard |
|
100 |
|
101 const XsdWildcard::Ptr wildcard(otherTerm); |
|
102 |
|
103 // wildcards using XsdWildcard::absentNamespace, so we have to fix that here |
|
104 QXmlName name = element->name(namePool); |
|
105 if (name.namespaceURI() == StandardNamespaces::empty) |
|
106 name.setNamespaceURI(namePool->allocateNamespace(XsdWildcard::absentNamespace())); |
|
107 |
|
108 // if the wildcards namespace constraint allows the elements name, they match |
|
109 if (XsdSchemaHelper::wildcardAllowsExpandedName(name, wildcard, namePool)) |
|
110 return true; |
|
111 } |
|
112 } else if (term->isWildcard()) { |
|
113 const XsdWildcard::Ptr wildcard(term); |
|
114 |
|
115 if (otherTerm->isElement()) { |
|
116 // the term is a wildcard and the other term an element |
|
117 |
|
118 const XsdElement::Ptr otherElement(otherTerm); |
|
119 |
|
120 // wildcards using XsdWildcard::absentNamespace, so we have to fix that here |
|
121 QXmlName name = otherElement->name(namePool); |
|
122 if (name.namespaceURI() == StandardNamespaces::empty) |
|
123 name.setNamespaceURI(namePool->allocateNamespace(XsdWildcard::absentNamespace())); |
|
124 |
|
125 // if the wildcards namespace constraint allows the elements name, they match |
|
126 if (XsdSchemaHelper::wildcardAllowsExpandedName(name, wildcard, namePool)) |
|
127 return true; |
|
128 |
|
129 } else if (otherTerm->isWildcard()) { |
|
130 // both, the term and the other term are wildcards |
|
131 |
|
132 const XsdWildcard::Ptr otherWildcard(otherTerm); |
|
133 |
|
134 // check if the range of the wildcard overlaps. |
|
135 const XsdWildcard::Ptr intersectionWildcard = XsdSchemaHelper::wildcardIntersection(wildcard, otherWildcard); |
|
136 if (!intersectionWildcard || |
|
137 (intersectionWildcard && !(intersectionWildcard->namespaceConstraint()->variety() != XsdWildcard::NamespaceConstraint::Not && intersectionWildcard->namespaceConstraint()->namespaces().isEmpty()))) |
|
138 return true; |
|
139 } |
|
140 } |
|
141 |
|
142 return false; |
|
143 } |
|
144 |
|
145 /** |
|
146 * This method is used by the subsumes algorithm to check whether the @p derivedTerm is validly derived from the @p baseTerm. |
|
147 * |
|
148 * @param baseTerm The term of the base component (type or group). |
|
149 * @param derivedTerm The term of the derived component (type or group). |
|
150 * @param particles A hash to map the passed base and derived term to the particles they belong to. |
|
151 * @param context The schema context. |
|
152 * @param errorMsg The error message in the case that an error occurs. |
|
153 */ |
|
154 static bool derivedTermValid(const XsdTerm::Ptr &baseTerm, const XsdTerm::Ptr &derivedTerm, const QHash<XsdTerm::Ptr, XsdParticle::Ptr> &particles, const XsdSchemaContext::Ptr &context, QString &errorMsg) |
|
155 { |
|
156 const NamePool::Ptr namePool(context->namePool()); |
|
157 |
|
158 // find the particles where the base and derived term belongs to |
|
159 const XsdParticle::Ptr baseParticle = particles.value(baseTerm); |
|
160 const XsdParticle::Ptr derivedParticle = particles.value(derivedTerm); |
|
161 |
|
162 // check that an empty particle can not be derived from a non-empty particle |
|
163 if (derivedParticle && baseParticle) { |
|
164 if (XsdSchemaHelper::isParticleEmptiable(derivedParticle) && !XsdSchemaHelper::isParticleEmptiable(baseParticle)) { |
|
165 errorMsg = QtXmlPatterns::tr("Empty particle cannot be derived from non-empty particle."); |
|
166 return false; |
|
167 } |
|
168 } |
|
169 |
|
170 if (baseTerm->isElement()) { |
|
171 const XsdElement::Ptr element(baseTerm); |
|
172 |
|
173 if (derivedTerm->isElement()) { |
|
174 // if both terms are elements |
|
175 |
|
176 const XsdElement::Ptr derivedElement(derivedTerm); |
|
177 |
|
178 // check names are equal |
|
179 if (element->name(namePool) != derivedElement->name(namePool)) { |
|
180 errorMsg = QtXmlPatterns::tr("Derived particle is missing element %1.").arg(formatKeyword(element->displayName(namePool))); |
|
181 return false; |
|
182 } |
|
183 |
|
184 // check value constraints are equal (if available) |
|
185 if (element->valueConstraint() && element->valueConstraint()->variety() == XsdElement::ValueConstraint::Fixed) { |
|
186 if (!derivedElement->valueConstraint()) { |
|
187 errorMsg = QtXmlPatterns::tr("Derived element %1 is missing value constraint as defined in base particle.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
188 return false; |
|
189 } |
|
190 |
|
191 if (derivedElement->valueConstraint()->variety() != XsdElement::ValueConstraint::Fixed) { |
|
192 errorMsg = QtXmlPatterns::tr("Derived element %1 has weaker value constraint than base particle.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
193 return false; |
|
194 } |
|
195 |
|
196 const QSourceLocation dummyLocation(QUrl(QLatin1String("http://dummy.org")), 1, 1); |
|
197 const XsdTypeChecker checker(context, QVector<QXmlName>(), dummyLocation); |
|
198 if (!checker.valuesAreEqual(element->valueConstraint()->value(), derivedElement->valueConstraint()->value(), derivedElement->type())) { |
|
199 errorMsg = QtXmlPatterns::tr("Fixed value constraint of element %1 differs from value constraint in base particle.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
200 return false; |
|
201 } |
|
202 } |
|
203 |
|
204 // check that a derived element can not be nillable if the base element is not nillable |
|
205 if (!element->isNillable() && derivedElement->isNillable()) { |
|
206 errorMsg = QtXmlPatterns::tr("Derived element %1 cannot be nillable as base element is not nillable.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
207 return false; |
|
208 } |
|
209 |
|
210 // check that the constraints of the derived element are more strict then the constraints of the base element |
|
211 const XsdElement::BlockingConstraints baseConstraints = element->disallowedSubstitutions(); |
|
212 const XsdElement::BlockingConstraints derivedConstraints = derivedElement->disallowedSubstitutions(); |
|
213 if (((baseConstraints & XsdElement::RestrictionConstraint) && !(derivedConstraints & XsdElement::RestrictionConstraint)) || |
|
214 ((baseConstraints & XsdElement::ExtensionConstraint) && !(derivedConstraints & XsdElement::ExtensionConstraint)) || |
|
215 ((baseConstraints & XsdElement::SubstitutionConstraint) && !(derivedConstraints & XsdElement::SubstitutionConstraint))) { |
|
216 errorMsg = QtXmlPatterns::tr("Block constraints of derived element %1 must not be more weaker than in the base element.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
217 return false; |
|
218 } |
|
219 |
|
220 // if the type of both elements is the same we can stop testing here |
|
221 if (element->type()->name(namePool) == derivedElement->type()->name(namePool)) |
|
222 return true; |
|
223 |
|
224 // check that the type of the derived element can validly derived from the type of the base element |
|
225 if (derivedElement->type()->isSimpleType()) { |
|
226 if (!XsdSchemaHelper::isSimpleDerivationOk(derivedElement->type(), element->type(), SchemaType::DerivationConstraints())) { |
|
227 errorMsg = QtXmlPatterns::tr("Simple type of derived element %1 cannot be validly derived from base element.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
228 return false; |
|
229 } |
|
230 } else if (derivedElement->type()->isComplexType()) { |
|
231 if (!XsdSchemaHelper::isComplexDerivationOk(derivedElement->type(), element->type(), SchemaType::DerivationConstraints())) { |
|
232 errorMsg = QtXmlPatterns::tr("Complex type of derived element %1 cannot be validly derived from base element.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
233 return false; |
|
234 } |
|
235 } |
|
236 |
|
237 // if both, derived and base element, have a complex type that contains a particle itself, apply the subsumes algorithm |
|
238 // recursive on their particles |
|
239 if (element->type()->isComplexType() && derivedElement->type()->isComplexType()) { |
|
240 if (element->type()->isDefinedBySchema() && derivedElement->type()->isDefinedBySchema()) { |
|
241 const XsdComplexType::Ptr baseType(element->type()); |
|
242 const XsdComplexType::Ptr derivedType(derivedElement->type()); |
|
243 if ((baseType->contentType()->variety() == XsdComplexType::ContentType::ElementOnly || |
|
244 baseType->contentType()->variety() == XsdComplexType::ContentType::Mixed) && |
|
245 (derivedType->contentType()->variety() == XsdComplexType::ContentType::ElementOnly || |
|
246 derivedType->contentType()->variety() == XsdComplexType::ContentType::Mixed)) { |
|
247 |
|
248 return XsdParticleChecker::subsumes(baseType->contentType()->particle(), derivedType->contentType()->particle(), context, errorMsg); |
|
249 } |
|
250 } |
|
251 } |
|
252 |
|
253 return true; |
|
254 } else if (derivedTerm->isWildcard()) { |
|
255 // derive a wildcard from an element is not allowed |
|
256 errorMsg = QtXmlPatterns::tr("Element %1 is missing in derived particle.").arg(formatKeyword(element->displayName(namePool))); |
|
257 return false; |
|
258 } |
|
259 } else if (baseTerm->isWildcard()) { |
|
260 const XsdWildcard::Ptr wildcard(baseTerm); |
|
261 |
|
262 if (derivedTerm->isElement()) { |
|
263 // the base term is a wildcard and derived term an element |
|
264 |
|
265 const XsdElement::Ptr derivedElement(derivedTerm); |
|
266 |
|
267 // wildcards using XsdWildcard::absentNamespace, so we have to fix that here |
|
268 QXmlName name = derivedElement->name(namePool); |
|
269 if (name.namespaceURI() == StandardNamespaces::empty) |
|
270 name.setNamespaceURI(namePool->allocateNamespace(XsdWildcard::absentNamespace())); |
|
271 |
|
272 // check that name of the element is allowed by the wildcards namespace constraint |
|
273 if (!XsdSchemaHelper::wildcardAllowsExpandedName(name, wildcard, namePool)) { |
|
274 errorMsg = QtXmlPatterns::tr("Element %1 does not match namespace constraint of wildcard in base particle.").arg(formatKeyword(derivedElement->displayName(namePool))); |
|
275 return false; |
|
276 } |
|
277 |
|
278 } else if (derivedTerm->isWildcard()) { |
|
279 // both, derived and base term are wildcards |
|
280 |
|
281 const XsdWildcard::Ptr derivedWildcard(derivedTerm); |
|
282 |
|
283 // check that the derived wildcard is a valid subset of the base wildcard |
|
284 if (!XsdSchemaHelper::isWildcardSubset(derivedWildcard, wildcard)) { |
|
285 errorMsg = QtXmlPatterns::tr("Wildcard in derived particle is not a valid subset of wildcard in base particle."); |
|
286 return false; |
|
287 } |
|
288 |
|
289 if (!XsdSchemaHelper::checkWildcardProcessContents(wildcard, derivedWildcard)) { |
|
290 errorMsg = QtXmlPatterns::tr("processContent of wildcard in derived particle is weaker than wildcard in base particle."); |
|
291 return false; |
|
292 } |
|
293 } |
|
294 |
|
295 return true; |
|
296 } |
|
297 |
|
298 return false; |
|
299 } |
|
300 |
|
301 typedef QHash<QXmlName, XsdElement::Ptr> ElementHash; |
|
302 |
|
303 /** |
|
304 * Internal helper method that checks if the given @p particle contains an element with the |
|
305 * same name and type twice. |
|
306 */ |
|
307 static bool hasDuplicatedElementsInternal(const XsdParticle::Ptr &particle, const NamePool::Ptr &namePool, ElementHash &hash, XsdElement::Ptr &conflictingElement) |
|
308 { |
|
309 const XsdTerm::Ptr term = particle->term(); |
|
310 if (term->isElement()) { |
|
311 const XsdElement::Ptr mainElement(term); |
|
312 XsdElement::List substGroups = mainElement->substitutionGroups(); |
|
313 if (substGroups.isEmpty()) |
|
314 substGroups << mainElement; |
|
315 |
|
316 for (int i = 0; i < substGroups.count(); ++i) { |
|
317 const XsdElement::Ptr element = substGroups.at(i); |
|
318 if (hash.contains(element->name(namePool))) { |
|
319 if (element->type()->name(namePool) != hash.value(element->name(namePool))->type()->name(namePool)) { |
|
320 conflictingElement = element; |
|
321 return true; |
|
322 } |
|
323 } else { |
|
324 hash.insert(element->name(namePool), element); |
|
325 } |
|
326 } |
|
327 } else if (term->isModelGroup()) { |
|
328 const XsdModelGroup::Ptr group(term); |
|
329 const XsdParticle::List particles = group->particles(); |
|
330 for (int i = 0; i < particles.count(); ++i) { |
|
331 if (hasDuplicatedElementsInternal(particles.at(i), namePool, hash, conflictingElement)) |
|
332 return true; |
|
333 } |
|
334 } |
|
335 |
|
336 return false; |
|
337 } |
|
338 |
|
339 bool XsdParticleChecker::hasDuplicatedElements(const XsdParticle::Ptr &particle, const NamePool::Ptr &namePool, XsdElement::Ptr &conflictingElement) |
|
340 { |
|
341 ElementHash hash; |
|
342 return hasDuplicatedElementsInternal(particle, namePool, hash, conflictingElement); |
|
343 } |
|
344 |
|
345 bool XsdParticleChecker::isUPAConform(const XsdParticle::Ptr &particle, const NamePool::Ptr &namePool) |
|
346 { |
|
347 /** |
|
348 * The algorithm is implemented like described in http://www.ltg.ed.ac.uk/~ht/XML_Europe_2003.html#S2.2 |
|
349 */ |
|
350 |
|
351 // create a state machine for the given particle |
|
352 XsdStateMachine<XsdTerm::Ptr> stateMachine(namePool); |
|
353 |
|
354 XsdStateMachineBuilder builder(&stateMachine, namePool); |
|
355 const XsdStateMachine<XsdTerm::Ptr>::StateId endState = builder.reset(); |
|
356 const XsdStateMachine<XsdTerm::Ptr>::StateId startState = builder.buildParticle(particle, endState); |
|
357 builder.addStartState(startState); |
|
358 |
|
359 /* |
|
360 static int counter = 0; |
|
361 { |
|
362 QFile file(QString("/tmp/file_upa%1.dot").arg(counter)); |
|
363 file.open(QIODevice::WriteOnly); |
|
364 stateMachine.outputGraph(&file, "Base"); |
|
365 file.close(); |
|
366 } |
|
367 ::system(QString("dot -Tpng /tmp/file_upa%1.dot -o/tmp/file_upa%1.png").arg(counter).toLatin1().data()); |
|
368 */ |
|
369 const XsdStateMachine<XsdTerm::Ptr> dfa = stateMachine.toDFA(); |
|
370 /* |
|
371 { |
|
372 QFile file(QString("/tmp/file_upa%1dfa.dot").arg(counter)); |
|
373 file.open(QIODevice::WriteOnly); |
|
374 dfa.outputGraph(&file, "Base"); |
|
375 file.close(); |
|
376 } |
|
377 ::system(QString("dot -Tpng /tmp/file_upa%1dfa.dot -o/tmp/file_upa%1dfa.png").arg(counter).toLatin1().data()); |
|
378 */ |
|
379 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> states = dfa.states(); |
|
380 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > > transitions = dfa.transitions(); |
|
381 |
|
382 // the basic idea of that algorithm is to iterate over all states of that machine and check that no two edges |
|
383 // that match on the same term leave a state, so for a given term it should always be obvious which edge to take |
|
384 QHashIterator<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> stateIt(states); |
|
385 while (stateIt.hasNext()) { // iterate over all states |
|
386 stateIt.next(); |
|
387 |
|
388 // fetch all transitions the current state allows |
|
389 const QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > currentTransitions = transitions.value(stateIt.key()); |
|
390 QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > transitionIt(currentTransitions); |
|
391 while (transitionIt.hasNext()) { // iterate over all transitions |
|
392 transitionIt.next(); |
|
393 |
|
394 if (transitionIt.value().size() > 1) { |
|
395 // we have one state with two edges leaving it, that means |
|
396 // the XsdTerm::Ptr exists twice, that is an error |
|
397 return false; |
|
398 } |
|
399 |
|
400 QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > innerTransitionIt(currentTransitions); |
|
401 while (innerTransitionIt.hasNext()) { // iterate over all transitions again, as we have to compare all transitions with all |
|
402 innerTransitionIt.next(); |
|
403 |
|
404 if (transitionIt.key() == innerTransitionIt.key()) // do no compare with ourself |
|
405 continue; |
|
406 |
|
407 // use the helper method termMatches to check if both term matches |
|
408 if (termMatches(transitionIt.key(), innerTransitionIt.key(), namePool)) |
|
409 return false; |
|
410 } |
|
411 } |
|
412 } |
|
413 |
|
414 return true; |
|
415 } |
|
416 |
|
417 bool XsdParticleChecker::subsumes(const XsdParticle::Ptr &particle, const XsdParticle::Ptr &derivedParticle, const XsdSchemaContext::Ptr &context, QString &errorMsg) |
|
418 { |
|
419 /** |
|
420 * The algorithm is implemented like described in http://www.ltg.ed.ac.uk/~ht/XML_Europe_2003.html#S2.3 |
|
421 */ |
|
422 |
|
423 const NamePool::Ptr namePool(context->namePool()); |
|
424 |
|
425 XsdStateMachine<XsdTerm::Ptr> baseStateMachine(namePool); |
|
426 XsdStateMachine<XsdTerm::Ptr> derivedStateMachine(namePool); |
|
427 |
|
428 // build up state machines for both particles |
|
429 { |
|
430 XsdStateMachineBuilder builder(&baseStateMachine, namePool); |
|
431 const XsdStateMachine<XsdTerm::Ptr>::StateId endState = builder.reset(); |
|
432 const XsdStateMachine<XsdTerm::Ptr>::StateId startState = builder.buildParticle(particle, endState); |
|
433 builder.addStartState(startState); |
|
434 |
|
435 baseStateMachine = baseStateMachine.toDFA(); |
|
436 } |
|
437 { |
|
438 XsdStateMachineBuilder builder(&derivedStateMachine, namePool); |
|
439 const XsdStateMachine<XsdTerm::Ptr>::StateId endState = builder.reset(); |
|
440 const XsdStateMachine<XsdTerm::Ptr>::StateId startState = builder.buildParticle(derivedParticle, endState); |
|
441 builder.addStartState(startState); |
|
442 |
|
443 derivedStateMachine = derivedStateMachine.toDFA(); |
|
444 } |
|
445 |
|
446 QHash<XsdTerm::Ptr, XsdParticle::Ptr> particlesHash = XsdStateMachineBuilder::particleLookupMap(particle); |
|
447 particlesHash.unite(XsdStateMachineBuilder::particleLookupMap(derivedParticle)); |
|
448 |
|
449 /* |
|
450 static int counter = 0; |
|
451 { |
|
452 QFile file(QString("/tmp/file_base%1.dot").arg(counter)); |
|
453 file.open(QIODevice::WriteOnly); |
|
454 baseStateMachine.outputGraph(&file, QLatin1String("Base")); |
|
455 file.close(); |
|
456 } |
|
457 { |
|
458 QFile file(QString("/tmp/file_derived%1.dot").arg(counter)); |
|
459 file.open(QIODevice::WriteOnly); |
|
460 derivedStateMachine.outputGraph(&file, QLatin1String("Base")); |
|
461 file.close(); |
|
462 } |
|
463 ::system(QString("dot -Tpng /tmp/file_base%1.dot -o/tmp/file_base%1.png").arg(counter).toLatin1().data()); |
|
464 ::system(QString("dot -Tpng /tmp/file_derived%1.dot -o/tmp/file_derived%1.png").arg(counter).toLatin1().data()); |
|
465 */ |
|
466 |
|
467 const XsdStateMachine<XsdTerm::Ptr>::StateId baseStartState = baseStateMachine.startState(); |
|
468 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> baseStates = baseStateMachine.states(); |
|
469 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > > baseTransitions = baseStateMachine.transitions(); |
|
470 |
|
471 const XsdStateMachine<XsdTerm::Ptr>::StateId derivedStartState = derivedStateMachine.startState(); |
|
472 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> derivedStates = derivedStateMachine.states(); |
|
473 const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > > derivedTransitions = derivedStateMachine.transitions(); |
|
474 |
|
475 // @see http://www.ltg.ed.ac.uk/~ht/XML_Europe_2003.html#S2.3.1 |
|
476 |
|
477 // define working set |
|
478 QList<QPair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId> > workSet; |
|
479 QList<QPair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId> > processedSet; |
|
480 |
|
481 // 1) fill working set initially with start states |
|
482 workSet.append(qMakePair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId>(baseStartState, derivedStartState)); |
|
483 processedSet.append(qMakePair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId>(baseStartState, derivedStartState)); |
|
484 |
|
485 while (!workSet.isEmpty()) { // while there are state sets to process |
|
486 |
|
487 // 3) dequeue on state set |
|
488 const QPair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId> set = workSet.takeFirst(); |
|
489 |
|
490 const QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > derivedTrans = derivedTransitions.value(set.second); |
|
491 QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > derivedIt(derivedTrans); |
|
492 |
|
493 const QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > baseTrans = baseTransitions.value(set.first); |
|
494 |
|
495 while (derivedIt.hasNext()) { |
|
496 derivedIt.next(); |
|
497 |
|
498 bool found = false; |
|
499 QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > baseIt(baseTrans); |
|
500 while (baseIt.hasNext()) { |
|
501 baseIt.next(); |
|
502 if (derivedTermValid(baseIt.key(), derivedIt.key(), particlesHash, context, errorMsg)) { |
|
503 const QPair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId> endSet = |
|
504 qMakePair<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateId>(baseIt.value().first(), derivedIt.value().first()); |
|
505 if (!processedSet.contains(endSet) && !workSet.contains(endSet)) { |
|
506 workSet.append(endSet); |
|
507 processedSet.append(endSet); |
|
508 } |
|
509 |
|
510 found = true; |
|
511 } |
|
512 } |
|
513 |
|
514 if (!found) { |
|
515 return false; |
|
516 } |
|
517 } |
|
518 } |
|
519 |
|
520 // 5) |
|
521 QHashIterator<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> it(derivedStates); |
|
522 while (it.hasNext()) { |
|
523 it.next(); |
|
524 |
|
525 if (it.value() == XsdStateMachine<XsdTerm::Ptr>::EndState || it.value() == XsdStateMachine<XsdTerm::Ptr>::StartEndState) { |
|
526 for (int i = 0; i < processedSet.count(); ++i) { |
|
527 if (processedSet.at(i).second == it.key() && |
|
528 (baseStates.value(processedSet.at(i).first) != XsdStateMachine<XsdTerm::Ptr>::EndState && |
|
529 baseStates.value(processedSet.at(i).first) != XsdStateMachine<XsdTerm::Ptr>::StartEndState)) { |
|
530 errorMsg = QtXmlPatterns::tr("Derived particle allows content that is not allowed in the base particle."); |
|
531 return false; |
|
532 } |
|
533 } |
|
534 } |
|
535 } |
|
536 |
|
537 return true; |
|
538 } |
|
539 |
|
540 QT_END_NAMESPACE |