xs:ID, xs:IDREF, and xs:IDREFS were validated only lexically (as NCNames); the document-wide rules that every ID is unique and every IDREF/IDREFS item matches some ID were not enforced, so duplicate IDs and dangling references passed. The tree validator now records ID and reference values during its typed walk over elements and attributes (a new IDTracker), and reports duplicate IDs and unresolved references after the whole document is seen, so a forward reference resolves against an ID declared later. XSTS: invalid instances accepted 179 to 166. Baseline updated; regression tests cover unique IDs, a forward reference, a duplicate ID, an unresolved IDREF, and IDREFS item resolution. The ID/IDREF helpers live in a ComplexValidator extension to keep the type within the file/body length limits. Refs #147, #129.
54 lines
2.2 KiB
Swift
54 lines
2.2 KiB
Swift
extension PureXML.Schema {
|
|
/// Accumulates `xs:ID` and `xs:IDREF`/`xs:IDREFS` values across an instance
|
|
/// document and reports the document-scoped violations XSD requires: every ID
|
|
/// is unique across the document, and every IDREF/IDREFS item matches some ID.
|
|
/// These are not simple-type (lexical) checks; they need the whole document,
|
|
/// so the tree validator records here during its typed walk and reports after.
|
|
final class IDTracker {
|
|
private var seenIDs: Set<String> = []
|
|
private var duplicates: [PureXML.Validation.ValidationError] = []
|
|
private var references: [(value: String, path: [PureXML.Validation.PathKey])] = []
|
|
|
|
func recordID(_ value: String, at path: [PureXML.Validation.PathKey]) {
|
|
if !seenIDs.insert(value).inserted {
|
|
duplicates.append(.init(reason: "duplicate ID '\(value)'", at: path))
|
|
}
|
|
}
|
|
|
|
func recordReference(_ value: String, at path: [PureXML.Validation.PathKey]) {
|
|
references.append((value, path))
|
|
}
|
|
|
|
/// Duplicate-ID errors plus an error for every reference with no matching
|
|
/// ID. Resolution runs after the whole document is seen, so a reference may
|
|
/// point at an ID declared later.
|
|
func errors() -> [PureXML.Validation.ValidationError] {
|
|
var result = duplicates
|
|
for reference in references where !seenIDs.contains(reference.value) {
|
|
result.append(.init(reason: "IDREF '\(reference.value)' has no matching ID", at: reference.path))
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PureXML.Schema.SimpleType {
|
|
/// Whether this is `xs:ID` (or an atomic restriction of it).
|
|
var isID: Bool {
|
|
if case .atomic = variety { return base == .id }
|
|
return false
|
|
}
|
|
|
|
/// Whether this is `xs:IDREF` (or an atomic restriction of it).
|
|
var isIDReference: Bool {
|
|
if case .atomic = variety { return base == .idref }
|
|
return false
|
|
}
|
|
|
|
/// Whether this is `xs:IDREFS` (a list whose item type is `xs:IDREF`).
|
|
var isIDReferenceList: Bool {
|
|
if case let .list(item) = variety { return item.base == .idref }
|
|
return false
|
|
}
|
|
}
|