PureXML/Sources/Schema/XSDParserHelpers.swift
Mihaela Mihaljevic be8a39394d fix(schema): advance XSTS valid-instances-rejected to 4 (#146)
Consolidates the in-progress #146 work; valid-instances-rejected reaches 4
on the 2006-11-06 XSTS archive (buckets now 1/262/4/147).

Regex: match Unicode scalars rather than grapheme clusters; the empty
pattern matches only the empty string; pin the category repertoire to
Unicode 3.2, the version the corpus is consistent at, so code points added
in 3.2 (Tagalog, Cyrillic Komi) are word characters while later additions
(the Tamil digit zero) stay unassigned and match \D. New XSDCategory and a
3.2 assignment table derived from DerivedAge, replacing the old Unicode31
files.

Content model: assess each child against the particle the content automaton
matched, not a by-name lookup (#180). Two same-named particles keep their
own fixed values, and an element matching a skip wildcard is no longer
validated against its global declaration. Selection is made reproducible
when an ambiguous schema slips past the UPA check.

XSTS runner: compile and merge every schemaDocument in a schemaTest, not
just the first; this also surfaced one previously-masked invalid-instance
acceptance (recorded in the baselines).

Also folds in attribute-group anyAttribute inheritance, extension wildcard
union, namespace-qualified type tables for multi-document
xsi:schemaLocation, per-container elementFormDefault, strict and lax
attribute wildcard validation, built-in type resolution, and xs:redefine
simpleType overrides.
2026-06-16 22:22:05 +02:00

234 lines
12 KiB
Swift

extension PureXML.Schema.XSDParser {
/// Gathers `nillable` and `default`/`fixed` value constraints from every
/// element declaration at any depth, keyed by the element's name.
static func elementMetadata(_ containers: [XSDTree]) -> (Set<String>, [String: PureXML.Schema.ValueConstraint]) {
var nillable: Set<String> = []
var constraints: [String: PureXML.Schema.ValueConstraint] = [:]
for container in containers {
for element in descendants(container, named: "element") {
guard let name = PureXML.Schema.XSDNode.attribute(element, "name") else { continue }
if PureXML.Schema.XSDNode.attribute(element, "nillable") == "true" { nillable.insert(name) }
if let constraint = valueConstraint(of: element) { constraints[name] = constraint }
}
}
return (nillable, constraints)
}
/// Gathers identity constraints (`unique`, `key`, `keyref`) declared on any
/// element at any depth, keyed by the element's name.
static func identityConstraints(_ containers: [XSDTree]) -> [String: [PureXML.Schema.IdentityConstraint]] {
var map: [String: [PureXML.Schema.IdentityConstraint]] = [:]
for container in containers {
for element in descendants(container, named: "element") {
guard let name = PureXML.Schema.XSDNode.attribute(element, "name") else { continue }
let constraints = PureXML.Schema.XSDNode.elementChildren(element).compactMap(constraint)
if !constraints.isEmpty { map[name, default: []] += constraints }
}
}
return map
}
/// Simple types for identity-constraint field paths, keyed by
/// ``identityFieldKey(constraint:field:)``.
static func identityFieldTypes(_ containers: [XSDTree], _ context: PureXML.Schema.XSDContext) -> [String: PureXML.Schema.SimpleType] {
var types: [String: PureXML.Schema.SimpleType] = [:]
for container in containers {
for element in descendants(container, named: "element") {
let constraints = PureXML.Schema.XSDNode.elementChildren(element).compactMap(constraint)
guard !constraints.isEmpty else { continue }
for constraint in constraints {
for field in constraint.fields {
if field == ".", selectorBranches(constraint.selector).count > 1 {
for branch in selectorBranches(constraint.selector) {
if let type = declaredElementSimpleType(named: branch, under: element, context) {
types[identityFieldKey(constraint: constraint, field: field, targetLocal: branch)] = type
}
}
continue
}
if let type = identityFieldType(field, host: element, constraint: constraint, context) {
types[identityFieldKey(constraint: constraint, field: field)] = type
}
}
}
}
}
return types
}
static func identityFieldKey(constraint: PureXML.Schema.IdentityConstraint, field: String, targetLocal: String? = nil) -> String {
if let targetLocal { return "\(constraint.name)|\(field)|\(targetLocal)" }
return "\(constraint.name)|\(field)"
}
private static func selectorBranches(_ selector: String) -> [String] {
let token = selector.split(separator: "/").last.map(String.init) ?? selector
guard token.contains("|") else {
return selectorTargetLocalName(selector).map { [$0] } ?? []
}
return token.split(separator: "|").compactMap { branch in
let local = PureXML.Schema.XSDNode.stripPrefix(String(branch))
return local.isEmpty ? nil : local
}
}
private static func identityFieldType(
_ field: String,
host: XSDTree,
constraint: PureXML.Schema.IdentityConstraint,
_ context: PureXML.Schema.XSDContext,
) -> PureXML.Schema.SimpleType? {
if field.hasPrefix("@") {
let attributeName = identityFieldAttributeName(field)
return attributeType(named: attributeName, under: host, context)
}
if field == "." {
let local = selectorTargetLocalName(constraint.selector)
return declaredElementSimpleType(named: local, under: host, context)
}
return nil
}
private static func identityFieldAttributeName(_ field: String) -> String {
let token = field.split(separator: "|").first.map(String.init) ?? field
guard token.hasPrefix("@") else { return String(field.dropFirst()) }
return String(token.dropFirst())
}
private static func selectorTargetLocalName(_ selector: String) -> String? {
let token = selector.split(separator: "/").last.map(String.init) ?? selector
return PureXML.Schema.XSDNode.stripPrefix(token)
}
private static func declaredElementSimpleType(
named local: String?,
under host: XSDTree,
_ context: PureXML.Schema.XSDContext,
) -> PureXML.Schema.SimpleType? {
guard let local else { return elementSimpleType(under: host, context) }
guard let complex = PureXML.Schema.XSDNode.firstChild(host, named: "complexType") else { return nil }
for child in descendants(complex, named: "element") {
guard PureXML.Schema.XSDNode.attribute(child, "name") == local,
let typeName = PureXML.Schema.XSDNode.attribute(child, "type")
else { continue }
let stripped = PureXML.Schema.XSDNode.stripPrefix(typeName)
let uri = PureXML.Schema.XSDNode.referenceNamespace(typeName, context.namespaceBindings)
if uri == PureXML.Schema.XSDParser.xsdNamespace, let builtin = PureXML.Schema.BuiltinType(rawValue: stripped) {
return PureXML.Schema.SimpleType(base: builtin)
}
}
return nil
}
private static func attributeType(named name: String, under host: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.SimpleType? {
for element in descendants(host, named: "element") {
guard let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType") else { continue }
for attribute in descendants(complex, named: "attribute") {
guard PureXML.Schema.XSDNode.attribute(attribute, "name") == name else { continue }
if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context) {
return use.type
}
}
}
return nil
}
private static func elementSimpleType(under host: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.SimpleType? {
guard let complex = PureXML.Schema.XSDNode.firstChild(host, named: "complexType") else { return nil }
for child in descendants(complex, named: "element") {
guard let typeName = PureXML.Schema.XSDNode.attribute(child, "type") else { continue }
let local = PureXML.Schema.XSDNode.stripPrefix(typeName)
let uri = PureXML.Schema.XSDNode.referenceNamespace(typeName, context.namespaceBindings)
if uri == PureXML.Schema.XSDParser.xsdNamespace, let builtin = PureXML.Schema.BuiltinType(rawValue: local) {
return PureXML.Schema.SimpleType(base: builtin)
}
}
return nil
}
private static func constraint(_ node: XSDTree) -> PureXML.Schema.IdentityConstraint? {
let kind: PureXML.Schema.IdentityConstraintKind
switch PureXML.Schema.XSDNode.localName(node) {
case "unique": kind = .unique
case "key": kind = .key
case "keyref": kind = .keyref(refer: PureXML.Schema.XSDNode.stripPrefix(PureXML.Schema.XSDNode.attribute(node, "refer") ?? ""))
default: return nil
}
let selector = PureXML.Schema.XSDNode.firstChild(node, named: "selector").flatMap { PureXML.Schema.XSDNode.attribute($0, "xpath") } ?? ""
let fields = PureXML.Schema.XSDNode.children(node, named: "field").compactMap { PureXML.Schema.XSDNode.attribute($0, "xpath") }
return PureXML.Schema.IdentityConstraint(name: PureXML.Schema.XSDNode.attribute(node, "name") ?? "", kind: kind, selector: selector, fields: fields)
}
static func descendants(_ node: XSDTree, named name: String) -> [XSDTree] {
var result: [XSDTree] = []
for child in PureXML.Schema.XSDNode.elementChildren(node) {
if PureXML.Schema.XSDNode.localName(child) == name { result.append(child) }
result += descendants(child, named: name)
}
return result
}
/// The qualified name of a local element declaration: in the target namespace
/// when `elementFormDefault` (or the element's own `form`) is qualified,
/// otherwise in no namespace.
static func localElementName(_ name: String, _ form: String?, _ context: PureXML.Schema.XSDContext) -> PureXML.Model.QualifiedName {
let qualified = form == "qualified" || (form == nil && context.elementFormQualified)
return PureXML.Model.QualifiedName(localName: name, namespaceURI: qualified ? context.targetNamespace : nil)
}
static func elementKey(_ name: String) -> String {
"element:\(name)"
}
/// The local name from a type-table key (`type:{namespace}local` or a legacy bare name).
static func bareTypeLocalName(_ reference: String) -> String {
guard reference.hasPrefix("type:") else { return reference }
let key = String(reference.dropFirst("type:".count))
guard let close = key.firstIndex(of: "}") else { return key }
return String(key[key.index(after: close)...])
}
/// The type-table key for a global type declaration in a specific namespace.
static func typeDeclarationKey(_ localName: String, namespaceURI: String?) -> String {
"type:\(PureXML.Schema.ComplexValidator.key(PureXML.Model.QualifiedName(localName: localName, namespaceURI: namespaceURI)))"
}
/// The type-table key for a global element declaration in a specific namespace.
static func elementDeclarationKey(_ name: PureXML.Model.QualifiedName) -> String {
"element:\(PureXML.Schema.ComplexValidator.key(name))"
}
/// The lookup key for a global attribute declaration in a specific namespace.
static func attributeDeclarationKey(_ name: PureXML.Model.QualifiedName) -> String {
"attribute:\(PureXML.Schema.ComplexValidator.key(name))"
}
static func indexByName(_ nodes: [XSDTree]) -> [String: XSDTree] {
var index: [String: XSDTree] = [:]
for node in nodes {
if let name = PureXML.Schema.XSDNode.attribute(node, "name") { index[name] = node }
}
return index
}
static func elementTypeName(_ node: XSDTree) -> String? {
if let typeName = PureXML.Schema.XSDNode.attribute(node, "type") {
return PureXML.Schema.XSDNode.stripPrefix(typeName)
}
if PureXML.Schema.XSDNode.firstChild(node, named: "simpleType") != nil || PureXML.Schema.XSDNode.firstChild(node, named: "complexType") != nil {
return nil
}
return "anyType"
}
static func derivation(_ node: XSDTree) -> XSDTree? {
PureXML.Schema.XSDNode.firstChild(node, named: "restriction") ?? PureXML.Schema.XSDNode.firstChild(node, named: "extension")
}
static func valueConstraint(of node: XSDTree) -> PureXML.Schema.ValueConstraint? {
if let fixed = PureXML.Schema.XSDNode.attribute(node, "fixed") { return .fixed(fixed) }
if let value = PureXML.Schema.XSDNode.attribute(node, "default") { return .default(value) }
return nil
}
}