fix(schema): reject global form in included/imported documents
The top-level declaration constraints (a global element/attribute may not carry the local-only form/use/ref/minOccurs/maxOccurs; a global definition needs a name) apply to every document in a schema composition, but the existing check ran only over the document being compiled. A violation inside an xs:include, xs:import, or xs:redefine target was therefore missed and the composed schema wrongly accepted. The same global-component check now runs over the other schema-document roots in the composition (the main document is excluded as already covered; redefine entries are skipped because each schema root walks its own redefine children). The constraint is absolute and position-independent, so no valid schema is newly rejected; libxml2 rejects both cases for the same reason. Catches attQ016 (import) and attQ018 (include). XSTS invalid-schemas- accepted 20 to 18, valid-schemas-rejected held at 0.
This commit is contained in:
parent
0ff46f6b78
commit
1c836d50f2
@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A top-level (global) element or attribute declaration carrying a `form` attribute is now rejected even when it lives in an INCLUDED or IMPORTED document. The `form` attribute is permitted only on a local declaration (XSD 1.0 schema-for-schemas: `topLevelAttribute`/`topLevelElement` have no `form`, since a global component's qualification is fixed); the check (`topLevelDeclarationFindings`, which also enforces a global needing a `name` and forbids the other local-only `use`/`ref`/`minOccurs`/`maxOccurs`) already existed but ran only over the document being compiled, so a violation in an `xs:include`/`xs:import`/`xs:redefine` target was missed and the composed schema wrongly accepted. The same global-component check now runs over the other schema-document roots in the composition (the main document is excluded as already covered, and `redefine` entries are skipped because each schema root walks its own `redefine` children). The constraint is absolute and document-position-independent, so no valid schema is newly rejected; libxml2 rejects both cases for the same reason. Catches attQ016 (import) and attQ018 (include). XSTS invalid-schemas-accepted 20 → 18, valid-schemas-rejected held at 0.
|
||||
- An identity-constraint field whose DECLARED target is a complex type with non-simple content is now rejected even when the instance node has no element children. The earlier fix rejected a field whose selected node carries element children (structural); this adds the case where the field's declared type is an empty or element-only/mixed complex type (an attributes-only element resolves to an empty-content complex type, so its instance node is childless yet not simple-typed). The field's target type is resolved through the schema type model (the selector target's type, then the field's same-named child element); a simple or simpleContent target, a type reached only through a named reference, a complex field path, or an unresolvable target is left alone, so no valid field is rejected. Catches idG006. XSTS invalid-instances-accepted 23 → 22, valid-instances-rejected held at 0.
|
||||
- A substitution-group `block` on a head element now bars substituting a member whose INLINE (anonymous) type reaches the head's type by a blocked method. The substitution-group filter already dropped a member when the head's `{disallowed substitutions}` (or the head type's `{prohibited substitutions}`) blocked the member's derivation method, but only for members with a NAMED type recorded in the derivation backbone; an element with an inline complex type had no backbone entry, so a member extending the head's type was wrongly kept even when the head blocked `extension`. Inline-typed elements now record their derivation under a synthetic key (an element key plus a `#` marker no QName can contain, so it cannot collide with a real type and is unreachable as any other derivation base), so the filter sees the inline member's method. A member reaching the head's type by an UNblocked method, a plain inline type with no derivation, and same-local-name elements in different namespaces are all unaffected. Catches disallowedSubst00105m (and a sibling). XSTS invalid-instances-accepted 25 → 23, valid-instances-rejected held at 0.
|
||||
- An identity-constraint (`key`/`unique`/`keyref`) field that identifies a node with complex content is now rejected during instance validation. Per cvc-identity-constraint.3 / c-fields-xpaths a field must evaluate to a node with a simple type (a simple-typed element, an element with simple content, or an attribute); the cardinality clause (at most one node) was enforced, but the simple-content clause was not. The check is structural and false-positive-free: a field whose selected node is an element carrying element children has complex (element-only or mixed) content and can never be simple-typed, so it is flagged, while a simple-typed or simple-content element (text only), an empty or text-only node, and an attribute are left alone. Catches idK012. XSTS invalid-instances-accepted 26 → 25, valid-instances-rejected held at 0.
|
||||
|
||||
@ -63,6 +63,23 @@ extension PureXML.Validation {
|
||||
}
|
||||
}
|
||||
|
||||
/// The top-level declaration constraints (a global definition needs a `name`;
|
||||
/// the local-only `form`/`use`/`ref`/`minOccurs`/`maxOccurs` are forbidden on
|
||||
/// a global element or attribute) apply to every schema document, not only the
|
||||
/// one the validation is rooted at. `schemaStructureValid` walks just
|
||||
/// `document.schema`, so a violation that lives in an included or imported
|
||||
/// document was missed; this runs the same global-component check over the
|
||||
/// other document roots in the composition. The main schema is excluded (it is
|
||||
/// already covered) and `redefine` container entries are skipped (each schema
|
||||
/// root already walks its own `redefine` children).
|
||||
static var containerTopLevelDeclarationsValid: Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Top-level declarations in included or imported documents follow the global-component constraints") { document in
|
||||
document.containers
|
||||
.filter { $0 !== document.schema && PureXML.Schema.XSDNode.localName($0) == "schema" }
|
||||
.flatMap { PureXML.Schema.XSDParser.topLevelDeclarationFindings($0) }
|
||||
}
|
||||
}
|
||||
|
||||
static var componentNamesUnique: Validation<PureXML.Schema.SchemaCompileRoot, PureXML.Schema.SchemaCompileContext> {
|
||||
compileRule("Global schema component names are unique within their symbol spaces") { document in
|
||||
PureXML.Schema.XSDParser.componentNameFindings(document.schema, document.containers, document.context)
|
||||
@ -210,6 +227,7 @@ extension PureXML.Validation {
|
||||
Validator<PureXML.Schema.SchemaCompileContext>.defaults(
|
||||
nonReference: [
|
||||
AnyValidation(idAttributesValid), AnyValidation(schemaStructureValid),
|
||||
AnyValidation(containerTopLevelDeclarationsValid),
|
||||
AnyValidation(componentNamesUnique), AnyValidation(simpleTypeFinalControlsValid),
|
||||
AnyValidation(contentModelsDeterministic), AnyValidation(attributeDeclarationsNotInXSI),
|
||||
AnyValidation(referencedSchemasResolveToSchemas), AnyValidation(redefinitionsDeriveFromThemselves),
|
||||
|
||||
71
Tests/SchemaContainerTopLevelFormTests.swift
Normal file
71
Tests/SchemaContainerTopLevelFormTests.swift
Normal file
@ -0,0 +1,71 @@
|
||||
import Testing
|
||||
@testable import PureXML
|
||||
|
||||
/// The top-level (global) declaration constraints apply to every document in a
|
||||
/// schema composition, not only the one being compiled. The `form` attribute is
|
||||
/// permitted only on a LOCAL element or attribute declaration; a global one that
|
||||
/// carries it is invalid (XSD 1.0 schema-for-schemas: `topLevelAttribute` and
|
||||
/// `topLevelElement` have no `form`). When the offending declaration lives in an
|
||||
/// included or imported document the check used to be skipped, so the schema was
|
||||
/// wrongly accepted. Mirrors XSTS attQ016 (import) and attQ018 (include).
|
||||
@Suite("global form in included/imported documents")
|
||||
struct SchemaContainerTopLevelFormTests {
|
||||
private func compiles(_ main: String, _ files: [String: String]) -> Bool {
|
||||
(try? PureXML.Schema.Document(main, schemaLoader: { files[$0] })) != nil
|
||||
}
|
||||
|
||||
private let main = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="urn:t" xmlns:t="urn:t">
|
||||
<xsd:include schemaLocation="lib.xsd"/>
|
||||
<xsd:element name="doc" type="xsd:string"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
|
||||
@Test("an included document with form on a global attribute is rejected")
|
||||
func test_includedGlobalAttributeForm() {
|
||||
let lib = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t">
|
||||
<xsd:attribute name="ga" form="qualified"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
#expect(!compiles(main, ["lib.xsd": lib]))
|
||||
}
|
||||
|
||||
@Test("an included document with form on a global element is rejected")
|
||||
func test_includedGlobalElementForm() {
|
||||
let lib = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t">
|
||||
<xsd:element name="ge" form="qualified" type="xsd:string"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
#expect(!compiles(main, ["lib.xsd": lib]))
|
||||
}
|
||||
|
||||
@Test("the same global attribute without form keeps the composition valid")
|
||||
func test_includedGlobalAttributeNoForm() {
|
||||
let lib = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t">
|
||||
<xsd:attribute name="ga" type="xsd:string"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
#expect(compiles(main, ["lib.xsd": lib]))
|
||||
}
|
||||
|
||||
@Test("an imported document with form on a global attribute is rejected")
|
||||
func test_importedGlobalAttributeForm() {
|
||||
let importer = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="urn:t" xmlns:t="urn:t">
|
||||
<xsd:import namespace="urn:i" schemaLocation="imp.xsd"/>
|
||||
<xsd:element name="doc" type="xsd:string"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
let imp = """
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:i">
|
||||
<xsd:attribute name="ia" form="qualified"/>
|
||||
</xsd:schema>
|
||||
"""
|
||||
#expect(!compiles(importer, ["imp.xsd": imp]))
|
||||
}
|
||||
}
|
||||
@ -87,6 +87,7 @@ struct ValidationConfigurationTests {
|
||||
#expect(PureXML.Validation.SchemaCompile.preCompileValidator().validationDescriptions == [
|
||||
"Schema component id attributes are valid NCNames and unique",
|
||||
"Schema vocabulary elements follow the schema-for-schemas structure",
|
||||
"Top-level declarations in included or imported documents follow the global-component constraints",
|
||||
"Global schema component names are unique within their symbol spaces",
|
||||
"Simple-type final controls are declared consistently",
|
||||
"Schema content models are deterministic (UPA)",
|
||||
|
||||
@ -31,7 +31,7 @@ struct XSTSSuiteTests {
|
||||
/// history of how each bucket reached its current value (every rule and its exact
|
||||
/// delta) lives in CHANGELOG.md and docs/xsts-deviations.md, not inline here.
|
||||
private let knownSchemaValidRejected = 0
|
||||
private let knownSchemaInvalidAccepted = 20
|
||||
private let knownSchemaInvalidAccepted = 18
|
||||
private let knownInstanceValidRejected = 0
|
||||
/// Instance bucket (133 -> 22), per-step deltas in CHANGELOG.md: xsi:type must derive from the declared
|
||||
/// type; anyType cannot stand in for anySimpleType; an untyped substitutionGroup member inherits its head's
|
||||
|
||||
Loading…
Reference in New Issue
Block a user