The attribute-use uniqueness check (ag-props-correct.2 / ct-props-correct.4)
flattens a complex type's or attribute group's uses through attributeGroup
references. A reference to a redefined group whose redefinition self-references its
former self stopped at the cycle guard, so the original's attributes were never
reached and a duplicate they cause was missed.
Expand a redefinition's self-reference to the group's pre-redefine base definition
(already retained in the context for this purpose) so the original's attributes
join the set; a genuine non-redefine cycle still terminates without contributing.
Separately, exclude use="prohibited" attributes from the duplicate-name and
single-ID counts: a prohibited use is a removal marker, not a member of
{attribute uses} (XSD 3.4.2 / 3.6.2). This is the spec-correct reading and keeps a
self-reference that re-imports an attribute and then prohibits it from being
counted as a duplicate.
This catches W3C XSTS attQ011 and attQ017. XSTS schema invalid-accepted 22 -> 20,
valid-rejected held at 0.
243 lines
14 KiB
Swift
243 lines
14 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// A redefinition of an attribute group restricts the original (src-redefine), so
|
|
/// it may neither eliminate a required attribute nor re-introduce a prohibited one
|
|
/// as usable. Keeping a required attribute, and re-declaring a prohibited attribute
|
|
/// as prohibited, are both valid (W3C attgZ002/attgZ003, schT3).
|
|
@Suite("redefined attribute group restriction")
|
|
struct SchemaRedefineAttributeGroupTests {
|
|
private func redefine(group body: String, base: String) throws -> PureXML.Schema.Document {
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>\(body)</xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='g'>\(base)</xs:attributeGroup></xs:schema>"
|
|
return try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
|
|
@Test("eliminating a required attribute is rejected")
|
|
func test_dropRequiredRejected() throws {
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attribute name='b' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' use='required' type='xs:string'/><xs:attribute name='b' type='xs:string'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("re-introducing a prohibited attribute as usable is rejected")
|
|
func test_reintroduceProhibitedRejected() throws {
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attribute name='a' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' use='prohibited' type='xs:string'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("keeping a required attribute and re-prohibiting a prohibited one is valid")
|
|
func test_validRedefinitionAccepted() throws {
|
|
_ = try redefine(
|
|
group: "<xs:attribute name='a' use='required' type='xs:string'/><xs:attribute name='p' use='prohibited' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' use='required' type='xs:string'/><xs:attribute name='p' use='prohibited' type='xs:string'/><xs:attribute name='o' type='xs:string'/>",
|
|
)
|
|
}
|
|
|
|
@Test("relaxing a fixed value in the redefinition is rejected")
|
|
func test_relaxFixedRejected() throws {
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attribute name='a' type='xs:string' default='abc'/>",
|
|
base: "<xs:attribute name='a' type='xs:string' fixed='abc'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("keeping the same fixed value in the redefinition is valid")
|
|
func test_keepFixedAccepted() throws {
|
|
_ = try redefine(
|
|
group: "<xs:attribute name='a' type='xs:string' fixed='abc'/>",
|
|
base: "<xs:attribute name='a' type='xs:string' fixed='abc'/>",
|
|
)
|
|
}
|
|
|
|
@Test("adding an attribute not in the original is rejected (superset)")
|
|
func test_addAttributeRejected() throws {
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attribute name='a' type='xs:string'/><xs:attribute name='b' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' type='xs:string'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("declaring a prohibited attribute the base lacks is valid (not a superset)")
|
|
func test_addProhibitedAccepted() throws {
|
|
_ = try redefine(
|
|
group: "<xs:attribute name='a' type='xs:string'/><xs:attribute name='z' use='prohibited' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' type='xs:string'/>",
|
|
)
|
|
}
|
|
|
|
@Test("re-declaring an attribute the self-reference already supplies is rejected (schM8)")
|
|
func test_selfRefRedeclareDuplicateRejected() throws {
|
|
// The self-reference re-imports the original's `a`, so directly re-declaring `a`
|
|
// produces two attribute uses of the same name: a duplicate (ag-props-correct.2).
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attributeGroup ref='g'/><xs:attribute name='a' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' type='xs:string'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("a self-reference re-declaring a prohibited attribute is not flagged as duplicate (lenient)")
|
|
func test_selfRefRedeclareProhibitedLenient() throws {
|
|
// A `use="prohibited"` re-declaration removes rather than duplicates, so the
|
|
// duplicate check declines; the base attribute is optional, so this compiles.
|
|
_ = try redefine(
|
|
group: "<xs:attributeGroup ref='g'/><xs:attribute name='a' use='prohibited' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' type='xs:string'/>",
|
|
)
|
|
}
|
|
|
|
@Test("adding an attribute alongside a self-reference is valid")
|
|
func test_addWithSelfReferenceAccepted() throws {
|
|
_ = try redefine(
|
|
group: "<xs:attributeGroup ref='g'/><xs:attribute name='b' type='xs:string'/>",
|
|
base: "<xs:attribute name='a' type='xs:string'/>",
|
|
)
|
|
}
|
|
|
|
@Test("a duplicate attribute via a redefined self-referencing group is rejected (attQ011)")
|
|
func test_redefineSelfRefDuplicateAcrossReferences() throws {
|
|
// attG references the global `foo` AND the redefined `red`; `red` self-references,
|
|
// so its original (which declares `foo`) is re-imported. attG then holds two
|
|
// attribute uses named `foo`: a duplicate (ag-props-correct.2).
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='red'>"
|
|
+ "<xs:attributeGroup ref='red'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:attribute name='foo' type='xs:string'/>"
|
|
+ "<xs:attributeGroup name='attG'><xs:attribute ref='foo'/><xs:attributeGroup ref='red'/></xs:attributeGroup>"
|
|
+ "<xs:complexType name='ct'><xs:attributeGroup ref='attG'/></xs:complexType>"
|
|
+ "<xs:element name='e' type='ct'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='red'><xs:attribute name='foo' type='xs:string'/></xs:attributeGroup></xs:schema>"
|
|
#expect(throws: (any Error).self) { try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc }) }
|
|
}
|
|
|
|
@Test("a redefined self-referencing group supplying a distinct attribute is valid (no false duplicate)")
|
|
func test_redefineSelfRefDistinctAttributeAccepted() throws {
|
|
// Same shape, but `red`'s original declares `bar`, not `foo`: attG holds `foo`
|
|
// and `bar`, no duplicate, so the self-reference expansion must not over-flag.
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='red'>"
|
|
+ "<xs:attributeGroup ref='red'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:attribute name='foo' type='xs:string'/>"
|
|
+ "<xs:attributeGroup name='attG'><xs:attribute ref='foo'/><xs:attributeGroup ref='red'/></xs:attributeGroup>"
|
|
+ "<xs:complexType name='ct'><xs:attributeGroup ref='attG'/></xs:complexType>"
|
|
+ "<xs:element name='e' type='ct'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='red'><xs:attribute name='bar' type='xs:string'/></xs:attributeGroup></xs:schema>"
|
|
_ = try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
|
|
@Test("widening a re-declared attribute's built-in type is rejected (schM4)")
|
|
func test_widenBuiltinTypeRejected() throws {
|
|
// int does not derive by restriction from boolean, so re-typing the attribute
|
|
// from boolean to int widens it: a redefinition must restrict, not widen.
|
|
#expect(throws: (any Error).self) {
|
|
try redefine(
|
|
group: "<xs:attribute name='a' type='xs:int'/>",
|
|
base: "<xs:attribute name='a' type='xs:boolean'/>",
|
|
)
|
|
}
|
|
}
|
|
|
|
@Test("narrowing a re-declared attribute's built-in type within the lattice is valid")
|
|
func test_narrowBuiltinTypeAccepted() throws {
|
|
// int derives from integer, so re-typing integer -> int is a valid restriction.
|
|
_ = try redefine(
|
|
group: "<xs:attribute name='a' type='xs:int'/>",
|
|
base: "<xs:attribute name='a' type='xs:integer'/>",
|
|
)
|
|
}
|
|
|
|
@Test("re-typing an originally untyped attribute is valid (anySimpleType base)")
|
|
func test_retypeUntypedBaseAccepted() throws {
|
|
// An absent base type is anySimpleType, which any type validly restricts.
|
|
_ = try redefine(
|
|
group: "<xs:attribute name='a' type='xs:int'/>",
|
|
base: "<xs:attribute name='a'/>",
|
|
)
|
|
}
|
|
|
|
@Test("a user type whose local name coincides with a built-in is not mistaken for it (no false positive)")
|
|
func test_userTypeNamedLikeBuiltinNotWidened() throws {
|
|
// The redefinition re-types `a` to `tns:int`, a USER simpleType that restricts
|
|
// xs:string. Its local name is the built-in keyword `int`, but it resolves to
|
|
// the target namespace, not the XSD namespace, so the type-widening check must
|
|
// not treat it as xsd:int (which would not derive from xsd:string) and reject.
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:tns='urn:t' targetNamespace='urn:t'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>"
|
|
+ "<xs:attribute name='a' type='tns:int'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:simpleType name='int'><xs:restriction base='xs:string'/></xs:simpleType>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' targetNamespace='urn:t'>"
|
|
+ "<xs:attributeGroup name='g'><xs:attribute name='a' type='xs:string'/></xs:attributeGroup></xs:schema>"
|
|
_ = try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
|
|
@Test("a non-self reference injecting a foreign attribute is rejected (attgC028)")
|
|
func test_nonSelfReferenceAddRejected() throws {
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>"
|
|
+ "<xs:attributeGroup ref='car'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:attributeGroup name='car'><xs:attribute name='foo1' type='xs:int'/>"
|
|
+ "<xs:attribute name='foo2' type='xs:string'/></xs:attributeGroup>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='g'><xs:attribute name='att' type='xs:int'/></xs:attributeGroup></xs:schema>"
|
|
#expect(throws: (any Error).self) { try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc }) }
|
|
}
|
|
|
|
@Test("a non-self reference whose attributes are all in the original is valid")
|
|
func test_nonSelfReferenceSubsetAccepted() throws {
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>"
|
|
+ "<xs:attributeGroup ref='sub'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:attributeGroup name='sub'><xs:attribute name='att' type='xs:int'/></xs:attributeGroup>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='g'><xs:attribute name='att' type='xs:int'/>"
|
|
+ "<xs:attribute name='att2' type='xs:string'/></xs:attributeGroup></xs:schema>"
|
|
_ = try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
|
|
@Test("a referenced group with an attribute wildcard is not judged (lenient, no false positive)")
|
|
func test_nonSelfReferenceWildcardLenient() throws {
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>"
|
|
+ "<xs:attributeGroup ref='car'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:attributeGroup name='car'><xs:attribute name='foo1' type='xs:int'/>"
|
|
+ "<xs:anyAttribute/></xs:attributeGroup>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='g'><xs:attribute name='att' type='xs:int'/></xs:attributeGroup></xs:schema>"
|
|
_ = try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
|
|
@Test("re-declaring an attribute the base inherits via a reference is valid")
|
|
func test_redeclareInheritedAccepted() throws {
|
|
let main = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:redefine schemaLocation='b.xsd'><xs:attributeGroup name='g'>"
|
|
+ "<xs:attribute name='x' type='xs:string'/></xs:attributeGroup></xs:redefine>"
|
|
+ "<xs:element name='e'/></xs:schema>"
|
|
let baseDoc = "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>"
|
|
+ "<xs:attributeGroup name='inner'><xs:attribute name='x' type='xs:string'/></xs:attributeGroup>"
|
|
+ "<xs:attributeGroup name='g'><xs:attributeGroup ref='inner'/></xs:attributeGroup></xs:schema>"
|
|
_ = try PureXML.Schema.Document(main, schemaLoader: { _ in baseDoc })
|
|
}
|
|
}
|