Extending a complexType whose whole content is a non-empty xs:all group, via complexContent extension that adds element content, nests that all in the sequence joining base and extension content, which cos-all-limited forbids (an all group must be the whole content model). Such schemas were accepted. This is the symmetric companion to the existing rule (an all group in the extension over a base with content); both now live in one pass. An empty-all base contributes no content, and an attribute-only extension adds none, so both stay valid. XSTS invalid-schemas-accepted 233 to 232, no other bucket moved.
110 lines
6.7 KiB
Swift
110 lines
6.7 KiB
Swift
private typealias ExtAllNode = PureXML.Schema.XSDNode
|
|
|
|
extension PureXML.Schema.XSDParser {
|
|
/// XSD 1.0 `cos-all-limited.1.2` reached through extension: an `xs:all` group
|
|
/// must be the whole content model of a complex type. When a complex type
|
|
/// extends a base that already has element content, the effective content model
|
|
/// is `sequence(base content, extension content)`, so an `all` in the extension
|
|
/// is nested in that sequence and is invalid. Such schemas were accepted.
|
|
///
|
|
/// Detected from the compiled base type: the extension is rejected only when its
|
|
/// base resolves (in this schema's own target namespace) to a complex type whose
|
|
/// content is element-only or mixed. An empty base (extending it with an `all`
|
|
/// makes the `all` the whole content, which is valid), a simple-content base, or
|
|
/// an unresolved/foreign base is left alone. Checked for a self-contained schema.
|
|
static func extensionAllGroupErrors(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext, _ types: [String: PureXML.Schema.ElementType]) -> [String] {
|
|
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
|
let bindings = context.namespaceBindings
|
|
var errors: [String] = []
|
|
for content in descendants(schema, named: "complexContent") {
|
|
guard let ext = ExtAllNode.firstChild(content, named: "extension"),
|
|
let base = ExtAllNode.attribute(ext, "base"),
|
|
ExtAllNode.referenceNamespace(base, bindings) == context.targetNamespace,
|
|
case let .complex(complex)? = types[ExtAllNode.stripPrefix(base)]
|
|
else { continue }
|
|
let baseName = ExtAllNode.stripPrefix(base)
|
|
// The extension itself adds an `all` group over a base that already has
|
|
// element content: the all would be nested in the extension sequence.
|
|
if extensionHasAllGroup(ext), baseHasElementContent(complex.content) {
|
|
errors.append("an 'all' group may not extend the type '\(baseName)', which has its own content; an all group must be the whole content model")
|
|
continue
|
|
}
|
|
// The base's own content IS an `all` group and the extension adds
|
|
// element content: the base's all is then nested in the sequence that
|
|
// joins base and extension content, which is forbidden (cos-all-limited).
|
|
if contentIsAllGroup(complex.content), extensionAddsElementContent(ext) {
|
|
errors.append("the type '\(baseName)' has an 'all' group as its whole content and may not be extended with element content")
|
|
}
|
|
}
|
|
return errors
|
|
}
|
|
|
|
/// Whether a content type's whole content model is a NON-EMPTY `all` group. An
|
|
/// empty `<all/>` contributes no content (the sibling rule treats extending
|
|
/// empty content as valid), so it is not flagged.
|
|
private static func contentIsAllGroup(_ content: PureXML.Schema.ContentType) -> Bool {
|
|
switch content {
|
|
case let .elementOnly(particle), let .mixed(particle):
|
|
guard case let .group(group) = particle.term, group.compositor == .all else { return false }
|
|
return !PureXML.Schema.ParticleRestriction.contentFree(particle)
|
|
case .empty, .simpleContent:
|
|
return false
|
|
}
|
|
}
|
|
|
|
/// XSD 1.0 `cos-ct-extends.1.4.2.2`: a `complexContent` extension that adds
|
|
/// element content (a model group) produces an element-only/mixed content type,
|
|
/// so its base must have complex or empty content. A `simpleContent` base has a
|
|
/// simple content type and cannot gain element content this way. An extension
|
|
/// that adds only attributes keeps the base's simple content and is valid (it
|
|
/// is the `complexContent`-around-`simpleContent` idiom), so the model group is
|
|
/// required for the error. Resolved in this schema's own target namespace.
|
|
static func simpleContentExtensionBaseErrors(_ schema: XSDTree, _ context: PureXML.Schema.XSDContext, _ types: [String: PureXML.Schema.ElementType]) -> [String] {
|
|
guard !skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) else { return [] }
|
|
let bindings = context.namespaceBindings
|
|
var errors: [String] = []
|
|
for content in descendants(schema, named: "complexContent") {
|
|
guard let ext = ExtAllNode.firstChild(content, named: "extension"),
|
|
extensionAddsElementContent(ext),
|
|
let base = ExtAllNode.attribute(ext, "base"),
|
|
ExtAllNode.referenceNamespace(base, bindings) == context.targetNamespace,
|
|
case let .complex(complex)? = types[ExtAllNode.stripPrefix(base)],
|
|
case .simpleContent = complex.content
|
|
else { continue }
|
|
errors.append("a complexContent extension that adds element content may not extend '\(ExtAllNode.stripPrefix(base))', which has simple content")
|
|
}
|
|
return errors
|
|
}
|
|
|
|
/// Whether a `complexContent` extension adds element content: an `element` or
|
|
/// a wildcard `any` somewhere in its model group. An empty group (`<sequence/>`)
|
|
/// or an attribute-only extension adds no element content, so it keeps the
|
|
/// base's simple content and is not flagged.
|
|
private static func extensionAddsElementContent(_ extension: XSDTree) -> Bool {
|
|
let hasElement = descendants(`extension`, named: "element").contains { $0.name?.namespaceURI == xsdNamespace }
|
|
let hasWildcard = descendants(`extension`, named: "any").contains { $0.name?.namespaceURI == xsdNamespace }
|
|
return hasElement || hasWildcard
|
|
}
|
|
|
|
private static func extensionHasAllGroup(_ extension: XSDTree) -> Bool {
|
|
ExtAllNode.elementChildren(`extension`).contains { child in
|
|
child.name?.namespaceURI == xsdNamespace && ExtAllNode.localName(child) == "all"
|
|
}
|
|
}
|
|
|
|
/// Whether the base type's content would wrap an `all` extension in a sequence.
|
|
/// Mixed content always does (a mixed base, even with an empty particle, may not
|
|
/// be extended by an `all`, per the resolution of W3C Bug 6202). Element-only
|
|
/// content does only when its particle can actually contribute a child: an
|
|
/// explicit but empty model group (`<xs:sequence/>`) is effectively empty, and
|
|
/// extending empty content with an `all` makes the `all` the whole content,
|
|
/// which is valid. Empty and simple-content bases carry no element content.
|
|
private static func baseHasElementContent(_ content: PureXML.Schema.ContentType) -> Bool {
|
|
switch content {
|
|
case .mixed: true
|
|
case let .elementOnly(particle): !PureXML.Schema.ParticleRestriction.contentFree(particle)
|
|
case .empty, .simpleContent: false
|
|
}
|
|
}
|
|
}
|