void XsdSchemaDebugger::dumpParticle(const XsdParticle::Ptr &particle, int level) { QString prefix; prefix.fill(QLatin1Char(' '), level); qDebug("%s min=%s max=%s", qPrintable(prefix), qPrintable(QString::number(particle->minimumOccurs())), qPrintable(particle->maximumOccursUnbounded() ? QLatin1String("unbounded") : QString::number(particle->maximumOccurs()))); if (particle->term()->isElement()) { qDebug("%selement (%s)", qPrintable(prefix), qPrintable(XsdElement::Ptr(particle->term())->displayName(m_namePool))); } else if (particle->term()->isModelGroup()) { const XsdModelGroup::Ptr group(particle->term()); if (group->compositor() == XsdModelGroup::SequenceCompositor) { qDebug("%ssequence", qPrintable(prefix)); } else if (group->compositor() == XsdModelGroup::AllCompositor) { qDebug("%sall", qPrintable(prefix)); } else if (group->compositor() == XsdModelGroup::ChoiceCompositor) { qDebug("%schoice", qPrintable(prefix)); } for (int i = 0; i < group->particles().count(); ++i) dumpParticle(group->particles().at(i), level + 5); } else if (particle->term()->isWildcard()) { XsdWildcard::Ptr wildcard(particle->term()); qDebug("%swildcard (process=%d)", qPrintable(prefix), wildcard->processContents()); } }
bool XsdSchemaChecker::isValidParticleExtension(const XsdParticle::Ptr &extension, const XsdParticle::Ptr &base) const { // @see http://www.w3.org/TR/xmlschema11-1/#cos-particle-extend // 1 if (extension == base) return true; // 2 if (extension->minimumOccurs() == 1 && extension->maximumOccurs() == 1 && extension->maximumOccursUnbounded() == false) { if (extension->term()->isModelGroup()) { const XsdModelGroup::Ptr modelGroup = extension->term(); if (modelGroup->compositor() == XsdModelGroup::SequenceCompositor) { if (particleEqualsRecursively(modelGroup->particles().first(), base)) return true; } } } // 3 if (extension->minimumOccurs() == base->minimumOccurs()) { // 3.1 if (extension->term()->isModelGroup() && base->term()->isModelGroup()) { const XsdModelGroup::Ptr extensionGroup(extension->term()); const XsdModelGroup::Ptr baseGroup(base->term()); if (extensionGroup->compositor() == XsdModelGroup::AllCompositor && baseGroup->compositor() == XsdModelGroup::AllCompositor) { const XsdParticle::List extensionParticles = extensionGroup->particles(); const XsdParticle::List baseParticles = baseGroup->particles(); for (int i = 0; i < baseParticles.count() && i < extensionParticles.count(); ++i) { if (baseParticles.at(i) != extensionParticles.at(i)) return false; } } } } return false; }
bool XsdParticleChecker::isUPAConform(const XsdParticle::Ptr &particle, const NamePool::Ptr &namePool) { /** * In case we encounter an <xsd:all> element, don't construct a state machine, but use the approach * described at http://www.w3.org/TR/xmlschema-1/#non-ambig * Reason: For n elements inside the <xsd:all>, represented in the NDA, the state machine * constructs n! states in the DFA, which does not scale. */ if (particle->term()->isModelGroup()) { const XsdModelGroup::Ptr group(particle->term()); if (group->compositor() == XsdModelGroup::AllCompositor) return isUPAConformXsdAll(particle, namePool); } /** * The algorithm is implemented like described in http://www.ltg.ed.ac.uk/~ht/XML_Europe_2003.html#S2.2 */ // create a state machine for the given particle XsdStateMachine<XsdTerm::Ptr> stateMachine(namePool); XsdStateMachineBuilder builder(&stateMachine, namePool); const XsdStateMachine<XsdTerm::Ptr>::StateId endState = builder.reset(); const XsdStateMachine<XsdTerm::Ptr>::StateId startState = builder.buildParticle(particle, endState); builder.addStartState(startState); /* static int counter = 0; { QFile file(QString("/tmp/file_upa%1.dot").arg(counter)); file.open(QIODevice::WriteOnly); stateMachine.outputGraph(&file, "Base"); file.close(); } ::system(QString("dot -Tpng /tmp/file_upa%1.dot -o/tmp/file_upa%1.png").arg(counter).toLatin1().data()); */ const XsdStateMachine<XsdTerm::Ptr> dfa = stateMachine.toDFA(); /* { QFile file(QString("/tmp/file_upa%1dfa.dot").arg(counter)); file.open(QIODevice::WriteOnly); dfa.outputGraph(&file, "Base"); file.close(); } ::system(QString("dot -Tpng /tmp/file_upa%1dfa.dot -o/tmp/file_upa%1dfa.png").arg(counter).toLatin1().data()); */ const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> states = dfa.states(); const QHash<XsdStateMachine<XsdTerm::Ptr>::StateId, QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > > transitions = dfa.transitions(); // the basic idea of that algorithm is to iterate over all states of that machine and check that no two edges // that match on the same term leave a state, so for a given term it should always be obvious which edge to take QHashIterator<XsdStateMachine<XsdTerm::Ptr>::StateId, XsdStateMachine<XsdTerm::Ptr>::StateType> stateIt(states); while (stateIt.hasNext()) { // iterate over all states stateIt.next(); // fetch all transitions the current state allows const QHash<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > currentTransitions = transitions.value(stateIt.key()); QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > transitionIt(currentTransitions); while (transitionIt.hasNext()) { // iterate over all transitions transitionIt.next(); if (transitionIt.value().size() > 1) { // we have one state with two edges leaving it, that means // the XsdTerm::Ptr exists twice, that is an error return false; } QHashIterator<XsdTerm::Ptr, QVector<XsdStateMachine<XsdTerm::Ptr>::StateId> > innerTransitionIt(currentTransitions); while (innerTransitionIt.hasNext()) { // iterate over all transitions again, as we have to compare all transitions with all innerTransitionIt.next(); if (transitionIt.key() == innerTransitionIt.key()) // do no compare with ourself continue; // use the helper method termMatches to check if both term matches if (termMatches(transitionIt.key(), innerTransitionIt.key(), namePool)) return false; } } } return true; }