diff --git a/PublicAPITests/PublicSurfaceTests.swift b/PublicAPITests/PublicSurfaceTests.swift
index 24e436d..c4cd9f0 100644
--- a/PublicAPITests/PublicSurfaceTests.swift
+++ b/PublicAPITests/PublicSurfaceTests.swift
@@ -39,7 +39,9 @@ struct PublicSurfaceTests {
var chunks = ["", "hello", ""].makeIterator()
var characters: [Character] = []
var reader = PureXML.Parsing.EventReader(pulling: {
- if characters.isEmpty, let chunk = chunks.next() { characters = Array(chunk) }
+ if characters.isEmpty, let chunk = chunks.next() {
+ characters = Array(chunk)
+ }
return characters.isEmpty ? nil : characters.removeFirst()
})
var events = 0
diff --git a/Sources/Canonical/CanonicalNodeSubset.swift b/Sources/Canonical/CanonicalNodeSubset.swift
index d9dd412..4697cca 100644
--- a/Sources/Canonical/CanonicalNodeSubset.swift
+++ b/Sources/Canonical/CanonicalNodeSubset.swift
@@ -18,7 +18,9 @@ extension PureXML.Canonical.Canonicalizer {
let declared = Set(apex.attributes.map(\.name.description))
for (prefix, uri) in ancestorNamespaces {
let name = prefix.isEmpty ? "xmlns" : "xmlns:\(prefix)"
- if !declared.contains(name) { attributes.append(PureXML.Model.Attribute(name, uri)) }
+ if !declared.contains(name) {
+ attributes.append(PureXML.Model.Attribute(name, uri))
+ }
}
}
return PureXML.Model.Element(name: apex.name, attributes: attributes, children: apex.children)
@@ -43,7 +45,9 @@ extension PureXML.Canonical.Canonicalizer {
/// apex does not already set, nearest ancestor winning.
static func inheritedXMLAttributes(above node: PureXML.Model.TreeNode, apex: PureXML.Model.Element, mergeBase: Bool) -> [PureXML.Model.Attribute] {
let present = Set(apex.attributes.map(\.name.description))
- if mergeBase { return inherited11XMLAttributes(above: node, apex: apex, present: present) }
+ if mergeBase {
+ return inherited11XMLAttributes(above: node, apex: apex, present: present)
+ }
// Canonical XML 1.0: the nearest of every in-scope xml:* attribute the
// apex does not already set.
var seen: Set = []
@@ -63,8 +67,12 @@ extension PureXML.Canonical.Canonicalizer {
/// The prefix a namespace declaration binds (empty for the default
/// namespace), or nil when the attribute is not a namespace declaration.
static func declaredPrefix(of name: PureXML.Model.QualifiedName) -> String? {
- if name.prefix == nil, name.localName == "xmlns" { return "" }
- if name.prefix == "xmlns" { return name.localName }
+ if name.prefix == nil, name.localName == "xmlns" {
+ return ""
+ }
+ if name.prefix == "xmlns" {
+ return name.localName
+ }
return nil
}
}
diff --git a/Sources/Canonical/CanonicalPrefixRewrite.swift b/Sources/Canonical/CanonicalPrefixRewrite.swift
index be5e0d8..f255767 100644
--- a/Sources/Canonical/CanonicalPrefixRewrite.swift
+++ b/Sources/Canonical/CanonicalPrefixRewrite.swift
@@ -208,8 +208,12 @@ extension PureXML.Canonical {
}
private static func declaredPrefix(of name: PureXML.Model.QualifiedName) -> String? {
- if name.prefix == nil, name.localName == "xmlns" { return "" }
- if name.prefix == "xmlns" { return name.localName }
+ if name.prefix == nil, name.localName == "xmlns" {
+ return ""
+ }
+ if name.prefix == "xmlns" {
+ return name.localName
+ }
return nil
}
}
diff --git a/Sources/Canonical/CanonicalURI.swift b/Sources/Canonical/CanonicalURI.swift
index 1e3009d..398c797 100644
--- a/Sources/Canonical/CanonicalURI.swift
+++ b/Sources/Canonical/CanonicalURI.swift
@@ -5,7 +5,9 @@ extension PureXML.Canonical.Canonicalizer {
static func inherited11XMLAttributes(above node: PureXML.Model.TreeNode, apex: PureXML.Model.Element, present: Set) -> [PureXML.Model.Attribute] {
var result: [PureXML.Model.Attribute] = []
for local in ["lang", "space"] where !present.contains("xml:\(local)") {
- if let attribute = nearestXMLAttribute(local, above: node) { result.append(attribute) }
+ if let attribute = nearestXMLAttribute(local, above: node) {
+ result.append(attribute)
+ }
}
if let base = mergedBase(above: node, apex: apex) {
result.append(PureXML.Model.Attribute("xml:base", base))
@@ -94,27 +96,35 @@ extension PureXML.Canonical.Canonicalizer {
var input = Substring(path)
var output = ""
while !input.isEmpty {
- if input.hasPrefix("../") { input = input.dropFirst(3)
+ if input.hasPrefix("../") {
+ input = input.dropFirst(3)
continue
}
- if input.hasPrefix("./") { input = input.dropFirst(2)
+ if input.hasPrefix("./") {
+ input = input.dropFirst(2)
continue
}
- if input.hasPrefix("/./") { input = "/" + input.dropFirst(3)
+ if input.hasPrefix("/./") {
+ input = "/" + input.dropFirst(3)
continue
}
- if input == "/." { input = "/"
+ if input == "/." {
+ input = "/"
continue
}
- if input.hasPrefix("/../") { input = "/" + input.dropFirst(4)
+ if input.hasPrefix("/../") {
+ input = "/" + input.dropFirst(4)
removeLastSegment(&output)
continue
}
- if input == "/.." { input = "/"
+ if input == "/.." {
+ input = "/"
removeLastSegment(&output)
continue
}
- if input == "." || input == ".." { break }
+ if input == "." || input == ".." {
+ break
+ }
let start = input.hasPrefix("/") ? input.index(after: input.startIndex) : input.startIndex
let next = input[start...].firstIndex(of: "/") ?? input.endIndex
output += input[.." }
+ if options.includeComments {
+ output += ""
+ }
case let .processingInstruction(target, data):
output += data.isEmpty ? "\(target)?>" : "\(target) \(data)?>"
}
@@ -149,7 +153,9 @@ public extension PureXML.Canonical {
let text = options.trimTextNodes ? value.trimmingXMLWhitespace() : value
output += Self.escapeText(text)
case let .comment(value):
- if options.includeComments { output += "" }
+ if options.includeComments {
+ output += ""
+ }
case let .processingInstruction(target, data):
output += data.isEmpty ? "\(target)?>" : "\(target) \(data)?>"
case .element, .document:
@@ -198,7 +204,9 @@ public extension PureXML.Canonical {
for prefix in utilized {
let uri = inScope[prefix] ?? ""
if prefix.isEmpty {
- if uri != (rendered[""] ?? "") { result.append(("", uri)) }
+ if uri != (rendered[""] ?? "") {
+ result.append(("", uri))
+ }
} else if !uri.isEmpty, rendered[prefix] != uri {
result.append((prefix, uri))
}
@@ -215,15 +223,21 @@ public extension PureXML.Canonical {
private static func attributeOrder(_ lhs: PureXML.Model.Attribute, _ rhs: PureXML.Model.Attribute) -> Bool {
let leftURI = lhs.name.namespaceURI ?? ""
let rightURI = rhs.name.namespaceURI ?? ""
- if leftURI != rightURI { return leftURI < rightURI }
+ if leftURI != rightURI {
+ return leftURI < rightURI
+ }
return lhs.name.localName < rhs.name.localName
}
private static func namespaceDeclarations(_ element: PureXML.Model.Element) -> [(String, String)] {
element.attributes.compactMap { attribute in
let name = attribute.name
- if name.prefix == nil, name.localName == "xmlns" { return ("", attribute.value) }
- if name.prefix == "xmlns" { return (name.localName, attribute.value) }
+ if name.prefix == nil, name.localName == "xmlns" {
+ return ("", attribute.value)
+ }
+ if name.prefix == "xmlns" {
+ return (name.localName, attribute.value)
+ }
return nil
}
}
@@ -327,7 +341,9 @@ public extension PureXML.Canonical.Canonicalizer {
case .text, .cdata:
output += Self.escapeText(options.trimTextNodes ? node.value.trimmingXMLWhitespace() : node.value)
case .comment:
- if options.includeComments { output += "" }
+ if options.includeComments {
+ output += ""
+ }
case .processingInstruction:
output += node.value.isEmpty ? "\(node.name?.description ?? "")?>" : "\(node.name?.description ?? "") \(node.value)?>"
case .element, .document, .doctype, .entityReference, .namespace:
diff --git a/Sources/Catalog/CatalogParser.swift b/Sources/Catalog/CatalogParser.swift
index 1e53240..93347ca 100644
--- a/Sources/Catalog/CatalogParser.swift
+++ b/Sources/Catalog/CatalogParser.swift
@@ -27,7 +27,9 @@ private struct CatalogBuilder {
insert(node, key: "systemId", base: base, into: &systemMap)
case "public":
insert(node, key: "publicId", base: base, into: &publicMap)
- if let id = attribute(node, "publicId") { publicPrefer[id] = prefer }
+ if let id = attribute(node, "publicId") {
+ publicPrefer[id] = prefer
+ }
case "uri":
insert(node, key: "name", base: base, into: &uriMap)
case "rewriteSystem":
@@ -62,7 +64,9 @@ private struct CatalogBuilder {
case "delegateURI":
appendDelegate(node, start: "uriStartString", base: base, into: &delegateURI)
case "nextCatalog":
- if let catalog = attribute(node, "catalog") { nextCatalogs.append(resolve(catalog, base)) }
+ if let catalog = attribute(node, "catalog") {
+ nextCatalogs.append(resolve(catalog, base))
+ }
default:
break
}
diff --git a/Sources/Catalog/Resolver.swift b/Sources/Catalog/Resolver.swift
index 2f4c0a6..59beae4 100644
--- a/Sources/Catalog/Resolver.swift
+++ b/Sources/Catalog/Resolver.swift
@@ -163,16 +163,22 @@ public extension PureXML.Catalog {
_ loadingCatalog: (String) -> String?,
_ visited: inout Set,
) -> String? {
- if let local = pick(self) { return local }
+ if let local = pick(self) {
+ return local
+ }
let matching = delegates
.filter { identifier.hasPrefix($0.startString) }
.sorted { $0.startString.count > $1.startString.count }
for rule in matching {
- if let sub = load(rule.catalog, loadingCatalog, &visited), let resolved = pick(sub) { return resolved }
+ if let sub = load(rule.catalog, loadingCatalog, &visited), let resolved = pick(sub) {
+ return resolved
+ }
}
for next in nextCatalogs {
guard let sub = load(next, loadingCatalog, &visited) else { continue }
- if let resolved = sub.chained(identifier, delegates: [], pick: pick, loadingCatalog, &visited) { return resolved }
+ if let resolved = sub.chained(identifier, delegates: [], pick: pick, loadingCatalog, &visited) {
+ return resolved
+ }
}
return nil
}
diff --git a/Sources/Catalog/SGMLCatalogParser.swift b/Sources/Catalog/SGMLCatalogParser.swift
index 3078d2d..ad7b9fb 100644
--- a/Sources/Catalog/SGMLCatalogParser.swift
+++ b/Sources/Catalog/SGMLCatalogParser.swift
@@ -27,16 +27,22 @@ private struct SGMLBuilder {
mutating func apply(_ keyword: String, _ scanner: inout SGMLScanner) {
let upper = keyword.uppercased()
- if applyPair(upper, &scanner) { return }
+ if applyPair(upper, &scanner) {
+ return
+ }
applySingle(upper, &scanner)
}
private mutating func applyPair(_ keyword: String, _ scanner: inout SGMLScanner) -> Bool {
switch keyword {
case "PUBLIC":
- if let id = scanner.next(), let uri = scanner.next() { publicMap[id] = resolve(uri) }
+ if let id = scanner.next(), let uri = scanner.next() {
+ publicMap[id] = resolve(uri)
+ }
case "SYSTEM":
- if let id = scanner.next(), let uri = scanner.next() { systemMap[id] = resolve(uri) }
+ if let id = scanner.next(), let uri = scanner.next() {
+ systemMap[id] = resolve(uri)
+ }
case "DELEGATE":
if let prefix = scanner.next(), let catalog = scanner.next() {
delegatePublic.append(PureXML.Catalog.DelegateRule(startString: prefix, catalog: resolve(catalog)))
@@ -50,11 +56,17 @@ private struct SGMLBuilder {
private mutating func applySingle(_ keyword: String, _ scanner: inout SGMLScanner) {
switch keyword {
case "CATALOG":
- if let catalog = scanner.next() { nextCatalogs.append(resolve(catalog)) }
+ if let catalog = scanner.next() {
+ nextCatalogs.append(resolve(catalog))
+ }
case "BASE":
- if let declared = scanner.next() { base = resolve(declared) }
+ if let declared = scanner.next() {
+ base = resolve(declared)
+ }
case "OVERRIDE":
- if let value = scanner.next() { preferPublic = value.uppercased() == "YES" }
+ if let value = scanner.next() {
+ preferPublic = value.uppercased() == "YES"
+ }
default:
break
}
diff --git a/Sources/Emitting/Escaping.swift b/Sources/Emitting/Escaping.swift
index 592d4a9..3be58ec 100644
--- a/Sources/Emitting/Escaping.swift
+++ b/Sources/Emitting/Escaping.swift
@@ -20,12 +20,16 @@ extension PureXML.Emitting {
while let marker = value.utf8[runStart...].firstIndex(where: {
textEscapableByte($0, asciiOnly: asciiOnly, escapeCarriageReturn: escapeCarriageReturn)
}) {
- if runStart < marker { result += value[runStart ..< marker] }
+ if runStart < marker {
+ result += value[runStart ..< marker]
+ }
let character = value[marker]
result += textEscape(character, asciiOnly: asciiOnly, escapeCarriageReturn: escapeCarriageReturn) ?? String(character)
runStart = value.index(after: marker)
}
- if runStart < value.endIndex { result += value[runStart...] }
+ if runStart < value.endIndex {
+ result += value[runStart...]
+ }
return result
}
@@ -98,12 +102,16 @@ extension PureXML.Emitting {
while let marker = value.utf8[runStart...].firstIndex(where: {
attributeEscapableByte($0, quoteByte: quoteByte, asciiOnly: asciiOnly)
}) {
- if runStart < marker { result += value[runStart ..< marker] }
+ if runStart < marker {
+ result += value[runStart ..< marker]
+ }
let character = value[marker]
result += attributeEscape(character, quote: quote, asciiOnly: asciiOnly) ?? String(character)
runStart = value.index(after: marker)
}
- if runStart < value.endIndex { result += value[runStart...] }
+ if runStart < value.endIndex {
+ result += value[runStart...]
+ }
return result
}
@@ -155,15 +163,21 @@ extension PureXML.Emitting {
static func comment(_ value: String) -> String {
// Fast path: a comment with no hyphen has no `--` and no trailing `-`,
// so it is returned unchanged rather than rebuilt character by character.
- if !value.utf8.contains(0x2D) { return value }
+ if !value.utf8.contains(0x2D) {
+ return value
+ }
var result = ""
var previousHyphen = false
for character in value {
- if character == "-", previousHyphen { result.append(" ") }
+ if character == "-", previousHyphen {
+ result.append(" ")
+ }
result.append(character)
previousHyphen = character == "-"
}
- if previousHyphen { result.append(" ") }
+ if previousHyphen {
+ result.append(" ")
+ }
return result
}
@@ -173,11 +187,15 @@ extension PureXML.Emitting {
static func processingInstruction(_ data: String) -> String {
// Fast path: data with no '?' cannot contain `?>`, so it is returned
// unchanged rather than rebuilt character by character.
- if !data.utf8.contains(0x3F) { return data }
+ if !data.utf8.contains(0x3F) {
+ return data
+ }
var result = ""
var previousQuestion = false
for character in data {
- if character == ">", previousQuestion { result.append(" ") }
+ if character == ">", previousQuestion {
+ result.append(" ")
+ }
result.append(character)
previousQuestion = character == "?"
}
diff --git a/Sources/HTML/HTMLAdoptionAgency.swift b/Sources/HTML/HTMLAdoptionAgency.swift
index 67612cb..e108861 100644
--- a/Sources/HTML/HTMLAdoptionAgency.swift
+++ b/Sources/HTML/HTMLAdoptionAgency.swift
@@ -37,8 +37,12 @@ extension HTMLDocument {
/// scope boundary.
private func hasInScope(_ target: PureXML.Model.TreeNode) -> Bool {
for node in openBody.reversed() {
- if node === target { return true }
- if Self.scopeBoundaries.contains(tagName(node)) { return false }
+ if node === target {
+ return true
+ }
+ if Self.scopeBoundaries.contains(tagName(node)) {
+ return false
+ }
}
return false
}
@@ -48,7 +52,9 @@ extension HTMLDocument {
/// re-wrapped (the `X` -> `X` behavior).
func reconstructActiveFormatting() {
guard var index = activeFormatting.indices.last else { return }
- if activeFormatting[index] == nil || isOpen(activeFormatting[index]) { return }
+ if activeFormatting[index] == nil || isOpen(activeFormatting[index]) {
+ return
+ }
while index > 0 {
index -= 1
if activeFormatting[index] == nil || isOpen(activeFormatting[index]) {
@@ -91,7 +97,9 @@ extension HTMLDocument {
activeFormatting.remove(at: formattingIndexInList)
return
}
- if !hasInScope(formattingElement) { return }
+ if !hasInScope(formattingElement) {
+ return
+ }
guard let furthestBlockIndex = openBody[(formattingOpenIndex + 1)...].firstIndex(where: { Self.special.contains(tagName($0)) }) else {
openBody.removeLast(openBody.count - formattingOpenIndex)
activeFormatting.remove(at: formattingIndexInList)
@@ -104,7 +112,9 @@ extension HTMLDocument {
private func lastFormatting(tag: String) -> Int? {
for index in activeFormatting.indices.reversed() {
guard let entry = activeFormatting[index] else { return nil } // a marker bounds the search
- if tagName(entry) == tag { return index }
+ if tagName(entry) == tag {
+ return index
+ }
}
return nil
}
@@ -124,7 +134,9 @@ extension HTMLDocument {
inner += 1
nodeIndex -= 1
let node = openBody[nodeIndex]
- if node === formattingElement { break }
+ if node === formattingElement {
+ break
+ }
guard let activeIndex = formattingIndex(node) else {
openBody.remove(at: nodeIndex)
continue
@@ -132,7 +144,9 @@ extension HTMLDocument {
let clone = node.shallowCopy()
activeFormatting[activeIndex] = clone
openBody[nodeIndex] = clone
- if lastNode === furthestBlock { bookmark = activeIndex + 1 }
+ if lastNode === furthestBlock {
+ bookmark = activeIndex + 1
+ }
clone.append(lastNode)
lastNode = clone
}
@@ -144,13 +158,19 @@ extension HTMLDocument {
furthestBlock.append(formattingClone)
if let removeIndex = formattingIndex(formattingElement) {
activeFormatting.remove(at: removeIndex)
- if removeIndex < bookmark { bookmark -= 1 }
+ if removeIndex < bookmark {
+ bookmark -= 1
+ }
}
activeFormatting.insert(formattingClone, at: min(bookmark, activeFormatting.count))
- if let removeOpen = openIndex(formattingElement) { openBody.remove(at: removeOpen) }
+ if let removeOpen = openIndex(formattingElement) {
+ openBody.remove(at: removeOpen)
+ }
// The clone becomes a child of the furthest block, so it sits immediately
// above it on the open-elements stack; inserting below would re-find the
// same furthest block and loop.
- if let blockIndex = openIndex(furthestBlock) { openBody.insert(formattingClone, at: blockIndex + 1) }
+ if let blockIndex = openIndex(furthestBlock) {
+ openBody.insert(formattingClone, at: blockIndex + 1)
+ }
}
}
diff --git a/Sources/HTML/HTMLDocumentBuilder.swift b/Sources/HTML/HTMLDocumentBuilder.swift
index ed63187..3cbba6c 100644
--- a/Sources/HTML/HTMLDocumentBuilder.swift
+++ b/Sources/HTML/HTMLDocumentBuilder.swift
@@ -84,8 +84,12 @@ final class HTMLDocument {
}
private func processInitial(_ token: Token) {
- if case .doctype = token { return }
- if isIgnorableWhitespace(token) { return }
+ if case .doctype = token {
+ return
+ }
+ if isIgnorableWhitespace(token) {
+ return
+ }
mode = .beforeHtml
process(token)
}
@@ -96,13 +100,17 @@ final class HTMLDocument {
mode = .beforeHead
return
}
- if isIgnorableWhitespace(token) { return }
+ if isIgnorableWhitespace(token) {
+ return
+ }
mode = .beforeHead
process(token)
}
private func processBeforeHead(_ token: Token) {
- if isIgnorableWhitespace(token) { return }
+ if isIgnorableWhitespace(token) {
+ return
+ }
if case let .startTag(name, attributes, _) = token, name == "head" {
headAttributes = modelAttributes(attributes)
mode = .inHead
@@ -140,7 +148,9 @@ final class HTMLDocument {
}
let element = PureXML.Model.TreeNode.element(name, attributes: modelAttributes(attributes))
openHead.last?.append(element)
- if !(PureXML.HTML.Elements.void.contains(name) || selfClosing) { openHead.append(element) }
+ if !(PureXML.HTML.Elements.void.contains(name) || selfClosing) {
+ openHead.append(element)
+ }
}
private func headClose(_ name: String) {
@@ -168,7 +178,9 @@ final class HTMLDocument {
process(token)
return
}
- if isIgnorableWhitespace(token) { return }
+ if isIgnorableWhitespace(token) {
+ return
+ }
mode = .inBody
process(token)
}
@@ -210,7 +222,11 @@ final class HTMLDocument {
case let .startTag(name, attributes, _) where name == "noframes":
framesetStack.last?.append(PureXML.Model.TreeNode.element("noframes", attributes: modelAttributes(attributes)))
case let .endTag(name) where name == "frameset":
- if framesetStack.count > 1 { framesetStack.removeLast() } else { mode = .afterFrameset }
+ if framesetStack.count > 1 {
+ framesetStack.removeLast()
+ } else {
+ mode = .afterFrameset
+ }
case let .comment(value):
framesetStack.last?.append(.comment(value))
default:
@@ -243,7 +259,9 @@ final class HTMLDocument {
private func bodyOpen(_ name: String, _ attributes: [(String, String)], selfClosing: Bool) {
if Self.closesSelect.contains(name), let selectIndex = openBody.lastIndex(where: { tagName($0) == "select" }), selectIndex >= 1 {
openBody.removeLast(openBody.count - selectIndex)
- if name == "select" { return } // a nested select just closes the open one
+ if name == "select" {
+ return
+ } // a nested select just closes the open one
}
if let selectIndex = selectInTableToClose(for: name) {
openBody.removeLast(openBody.count - selectIndex)
@@ -278,8 +296,12 @@ final class HTMLDocument {
case "tr" where context == "table":
bodyPushImplied("tbody")
case "td", "th":
- if context == "table" { bodyPushImplied("tbody") }
- if context == "table" || ["tbody", "thead", "tfoot"].contains(context) { bodyPushImplied("tr") }
+ if context == "table" {
+ bodyPushImplied("tbody")
+ }
+ if context == "table" || ["tbody", "thead", "tfoot"].contains(context) {
+ bodyPushImplied("tr")
+ }
default:
break
}
@@ -303,11 +325,17 @@ final class HTMLDocument {
/// The foreign-content namespace for a body element: SVG/MathML on entry, the
/// nearest open foreign ancestor's, or HTML (nil) inside an integration point.
private func bodyForeignNamespace(for name: String) -> String? {
- if name == "svg" { return ForeignNamespace.svg }
- if name == "math" { return ForeignNamespace.mathml }
+ if name == "svg" {
+ return ForeignNamespace.svg
+ }
+ if name == "math" {
+ return ForeignNamespace.mathml
+ }
for node in openBody.reversed() {
guard let namespace = node.name?.namespaceURI else { continue }
- if Self.svgIntegrationPoints.contains(tagName(node)) { return nil }
+ if Self.svgIntegrationPoints.contains(tagName(node)) {
+ return nil
+ }
return namespace
}
return nil
@@ -330,7 +358,9 @@ extension HTMLDocument {
}
private func isIgnorableWhitespace(_ token: Token) -> Bool {
- if case let .text(value) = token { return value.trimmingXMLWhitespace().isEmpty }
+ if case let .text(value) = token {
+ return value.trimmingXMLWhitespace().isEmpty
+ }
return false
}
}
diff --git a/Sources/HTML/HTMLEntities.swift b/Sources/HTML/HTMLEntities.swift
index d726e4e..85f718d 100644
--- a/Sources/HTML/HTMLEntities.swift
+++ b/Sources/HTML/HTMLEntities.swift
@@ -59,13 +59,17 @@ extension PureXML.HTML.Tokenizer {
private static func decodeNumeric(_ characters: [Character], at start: Int) -> (String, Int)? {
var cursor = start + 2 // past ""
let isHex = cursor < characters.count && (characters[cursor] == "x" || characters[cursor] == "X")
- if isHex { cursor += 1 }
+ if isHex {
+ cursor += 1
+ }
let digitsStart = cursor
while cursor < characters.count, isDigit(characters[cursor], hex: isHex) {
cursor += 1
}
guard cursor > digitsStart, let number = UInt32(String(characters[digitsStart ..< cursor]), radix: isHex ? 16 : 10) else { return nil }
- if cursor < characters.count, characters[cursor] == ";" { cursor += 1 }
+ if cursor < characters.count, characters[cursor] == ";" {
+ cursor += 1
+ }
return (String(numericCharacter(number)), cursor - start)
}
diff --git a/Sources/HTML/HTMLSerializer.swift b/Sources/HTML/HTMLSerializer.swift
index 3190392..83792fe 100644
--- a/Sources/HTML/HTMLSerializer.swift
+++ b/Sources/HTML/HTMLSerializer.swift
@@ -39,7 +39,9 @@ extension PureXML.HTML {
output += ">"
// A void element has no end tag and no content (the html model);
// anything nested is dropped, as the recursive form did.
- if Elements.void.contains(name.lowercased()) { return }
+ if Elements.void.contains(name.lowercased()) {
+ return
+ }
stack.append(HTMLSerializeStep.close(name))
// A raw-text element (script, style) writes its own text children
// verbatim; nested elements still serialize normally by their own
@@ -57,7 +59,9 @@ extension PureXML.HTML {
private static func attributeText(_ attribute: PureXML.Model.Attribute) -> String {
let name = attribute.name.description
- if attribute.value.isEmpty { return " \(name)" }
+ if attribute.value.isEmpty {
+ return " \(name)"
+ }
// Boolean attributes minimize when the value repeats the name
// (CHECKED="CHECKED" serializes as CHECKED), the html form.
if Elements.booleanAttributes.contains(name.lowercased()), attribute.value.lowercased() == name.lowercased() {
@@ -132,7 +136,9 @@ extension PureXML.HTML {
default: result += latin1Entity(scalar) ?? String(Character(scalar))
}
}
- if previousWasAmpersand { result += "&" }
+ if previousWasAmpersand {
+ result += "&"
+ }
return result
}
diff --git a/Sources/HTML/HTMLTokenizer.swift b/Sources/HTML/HTMLTokenizer.swift
index d3da723..d690d78 100644
--- a/Sources/HTML/HTMLTokenizer.swift
+++ b/Sources/HTML/HTMLTokenizer.swift
@@ -27,10 +27,18 @@ extension PureXML.HTML {
return rawText(until: raw)
}
guard peek() == "<" else { return text() }
- if matches("") { .comment(String($0)) } }
- if startsWith(buffer, "") { .cdata(String($0)) } }
- if startsWith(buffer, "") { _ in .ignorable } }
- if startsWith(buffer, "") { return scanEndTag(buffer) }
- if startsWith(buffer, "") { return scanProcessingInstruction(buffer) }
+ if startsWith(buffer, "") { .comment(String($0)) }
+ }
+ if startsWith(buffer, "") { .cdata(String($0)) }
+ }
+ if startsWith(buffer, "") { _ in .ignorable }
+ }
+ if startsWith(buffer, "") {
+ return scanEndTag(buffer)
+ }
+ if startsWith(buffer, "") {
+ return scanProcessingInstruction(buffer)
+ }
return scanStartTag(buffer, final: final)
}
@@ -162,7 +174,9 @@ extension PureXML.Parsing {
guard let end = tagEnd(buffer) else { return final ? .token(.text("<"), consumed: 1) : .needMore }
var inner = Array(buffer[1 ..< end])
let selfClosing = inner.last == "/"
- if selfClosing { inner.removeLast() }
+ if selfClosing {
+ inner.removeLast()
+ }
let (name, attributes) = parseTag(inner)
return .token(.open(name: name, attributes: attributes, selfClosing: selfClosing), consumed: end + 1)
}
@@ -201,7 +215,9 @@ extension PureXML.Parsing {
for index in 1 ..< buffer.count {
let character = buffer[index]
if let active = quote {
- if character == active { quote = nil }
+ if character == active {
+ quote = nil
+ }
} else if character == "\"" || character == "'" {
quote = character
} else if character == ">" {
diff --git a/Sources/Parsing/Reader.swift b/Sources/Parsing/Reader.swift
index f33ea3b..21d76fe 100644
--- a/Sources/Parsing/Reader.swift
+++ b/Sources/Parsing/Reader.swift
@@ -67,7 +67,9 @@ extension PureXML.Parsing {
// Split multi-scalar graphemes so the buffer is scalar-level.
var queued: [Character] = []
self.pull = {
- if !queued.isEmpty { return queued.removeFirst() }
+ if !queued.isEmpty {
+ return queued.removeFirst()
+ }
guard let next = pull() else { return nil }
let scalars = next.unicodeScalars
guard scalars.count > 1 else { return next }
@@ -133,10 +135,14 @@ extension PureXML.Parsing {
private mutating func normalizedPull() -> Character? {
guard let character = rawNext() else { return nil }
// Swift coalesces a CR+LF pair into one grapheme-cluster character.
- if character == "\r\n" { return "\n" }
+ if character == "\r\n" {
+ return "\n"
+ }
if character == "\r" {
let next = rawNext()
- if next != "\n", !(xml11 && next == "\u{85}") { pendingRaw = next }
+ if next != "\n", !(xml11 && next == "\u{85}") {
+ pendingRaw = next
+ }
return "\n"
}
if xml11, character == "\u{85}" || character == "\u{2028}" {
@@ -201,7 +207,9 @@ extension PureXML.Parsing {
/// True if the upcoming characters equal `literal`, without consuming.
mutating func matches(_ literal: String) -> Bool {
if buffer.isEmpty, pendingRaw == nil, let storage {
- if let byteResult = matchesBytes(literal, storage) { return byteResult }
+ if let byteResult = matchesBytes(literal, storage) {
+ return byteResult
+ }
}
// No per-call Array materialization: literals are short constants
// and this runs on every scan decision (the hottest comparison in
@@ -211,7 +219,9 @@ extension PureXML.Parsing {
guard available >= count else { return false }
var index = 0
for character in literal {
- if buffer[head + index] != character { return false }
+ if buffer[head + index] != character {
+ return false
+ }
index += 1
}
return true
@@ -254,10 +264,18 @@ extension PureXML.Parsing {
let limit = byteIndex + maxLength
while index < storage.count {
let byte = pointer[index]
- if byte >= 0x80 { return nil }
- if !PureXML.Parsing.XMLCharacter.isASCIINameChar(byte) { break }
- if index >= limit { return nil }
- if colon == nil, byte == 0x3A { colon = index - byteIndex }
+ if byte >= 0x80 {
+ return nil
+ }
+ if !PureXML.Parsing.XMLCharacter.isASCIINameChar(byte) {
+ break
+ }
+ if index >= limit {
+ return nil
+ }
+ if colon == nil, byte == 0x3A {
+ colon = index - byteIndex
+ }
index += 1
}
let length = index - byteIndex
@@ -289,7 +307,9 @@ extension PureXML.Parsing {
}
mutating func skipSpace() {
- if skipSpaceBytes() { return }
+ if skipSpaceBytes() {
+ return
+ }
while let character = peek(), character.isXMLWhitespace {
advance()
}
diff --git a/Sources/Parsing/ReaderByteRuns.swift b/Sources/Parsing/ReaderByteRuns.swift
index 7075bd1..1e6f340 100644
--- a/Sources/Parsing/ReaderByteRuns.swift
+++ b/Sources/Parsing/ReaderByteRuns.swift
@@ -22,19 +22,25 @@ extension PureXML.Parsing.Reader {
// the slow path consumed "]]" and the run would begin with '>': leave a
// leading '>' to the slow path and its cdataCloseInContent check (W3C
// ibm14n01).
- if byteIndex < count, pointer[byteIndex] == 0x3E { return nil }
+ if byteIndex < count, pointer[byteIndex] == 0x3E {
+ return nil
+ }
var index = byteIndex
var newlines = 0
var lastLineStart = -1
while index < count {
let byte = pointer[index]
- if byte == 0x3C { break } // '<'
+ if byte == 0x3C {
+ break
+ } // '<'
// Valid plain ASCII content only; CR, ']' and non-ASCII go to the
// character path.
guard byte < 0x80, byte != 0x0D, byte != 0x5D,
byte >= 0x20 || byte == 0x09 || byte == 0x0A
else {
- if index == byteIndex { return nil }
+ if index == byteIndex {
+ return nil
+ }
break
}
if byte == 0x0A {
diff --git a/Sources/Parsing/ShiftJISDecoder.swift b/Sources/Parsing/ShiftJISDecoder.swift
index d553067..fe3bec0 100644
--- a/Sources/Parsing/ShiftJISDecoder.swift
+++ b/Sources/Parsing/ShiftJISDecoder.swift
@@ -19,7 +19,9 @@ extension PureXML.Parsing.ByteDecoder {
scalars.append(scalar)
} else {
scalars.append("\u{FFFD}")
- if byte <= 0x7F { scalars.append(Unicode.Scalar(byte)) } // reprocess ASCII trail
+ if byte <= 0x7F {
+ scalars.append(Unicode.Scalar(byte))
+ } // reprocess ASCII trail
}
} else if let single = singleByteScalar(byte) {
scalars.append(single)
@@ -29,15 +31,21 @@ extension PureXML.Parsing.ByteDecoder {
scalars.append("\u{FFFD}")
}
}
- if lead != 0 { scalars.append("\u{FFFD}") }
+ if lead != 0 {
+ scalars.append("\u{FFFD}")
+ }
return String(scalars)
}
/// The scalar for a byte that is not part of a two-byte sequence: ASCII
/// (`0x00`-`0x80`) or half-width katakana (`0xA1`-`0xDF`), else nil.
static func singleByteScalar(_ byte: UInt8) -> Unicode.Scalar? {
- if byte <= 0x80 { return Unicode.Scalar(byte) }
- if (0xA1 ... 0xDF).contains(byte) { return Unicode.Scalar(0xFF61 + UInt32(byte) - 0xA1) }
+ if byte <= 0x80 {
+ return Unicode.Scalar(byte)
+ }
+ if (0xA1 ... 0xDF).contains(byte) {
+ return Unicode.Scalar(0xFF61 + UInt32(byte) - 0xA1)
+ }
return nil
}
@@ -53,7 +61,9 @@ extension PureXML.Parsing.ByteDecoder {
let offset = lead < 0xA0 ? 0x81 : 0xC1
let trailOffset = trail < 0x7F ? 0x40 : 0x41
let pointer = (Int(lead) - offset) * 188 + Int(trail) - trailOffset
- if (8836 ... 10715).contains(pointer) { return Unicode.Scalar(0xE000 + pointer - 8836) } // EUDC private use
+ if (8836 ... 10715).contains(pointer) {
+ return Unicode.Scalar(0xE000 + pointer - 8836)
+ } // EUDC private use
guard pointer >= 0, pointer < jis0208.count, jis0208[pointer] != 0xFFFF else { return nil }
return Unicode.Scalar(UInt32(jis0208[pointer]))
}
diff --git a/Sources/Parsing/StreamingDecoder.swift b/Sources/Parsing/StreamingDecoder.swift
index 49b2f59..1d5f4eb 100644
--- a/Sources/Parsing/StreamingDecoder.swift
+++ b/Sources/Parsing/StreamingDecoder.swift
@@ -68,8 +68,12 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
- if lead == 0x80 { return "\u{20AC}" }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
+ if lead == 0x80 {
+ return "\u{20AC}"
+ }
guard (0x81 ... 0xFE).contains(lead) else { return Self.replacement }
guard let trail = nextByte() else { finished = true
return Self.replacement
@@ -84,8 +88,12 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
- if lead == 0x80 { return "\u{20AC}" }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
+ if lead == 0x80 {
+ return "\u{20AC}"
+ }
guard (0x81 ... 0xFE).contains(lead) else { return Self.replacement }
guard let second = nextByte() else { finished = true
return Self.replacement
@@ -108,7 +116,9 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
guard (0x81 ... 0xFE).contains(lead) else { return Self.replacement }
guard let trail = nextByte() else { finished = true
return Self.replacement
@@ -125,7 +135,9 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
guard (0x81 ... 0xFE).contains(lead) else { return Self.replacement }
guard let trail = nextByte() else { finished = true
return Self.replacement
@@ -140,7 +152,9 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
if lead == 0x8E {
guard let byte = nextByte() else { finished = true
return Self.replacement
@@ -167,7 +181,9 @@ extension PureXML.Parsing {
finished = true
return nil
}
- if let single = PureXML.Parsing.ByteDecoder.ShiftJIS.singleByteScalar(lead) { return Character(single) }
+ if let single = PureXML.Parsing.ByteDecoder.ShiftJIS.singleByteScalar(lead) {
+ return Character(single)
+ }
guard PureXML.Parsing.ByteDecoder.ShiftJIS.isLead(lead) else { return Self.replacement }
guard let trail = nextByte() else {
finished = true
@@ -186,7 +202,9 @@ extension PureXML.Parsing {
}
ordered.append(UInt32(byte))
}
- if !bigEndian { ordered.reverse() }
+ if !bigEndian {
+ ordered.reverse()
+ }
let value = ordered.reduce(UInt32(0)) { ($0 << 8) | $1 }
return Character(Unicode.Scalar(value) ?? "\u{FFFD}")
}
diff --git a/Sources/Parsing/StreamingDecoderEUCTW.swift b/Sources/Parsing/StreamingDecoderEUCTW.swift
index 6c1336c..c8709cc 100644
--- a/Sources/Parsing/StreamingDecoderEUCTW.swift
+++ b/Sources/Parsing/StreamingDecoderEUCTW.swift
@@ -6,7 +6,9 @@ extension PureXML.Parsing.StreamingDecoder {
finished = true
return nil
}
- if lead <= 0x7F { return Character(Unicode.Scalar(lead)) }
+ if lead <= 0x7F {
+ return Character(Unicode.Scalar(lead))
+ }
if lead == 0x8E {
guard let plane = nextByte(), let byte1 = nextByte(), let byte2 = nextByte() else {
finished = true
diff --git a/Sources/Parsing/TextReader.swift b/Sources/Parsing/TextReader.swift
index 1ac1091..e775ba5 100644
--- a/Sources/Parsing/TextReader.swift
+++ b/Sources/Parsing/TextReader.swift
@@ -186,7 +186,9 @@ public extension PureXML.Parsing {
applyStartElement(name: name, attributes: attributes)
case let .endElement(name):
currentDepth -= 1
- if !langStack.isEmpty { langStack.removeLast() }
+ if !langStack.isEmpty {
+ langStack.removeLast()
+ }
currentName = name
xmlLang = langStack.last ?? nil
set(.endElement, name: name.description, depth: currentDepth)
diff --git a/Sources/Parsing/XMLCharacter.swift b/Sources/Parsing/XMLCharacter.swift
index f3ace93..83f2f51 100644
--- a/Sources/Parsing/XMLCharacter.swift
+++ b/Sources/Parsing/XMLCharacter.swift
@@ -67,7 +67,9 @@ public extension PureXML.Parsing {
/// `NameChar`: a name character after the first.
public static func isNameChar(_ scalar: Unicode.Scalar) -> Bool {
- if isNameStart(scalar) { return true }
+ if isNameStart(scalar) {
+ return true
+ }
switch scalar.value {
case 0x2D, // "-"
0x2E, // "."
@@ -86,7 +88,9 @@ public extension PureXML.Parsing {
var scalars = name.unicodeScalars.makeIterator()
guard let first = scalars.next(), isNameStart(first) else { return false }
while let scalar = scalars.next() {
- if !isNameChar(scalar) { return false }
+ if !isNameChar(scalar) {
+ return false
+ }
}
return true
}
diff --git a/Sources/Pattern/Matcher.swift b/Sources/Pattern/Matcher.swift
index 55283a3..275416b 100644
--- a/Sources/Pattern/Matcher.swift
+++ b/Sources/Pattern/Matcher.swift
@@ -22,7 +22,9 @@ extension PureXML.Pattern {
case let .prefixWildcard(prefix):
return name.prefix == prefix
case let .name(text):
- if text.contains(":") { return name.description == text }
+ if text.contains(":") {
+ return name.description == text
+ }
return name.description == text || name.localName == text
}
}
@@ -105,7 +107,9 @@ public extension PureXML.Pattern {
private func ancestorMatches(_ stepIndex: Int, below pathIndex: Int, _ path: [PureXML.Model.QualifiedName]) -> Bool {
var candidate = pathIndex - 1
while candidate >= 0 {
- if matchUpTo(stepIndex, candidate, path) { return true }
+ if matchUpTo(stepIndex, candidate, path) {
+ return true
+ }
candidate -= 1
}
return false
diff --git a/Sources/Pattern/PatternCompiler.swift b/Sources/Pattern/PatternCompiler.swift
index 46ef437..19945af 100644
--- a/Sources/Pattern/PatternCompiler.swift
+++ b/Sources/Pattern/PatternCompiler.swift
@@ -42,8 +42,12 @@ extension PureXML.Pattern {
}
private mutating func leadingGap() -> Gap {
- if consume("//") { return .descendant }
- if consume("/") { return .root }
+ if consume("//") {
+ return .descendant
+ }
+ if consume("/") {
+ return .root
+ }
return .descendant
}
@@ -60,11 +64,17 @@ extension PureXML.Pattern {
private mutating func parseStep(gap: Gap) throws -> RawStep {
guard let character = peek() else { throw PatternError.expectedStep }
- if character == "[" { throw PatternError.unsupported("predicate") }
- if character == "." { throw PatternError.unsupported(".") }
+ if character == "[" {
+ throw PatternError.unsupported("predicate")
+ }
+ if character == "." {
+ throw PatternError.unsupported(".")
+ }
let isAttribute = consume("@")
let test = try parseTest()
- if peek() == "[" { throw PatternError.unsupported("predicate") }
+ if peek() == "[" {
+ throw PatternError.unsupported("predicate")
+ }
return RawStep(gap: gap, isAttribute: isAttribute, test: test)
}
@@ -116,7 +126,9 @@ extension PureXML.Pattern {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
}
}
diff --git a/Sources/Pattern/PatternStream.swift b/Sources/Pattern/PatternStream.swift
index 9d0a1e1..2b721d3 100644
--- a/Sources/Pattern/PatternStream.swift
+++ b/Sources/Pattern/PatternStream.swift
@@ -29,7 +29,9 @@ public extension PureXML.Pattern.Matcher {
stack.append(name)
collect(stack: stack, attributes: attributes, into: &results)
case .endElement:
- if !stack.isEmpty { stack.removeLast() }
+ if !stack.isEmpty {
+ stack.removeLast()
+ }
default:
break
}
diff --git a/Sources/Regex/RegexAutomaton.swift b/Sources/Regex/RegexAutomaton.swift
index feae68e..f7b34b2 100644
--- a/Sources/Regex/RegexAutomaton.swift
+++ b/Sources/Regex/RegexAutomaton.swift
@@ -30,7 +30,9 @@ extension PureXML.Regex {
next.insert(transition.target)
}
}
- if next.isEmpty { return false }
+ if next.isEmpty {
+ return false
+ }
current = closure(next)
}
return current.contains(accept)
@@ -104,7 +106,11 @@ extension PureXML.Regex {
var last: Int?
for node in nodes {
let part = fragment(for: node)
- if let previous = last { states[previous].epsilon.append(part.start) } else { first = part.start }
+ if let previous = last {
+ states[previous].epsilon.append(part.start)
+ } else {
+ first = part.start
+ }
last = part.accept
}
return (first ?? addState(), last ?? addState())
@@ -127,7 +133,9 @@ extension PureXML.Regex {
let boundedMin = Swift.min(minimum, Self.repeatUnrollCap)
var unrolled = 0
for _ in 0 ..< boundedMin {
- if stateBudgetExhausted { break }
+ if stateBudgetExhausted {
+ break
+ }
let part = fragment(for: node)
states[current].epsilon.append(part.start)
current = part.accept
diff --git a/Sources/Regex/RegexCategory.swift b/Sources/Regex/RegexCategory.swift
index 1700f5c..c0e405e 100644
--- a/Sources/Regex/RegexCategory.swift
+++ b/Sources/Regex/RegexCategory.swift
@@ -23,7 +23,9 @@ extension PureXML.Regex {
/// a two-letter category (`Lu`, `Nd`, ...), or an `Is` block name.
enum CategoryMatcher {
static func isKnown(_ name: String) -> Bool {
- if name.hasPrefix("Is") { return blocks[String(name.dropFirst(2))] != nil }
+ if name.hasPrefix("Is") {
+ return blocks[String(name.dropFirst(2))] != nil
+ }
return groups.contains(name) || codeValues.contains(name)
}
diff --git a/Sources/Regex/RegexParser.swift b/Sources/Regex/RegexParser.swift
index 2f68f89..6b74c69 100644
--- a/Sources/Regex/RegexParser.swift
+++ b/Sources/Regex/RegexParser.swift
@@ -20,7 +20,9 @@ extension PureXML.Regex {
}
static func parse(_ pattern: String) throws -> Node {
- if pattern.isEmpty { return .empty }
+ if pattern.isEmpty {
+ return .empty
+ }
var parser = RegexParser(pattern)
let node = try parser.parseAlternation()
guard parser.isAtEnd else { throw RegexError.unbalanced }
@@ -41,7 +43,9 @@ extension PureXML.Regex {
while let character = peek(), character != "|", character != ")" {
try pieces.append(parsePiece())
}
- if pieces.isEmpty { return .empty }
+ if pieces.isEmpty {
+ return .empty
+ }
return pieces.count == 1 ? pieces[0] : .concat(pieces)
}
@@ -75,7 +79,9 @@ extension PureXML.Regex {
maximum = peek() == "}" ? nil : parseInteger()
}
guard peek() == "}", let minimum else { throw RegexError.badQuantifier }
- if let maximum, minimum > maximum { throw RegexError.reversedQuantifier }
+ if let maximum, minimum > maximum {
+ throw RegexError.reversedQuantifier
+ }
advance()
return .repeated(atom, min: minimum, max: maximum)
}
@@ -186,7 +192,9 @@ extension PureXML.Regex {
cls.negated = true
}
// A class must have at least one member; `[]` (or `[^]`) is invalid.
- if peek() == "]" { throw RegexError.emptyClass }
+ if peek() == "]" {
+ throw RegexError.emptyClass
+ }
while let character = peek(), character != "]" {
if character == "-", peek(1) == "[" {
advance()
@@ -236,13 +244,19 @@ extension PureXML.Regex {
// A nested `[` is legal only as a `-[...]` subtraction, routed by
// parseClass/parseClassMember before reaching here; any other `[`
// is an unescaped class bracket, invalid per Appendix F.
- if character == "[" { throw RegexError.unescapedClassBracket }
+ if character == "[" {
+ throw RegexError.unescapedClassBracket
+ }
return .scalar(scalar(of: character))
}
guard let escaped = peek() else { throw RegexError.incompleteEscape }
advance()
- if let single = Self.singleEscape(escaped) { return .scalar(single) }
- if let member = Self.classEscape(escaped) { return member }
+ if let single = Self.singleEscape(escaped) {
+ return .scalar(single)
+ }
+ if let member = Self.classEscape(escaped) {
+ return member
+ }
if escaped == "p" || escaped == "P" {
return try .category(parseCategory(negated: escaped == "P"))
}
@@ -274,7 +288,9 @@ extension PureXML.Regex {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
}
}
diff --git a/Sources/Regex/RegexSyntax.swift b/Sources/Regex/RegexSyntax.swift
index 7d22896..81f3def 100644
--- a/Sources/Regex/RegexSyntax.swift
+++ b/Sources/Regex/RegexSyntax.swift
@@ -121,7 +121,9 @@ extension PureXML.Regex {
// removes from that result: `[^cde-[ag]]` is (not c/d/e) minus a/g,
// i.e. excludes {a,c,d,e,g}, not just {c,d,e} (XSTS RegexTest_430).
var hit = negated ? !inBase : inBase
- if hit, subtraction.contains(where: { $0.matches(scalar) }) { hit = false }
+ if hit, subtraction.contains(where: { $0.matches(scalar) }) {
+ hit = false
+ }
return hit
}
diff --git a/Sources/Regex/XSDCategory.swift b/Sources/Regex/XSDCategory.swift
index 1c53fe9..5830091 100644
--- a/Sources/Regex/XSDCategory.swift
+++ b/Sources/Regex/XSDCategory.swift
@@ -47,7 +47,9 @@ extension PureXML.Regex {
]
static func generalCategoryCode(_ scalar: Unicode.Scalar) -> String {
- if let override = categoryOverrides[scalar.value] { return override }
+ if let override = categoryOverrides[scalar.value] {
+ return override
+ }
guard assignedInUnicode32(scalar.value) else { return "Cn" }
return categoryCodes[scalar.properties.generalCategory] ?? "Cn"
}
diff --git a/Sources/Schema/BuiltinType.swift b/Sources/Schema/BuiltinType.swift
index 36adc27..c2ba507 100644
--- a/Sources/Schema/BuiltinType.swift
+++ b/Sources/Schema/BuiltinType.swift
@@ -92,7 +92,9 @@ public extension PureXML.Schema {
func derives(from other: BuiltinType) -> Bool {
var current: BuiltinType? = self
while let type = current {
- if type == other { return true }
+ if type == other {
+ return true
+ }
current = type.derivationBase
}
return false
@@ -108,8 +110,12 @@ public extension PureXML.Schema {
}
var primitive: Primitive {
- if let primitive = Self.stringLike[self] { return primitive }
- if Self.integerLike.contains(self) { return .integer }
+ if let primitive = Self.stringLike[self] {
+ return primitive
+ }
+ if Self.integerLike.contains(self) {
+ return .integer
+ }
return computedPrimitive
}
diff --git a/Sources/Schema/ComplexType.swift b/Sources/Schema/ComplexType.swift
index 6028e3f..4e81f9c 100644
--- a/Sources/Schema/ComplexType.swift
+++ b/Sources/Schema/ComplexType.swift
@@ -121,9 +121,15 @@ public extension PureXML.Schema {
return .notAbsent
case let (.notNamespace(name), .enumerated(set)), let (.enumerated(set), .notNamespace(name)):
let hasName = set.contains(name), hasAbsent = set.contains("")
- if hasName, hasAbsent { return .any } // E1-10 5.1.1
- if hasName { return .notAbsent } // E1-10 5.1.2
- if hasAbsent { return nil } // NOT EXPRESSIBLE, E1-10 5.1.3
+ if hasName, hasAbsent {
+ return .any
+ } // E1-10 5.1.1
+ if hasName {
+ return .notAbsent
+ } // E1-10 5.1.2
+ if hasAbsent {
+ return nil
+ } // NOT EXPRESSIBLE, E1-10 5.1.3
return .notNamespace(name) // E1-10 5.1.4
case let (.notAbsent, .enumerated(set)), let (.enumerated(set), .notAbsent):
return set.contains("") ? .any : .notAbsent
@@ -131,8 +137,12 @@ public extension PureXML.Schema {
}
private static func unionProcessContents(_ left: ProcessContents, _ right: ProcessContents) -> ProcessContents {
- if left == .strict || right == .strict { return .strict }
- if left == .lax || right == .lax { return .lax }
+ if left == .strict || right == .strict {
+ return .strict
+ }
+ if left == .lax || right == .lax {
+ return .lax
+ }
return .skip
}
@@ -191,8 +201,12 @@ public extension PureXML.Schema {
// When the two differ the intersection is strictly not expressible; keep
// the less strict so a matched attribute is never validated more harshly
// than either source asked for (under-validation, not over-rejection).
- if left == right { return left }
- if left == .skip || right == .skip { return .skip }
+ if left == right {
+ return left
+ }
+ if left == .skip || right == .skip {
+ return .skip
+ }
return .lax
}
}
@@ -292,7 +306,9 @@ public extension PureXML.Schema {
/// The fixed value, when this is a `fixed` constraint.
public var fixedValue: String? {
- if case let .fixed(value) = self { return value }
+ if case let .fixed(value) = self {
+ return value
+ }
return nil
}
diff --git a/Sources/Schema/ComplexValidator.swift b/Sources/Schema/ComplexValidator.swift
index acece36..c4d6e1c 100644
--- a/Sources/Schema/ComplexValidator.swift
+++ b/Sources/Schema/ComplexValidator.swift
@@ -23,9 +23,9 @@ public extension PureXML.Schema {
/// The named type table that `ElementType.typeReference` resolves against.
let types: [String: ElementType]
/// Local names of elements declared `nillable="true"`.
- private let nillableElements: Set
+ let nillableElements: Set
/// The `default`/`fixed` value constraint declared on each element name.
- private let elementConstraints: [String: ValueConstraint]
+ let elementConstraints: [String: ValueConstraint]
/// Complex types declared `abstract="true"`, keyed by namespaced identity
/// (`{ns}local`) so same-local-name types in different namespaces stay distinct.
let abstractTypes: Set
@@ -79,8 +79,21 @@ public extension PureXML.Schema {
at path: [PureXML.Validation.PathKey] = [],
namespaceBindings inheritedBindings: [String: String] = [:],
) -> [PureXML.Validation.ValidationError] {
+ validateTagged(element, against: type, at: path, namespaceBindings: inheritedBindings).map(\.error)
+ }
+
+ /// The tagged form of ``validate(_:against:at:namespaceBindings:)``: the same
+ /// computation, with each finding labeled by the decomposed rule it belongs
+ /// to. The public, untagged form is a thin `.map(\.error)` over this so
+ /// behavior and output stay identical for every existing caller.
+ func validateTagged(
+ _ element: PureXML.Model.Element,
+ against type: ComplexType,
+ at path: [PureXML.Validation.PathKey] = [],
+ namespaceBindings inheritedBindings: [String: String] = [:],
+ ) -> [XSDFinding] {
let namespaceBindings = Self.namespaceBindings(for: element, inherited: inheritedBindings)
- var errors: [XSDFailure] = []
+ var errors: [XSDFinding] = []
validateAttributes(element, type, at: path, into: &errors)
// An xsi:nil element keeps its attribute obligations but must have no
// content; its content model is not otherwise checked.
@@ -90,68 +103,15 @@ public extension PureXML.Schema {
validateContent(element, type.content, at: path, namespaceBindings: namespaceBindings, into: &errors)
// A simpleContent fixed value is compared in its simple type's value space
// (cvc-elt.5.2.2.1), e.g. "05" equals an xs:int fixed "5".
- let simpleContentType: SimpleType? = if case let .simpleContent(simple) = type.content { simple } else { nil }
+ let simpleContentType: SimpleType? = if case let .simpleContent(simple) = type.content {
+ simple
+ } else {
+ nil
+ }
errors += elementFixedErrors(element, valueType: simpleContentType, at: path)
return errors
}
- /// The errors from an `xsi:nil="true"` element: rejecting it when the
- /// element is not nillable, or when it carries content. Returns nil when
- /// the element is not nilled.
- func nilErrors(_ element: PureXML.Model.Element, fixedValue: String? = nil, at path: XSDPath) -> [XSDFailure]? {
- let name = element.name.localName
- // cvc-elt.3.1: a non-nillable element must carry no `xsi:nil` attribute at
- // all, whatever its value (so `xsi:nil="false"` is equally forbidden, not
- // only `xsi:nil="true"`).
- if Self.nilAttributeValue(element) != nil, !nillableElements.contains(name) {
- return [XSDFailure(reason: "element '\(name)' is not nillable but has xsi:nil", at: path)]
- }
- guard Self.isNil(element) else { return nil }
- if Self.hasContent(element) {
- return [XSDFailure(reason: "nil element '\(name)' must be empty", at: path)]
- }
- // cvc-elt.3.2.2: a nilled element may not have a fixed {value constraint}.
- // The matched particle's constraint is authoritative; the by-local-name map
- // is a fallback for paths without a particle (e.g. the resolved-type entry).
- if (fixedValue ?? elementConstraints[name]?.fixedValue) != nil {
- return [XSDFailure(reason: "nil element '\(name)' must not have a fixed value constraint", at: path)]
- }
- return []
- }
-
- /// The text an element's simple content is validated against: its character
- /// content, or, when the element is empty, its `default`/`fixed` value (an
- /// empty element takes that value, and the value, not the empty string, must
- /// satisfy the type). The matched particle's constraint takes precedence over
- /// the by-local-name `elementConstraints` map, exactly as the tree path's
- /// `validateChild` resolves it.
- func effectiveSimpleText(for element: PureXML.Model.Element, particleConstraint: ValueConstraint? = nil) -> String {
- let text = Self.rawTextContent(element)
- let constraint = particleConstraint ?? elementConstraints[element.name.localName]
- return text.isEmpty ? (constraint?.value ?? text) : text
- }
-
- /// The error from a `fixed` element value constraint: the element's text
- /// must equal the fixed value in the element type's value space when known.
- func elementFixedErrors(
- _ element: PureXML.Model.Element,
- valueType: SimpleType? = nil,
- particleFixed: String? = nil,
- at path: XSDPath,
- ) -> [XSDFailure] {
- guard let fixed = particleFixed ?? elementConstraints[element.name.localName]?.fixedValue else { return [] }
- // A fixed element's content is its fixed character value: it may not carry
- // element children, even under a mixed content type (cvc-elt.5.2.2.2.1).
- if !element.children.compactMap(\.element).isEmpty {
- return [XSDFailure(reason: "fixed element '\(element.name.localName)' must not have element children", at: path)]
- }
- let text = Self.rawTextContent(element)
- // An empty element takes the fixed value; only present content must equal it.
- if text.isEmpty { return [] }
- if let valueType, valueType.valueMatches(text, literal: fixed) { return [] }
- return text == fixed ? [] : [XSDFailure(reason: "element '\(element.name.localName)' is fixed and must be '\(fixed)'", at: path)]
- }
-
/// Validates `element` against any element type at `path`, resolving a
/// `typeReference` through the type table.
public func validate(
@@ -159,7 +119,16 @@ public extension PureXML.Schema {
as type: ElementType,
at path: [PureXML.Validation.PathKey] = [],
) -> [PureXML.Validation.ValidationError] {
- var errors: [XSDFailure] = []
+ validateTagged(element, as: type, at: path).map(\.error)
+ }
+
+ /// The tagged form of ``validate(_:as:at:)``. See ``validateTagged(_:against:at:namespaceBindings:)``.
+ func validateTagged(
+ _ element: PureXML.Model.Element,
+ as type: ElementType,
+ at path: [PureXML.Validation.PathKey] = [],
+ ) -> [XSDFinding] {
+ var errors: [XSDFinding] = []
let namespaceBindings = Self.namespaceBindings(for: element)
validateChild(element, against: type, at: path, namespaceBindings: namespaceBindings, into: &errors)
return errors
@@ -172,17 +141,21 @@ public extension PureXML.Schema {
_ content: ContentType,
at path: XSDPath,
namespaceBindings: [String: String],
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
let children = element.children.compactMap(\.element)
switch content {
case .empty:
- if !children.isEmpty { errors.append(XSDFailure(reason: "element must be empty", at: path)) }
+ if !children.isEmpty {
+ errors.append(XSDFinding(.emptyContentForbidsChildren, reason: "element must be empty", at: path))
+ }
rejectText(element, at: path, into: &errors)
case let .simpleContent(type):
- if !children.isEmpty { errors.append(XSDFailure(reason: "element must not have children", at: path)) }
+ if !children.isEmpty {
+ errors.append(XSDFinding(.simpleContentForbidsChildren, reason: "element must not have children", at: path))
+ }
if let error = type.validate(Self.rawTextContent(element)) {
- errors.append(XSDFailure(reason: "content: \(error)", at: path))
+ errors.append(XSDFinding(.simpleContentTypeValid, reason: "content: \(error)", at: path))
}
case let .elementOnly(particle):
rejectText(element, at: path, into: &errors)
@@ -192,9 +165,9 @@ public extension PureXML.Schema {
}
}
- func rejectText(_ element: PureXML.Model.Element, at path: XSDPath, into errors: inout [XSDFailure]) {
+ func rejectText(_ element: PureXML.Model.Element, at path: XSDPath, into errors: inout [XSDFinding]) {
guard !Self.textContent(element).isEmpty else { return }
- errors.append(XSDFailure(reason: "element must not contain text", at: path))
+ errors.append(XSDFinding(.textForbidden, reason: "element must not contain text", at: path))
}
// MARK: Particles
@@ -204,7 +177,7 @@ public extension PureXML.Schema {
children: [PureXML.Model.Element],
at path: XSDPath,
namespaceBindings: [String: String],
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
// Locate each content-model violation at the offending child (or the
// missing one), with a recovery hint, rather than one opaque failure,
@@ -232,7 +205,7 @@ public extension PureXML.Schema {
_ children: [PureXML.Model.Element],
matched: [PureXML.Schema.MatchedParticle?],
frame: XSDChildValidationFrame,
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
let steps = Self.childSteps(children)
for (index, child) in children.enumerated() {
@@ -254,60 +227,13 @@ public extension PureXML.Schema {
}
}
- /// Validates a wildcard-matched child by its `processContents`: skip does
- /// nothing, lax validates against a global declaration when one exists, and
- /// strict requires one.
- private func globalElementDeclaration(for name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
- // A global element declaration is always in the schema's target
- // namespace, so an instance element matches one only by full qualified
- // name. The namespaced key already covers a no-namespace global (its key
- // is `element:{}local`); matching an unqualified instance against a
- // namespaced global by bare local name would conflate namespaces, e.g.
- // accepting unqualified text where a namespaced `xs:short` is declared.
- types[PureXML.Schema.XSDParser.elementDeclarationKey(name)]
- }
-
- private func validateWildcardChild(
- _ child: PureXML.Model.Element,
- processContents: ProcessContents,
- at path: XSDPath,
- namespaceBindings: [String: String],
- into errors: inout [XSDFailure],
- ) {
- switch processContents {
- case .skip:
- return
- case .lax:
- if let declaration = globalElementDeclaration(for: child.name) {
- validateChild(child, against: declaration, at: path, namespaceBindings: namespaceBindings, into: &errors)
- }
- case .strict:
- if let declaration = globalElementDeclaration(for: child.name) {
- validateChild(child, against: declaration, at: path, namespaceBindings: namespaceBindings, into: &errors)
- } else if let type = xsiDeclaredType(for: child, namespaceBindings: namespaceBindings) {
- validateChild(child, against: type, at: path, namespaceBindings: namespaceBindings, into: &errors)
- } else {
- errors.append(XSDFailure(reason: "no declaration for wildcard-matched element '\(child.name.localName)'", at: path))
- }
- }
- }
-
- /// The type named by a wildcard-matched element's `xsi:type`, when present.
- private func xsiDeclaredType(for child: PureXML.Model.Element, namespaceBindings: [String: String]) -> ElementType? {
- guard Self.xsiTypeAttributeValue(child) != nil else { return nil }
- guard let reference = Self.xsiTypeReference(child, namespaceBindings: namespaceBindings),
- let type = Self.resolveNamedType(reference, in: types)
- else { return nil }
- return type
- }
-
- private func validateChild(
+ func validateChild(
_ child: PureXML.Model.Element,
against declared: ElementType,
at path: XSDPath,
namespaceBindings: [String: String],
particleConstraint: ValueConstraint? = nil,
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
// An instance `xsi:type` overrides the declared type, provided the named
// type exists in the schema. Resolve to the type itself rather than a
@@ -315,7 +241,7 @@ public extension PureXML.Schema {
// the declared type can forbid the substitution; an abstract declared
// type requires one.
if let xsiError = xsiTypeOverrideError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) {
- errors.append(xsiError)
+ errors.append(XSDFinding(.xsiTypeSubstitutionValid, xsiError))
return
}
guard let declared = overriddenType(declared, for: child, namespaceBindings: namespaceBindings, at: path, into: &errors) else { return }
@@ -328,26 +254,30 @@ public extension PureXML.Schema {
// violation. Checked before the nil short-circuit, since an
// xsi:nil element is equally bound by it.
for attribute in child.attributes where !Self.isNamespaceDeclaration(attribute) && !Self.isSchemaInstanceAttribute(attribute) {
- errors.append(XSDFailure(reason: "'\(child.name.localName)' has a simple type and must not carry attribute '\(attribute.name.localName)'", at: path))
+ errors.append(XSDFinding(
+ .simpleTypeDisallowedAttribute,
+ reason: "'\(child.name.localName)' has a simple type and must not carry attribute '\(attribute.name.localName)'",
+ at: path,
+ ))
}
if let nilErrors = nilErrors(child, fixedValue: particleConstraint?.fixedValue, at: path) {
errors += nilErrors
return
}
if !child.children.compactMap(\.element).isEmpty {
- errors.append(XSDFailure(reason: "'\(child.name.localName)' must not have children", at: path))
+ errors.append(XSDFinding(.simpleTypeForbidsChildren, reason: "'\(child.name.localName)' must not have children", at: path))
}
// An empty element takes its `default`/`fixed` value; that value
// (not the empty string) is what must be valid against the type,
// including any xsi:type override already resolved into `simple`.
let text = Self.rawTextContent(child)
if let error = simple.validate(effectiveSimpleText(for: child, particleConstraint: particleConstraint)) {
- errors.append(XSDFailure(reason: "'\(child.name.localName)': \(error)", at: path))
+ errors.append(XSDFinding(.simpleTypeValueValid, reason: "'\(child.name.localName)': \(error)", at: path))
}
recordIDs(simple, value: text, at: path)
errors += elementFixedErrors(child, valueType: simple, particleFixed: particleConstraint?.fixedValue, at: path)
case let .complex(complex):
- errors.append(contentsOf: validate(child, against: complex, at: path, namespaceBindings: namespaceBindings))
+ errors.append(contentsOf: validateTagged(child, against: complex, at: path, namespaceBindings: namespaceBindings))
case .typeReference:
validateResolvedReference(declared, child, at: path, namespaceBindings: namespaceBindings, into: &errors)
}
@@ -360,13 +290,13 @@ public extension PureXML.Schema {
_ child: PureXML.Model.Element,
at path: XSDPath,
namespaceBindings: [String: String],
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
switch resolveReference(declared) {
case let .unknown(name):
- errors.append(XSDFailure(reason: "unknown type '\(name)'", at: path))
+ errors.append(XSDFinding(.typeReferenceResolves, reason: "unknown type '\(name)'", at: path))
case let .circular(name):
- errors.append(XSDFailure(reason: "circular type reference '\(name)'", at: path))
+ errors.append(XSDFinding(.typeReferenceResolves, reason: "circular type reference '\(name)'", at: path))
case let .resolved(resolved):
validateChild(child, against: resolved, at: path, namespaceBindings: namespaceBindings, into: &errors)
}
diff --git a/Sources/Schema/ComplexValidatorAttributes.swift b/Sources/Schema/ComplexValidatorAttributes.swift
index 04f2240..c470fa6 100644
--- a/Sources/Schema/ComplexValidatorAttributes.swift
+++ b/Sources/Schema/ComplexValidatorAttributes.swift
@@ -3,21 +3,21 @@ extension PureXML.Schema.ComplexValidator {
_ element: PureXML.Model.Element,
_ type: PureXML.Schema.ComplexType,
at path: [PureXML.Validation.PathKey],
- into errors: inout [PureXML.Validation.ValidationError],
+ into errors: inout [XSDFinding],
) {
let present = element.attributes.filter { !Self.isNamespaceDeclaration($0) && !Self.isSchemaInstanceAttribute($0) }
for use in type.attributes where !use.prohibited {
let match = present.first { Self.attributeMatches($0.name, declared: use.name, use: use, on: element.name) }
if let match {
if let error = use.type.validate(match.value) {
- errors.append(PureXML.Validation.ValidationError(reason: "attribute '\(use.name.localName)': \(error)", at: path))
+ errors.append(XSDFinding(.attributeTypeValid, reason: "attribute '\(use.name.localName)': \(error)", at: path))
}
if let fixed = use.valueConstraint?.fixedValue, !use.type.valueMatches(match.value, literal: fixed) {
- errors.append(PureXML.Validation.ValidationError(reason: "attribute '\(use.name.localName)' is fixed and must be '\(fixed)'", at: path))
+ errors.append(XSDFinding(.attributeFixedMatches, reason: "attribute '\(use.name.localName)' is fixed and must be '\(fixed)'", at: path))
}
recordIDs(use.type, value: match.value, at: path)
} else if use.required {
- errors.append(PureXML.Validation.ValidationError(reason: "missing required attribute '\(use.name.localName)'", at: path))
+ errors.append(XSDFinding(.attributeRequiredPresent, reason: "missing required attribute '\(use.name.localName)'", at: path))
} else if let constraint = use.valueConstraint {
// An absent attribute takes its `default`/`fixed` value, so a
// defaulted or fixed IDREF/IDREFS must still resolve to a matching
@@ -28,7 +28,8 @@ extension PureXML.Schema.ComplexValidator {
}
validateWildcardAttributes(present, element: element, type: type, at: path, into: &errors)
if idTypedAttributeCount(present, type, on: element) > 1 {
- errors.append(PureXML.Validation.ValidationError(
+ errors.append(XSDFinding(
+ .attributeSingleID,
reason: "an element may carry at most one attribute of type ID",
at: path,
))
@@ -45,7 +46,9 @@ extension PureXML.Schema.ComplexValidator {
on element: PureXML.Model.Element,
) -> Int {
present.reduce(into: 0) { count, attribute in
- if isIDTyped(attribute, type, on: element) { count += 1 }
+ if isIDTyped(attribute, type, on: element) {
+ count += 1
+ }
}
}
@@ -73,7 +76,9 @@ extension PureXML.Schema.ComplexValidator {
use: PureXML.Schema.AttributeUse,
on elementName: PureXML.Model.QualifiedName,
) -> Bool {
- if sameName(instance, declared) { return true }
+ if sameName(instance, declared) {
+ return true
+ }
guard use.chameleonUnprefixed,
instance.namespaceURI == nil || instance.namespaceURI?.isEmpty == true,
let declaredNamespace = declared.namespaceURI, !declaredNamespace.isEmpty
@@ -86,7 +91,7 @@ extension PureXML.Schema.ComplexValidator {
element: PureXML.Model.Element,
type: PureXML.Schema.ComplexType,
at path: [PureXML.Validation.PathKey],
- into errors: inout [PureXML.Validation.ValidationError],
+ into errors: inout [XSDFinding],
) {
// A `use="prohibited"` use is excluded from the effective {attribute uses},
// so a prohibited attribute is treated as undeclared and left to the
@@ -96,7 +101,7 @@ extension PureXML.Schema.ComplexValidator {
let wildcard = type.attributeWildcard
for attribute in present where !declared.contains(where: { Self.attributeMatches(attribute.name, declared: $0.name, use: $0, on: elementName) }) {
guard let wildcard, wildcard.admits(attribute.name) else {
- errors.append(PureXML.Validation.ValidationError(reason: "undeclared attribute '\(attribute.name.localName)'", at: path))
+ errors.append(XSDFinding(.attributeDeclaredOrPermitted, reason: "undeclared attribute '\(attribute.name.localName)'", at: path))
continue
}
switch wildcard.processContents {
@@ -108,7 +113,8 @@ extension PureXML.Schema.ComplexValidator {
}
case .strict:
guard let use = globalAttributeUse(for: attribute.name) else {
- errors.append(PureXML.Validation.ValidationError(
+ errors.append(XSDFinding(
+ .wildcardAttributeUndeclared,
reason: "no declaration for wildcard-matched attribute '\(attribute.name.localName)'",
at: path,
))
@@ -127,13 +133,13 @@ extension PureXML.Schema.ComplexValidator {
_ attribute: PureXML.Model.Attribute,
use: PureXML.Schema.AttributeUse,
at path: [PureXML.Validation.PathKey],
- into errors: inout [PureXML.Validation.ValidationError],
+ into errors: inout [XSDFinding],
) {
if let error = use.type.validate(attribute.value) {
- errors.append(PureXML.Validation.ValidationError(reason: "attribute '\(attribute.name.localName)': \(error)", at: path))
+ errors.append(XSDFinding(.wildcardAttributeTypeValid, reason: "attribute '\(attribute.name.localName)': \(error)", at: path))
}
if let fixed = use.valueConstraint?.fixedValue, !use.type.valueMatches(attribute.value, literal: fixed) {
- errors.append(PureXML.Validation.ValidationError(reason: "attribute '\(attribute.name.localName)' is fixed and must be '\(fixed)'", at: path))
+ errors.append(XSDFinding(.wildcardAttributeFixedMatches, reason: "attribute '\(attribute.name.localName)' is fixed and must be '\(fixed)'", at: path))
}
recordIDs(use.type, value: attribute.value, at: path)
}
diff --git a/Sources/Schema/ComplexValidatorContentModel.swift b/Sources/Schema/ComplexValidatorContentModel.swift
index 9ef6cbf..dced8ef 100644
--- a/Sources/Schema/ComplexValidatorContentModel.swift
+++ b/Sources/Schema/ComplexValidatorContentModel.swift
@@ -4,7 +4,7 @@
extension PureXML.Schema.ComplexValidator {
/// Walks the children through the content automaton, flagging the first child
/// the follow-set rejects, or the missing content when the sequence ends early.
- func sequenceStructureErrors(_ particle: PureXML.Schema.Particle, children: [PureXML.Model.Element], at path: XSDPath, into errors: inout [XSDFailure]) {
+ func sequenceStructureErrors(_ particle: PureXML.Schema.Particle, children: [PureXML.Model.Element], at path: XSDPath, into errors: inout [XSDFinding]) {
let matcher = PureXML.Schema.ContentRTNMatcher(PureXML.Schema.ContentRTNBuilder.build(particle))
let steps = Self.childSteps(children)
// Advance one active state-set across the children rather than re-walking
@@ -14,19 +14,23 @@ extension PureXML.Schema.ComplexValidator {
for (index, child) in children.enumerated() {
guard let next = matcher.step(current, over: child.name) else {
let allowed = matcher.admissible(from: current)
- errors.append(XSDFailure(reason: "element '\(child.name.localName)' is not allowed here\(Self.expectation(allowed))", at: path + [steps[index]]))
+ errors.append(XSDFinding(
+ .contentModelStructure,
+ reason: "element '\(child.name.localName)' is not allowed here\(Self.expectation(allowed))",
+ at: path + [steps[index]],
+ ))
return
}
current = next
}
if !matcher.isComplete(current) {
- errors.append(XSDFailure(reason: "content is incomplete\(Self.expectation(matcher.admissible(from: current)))", at: path))
+ errors.append(XSDFinding(.contentModelStructure, reason: "content is incomplete\(Self.expectation(matcher.admissible(from: current)))", at: path))
}
}
/// Locates `all`-group violations: each child that is not an in-bounds member,
/// recovering past it, then each required member that never appeared.
- func allStructureErrors(_ group: PureXML.Schema.Group, children: [PureXML.Model.Element], groupOptional: Bool = false, at path: XSDPath, into errors: inout [XSDFailure]) {
+ func allStructureErrors(_ group: PureXML.Schema.Group, children: [PureXML.Model.Element], groupOptional: Bool = false, at path: XSDPath, into errors: inout [XSDFinding]) {
var counts = [Int](repeating: 0, count: group.particles.count)
let steps = Self.childSteps(children)
for (index, child) in children.enumerated() {
@@ -35,7 +39,7 @@ extension PureXML.Schema.ComplexValidator {
let room = member.maxOccurs.map { counts[slot] < $0 } ?? true
return room && Self.memberMatches(member.term, child.name)
}) else {
- errors.append(XSDFailure(reason: "element '\(child.name.localName)' is not allowed here", at: path + [steps[index]]))
+ errors.append(XSDFinding(.contentModelStructure, reason: "element '\(child.name.localName)' is not allowed here", at: path + [steps[index]]))
continue
}
counts[position] += 1
@@ -44,10 +48,12 @@ extension PureXML.Schema.ComplexValidator {
// absent (zero occurrences), so its required members are not expected; but
// once any child appears the group is present once and every member's own
// `minOccurs` applies. A required group (`minOccurs="1"`) always enforces.
- if groupOptional, children.isEmpty { return }
+ if groupOptional, children.isEmpty {
+ return
+ }
for (index, member) in group.particles.enumerated() where counts[index] < member.minOccurs {
if case let .element(name, _, _, _, _, _) = member.term {
- errors.append(XSDFailure(reason: "element '\(name.localName)' is required but missing", at: path))
+ errors.append(XSDFinding(.contentModelStructure, reason: "element '\(name.localName)' is required but missing", at: path))
}
}
}
@@ -92,7 +98,9 @@ extension PureXML.Schema.ComplexValidator {
/// A "; expected a, b" hint naming the elements the automaton accepts next.
static func expectation(_ labels: [PureXML.Schema.TermLabel]) -> String {
let names = labels.compactMap { label -> String? in
- if case let .name(qualified) = label { return "<\(qualified.localName)>" }
+ if case let .name(qualified) = label {
+ return "<\(qualified.localName)>"
+ }
return nil
}
let unique = Set(names).sorted()
diff --git a/Sources/Schema/ComplexValidatorDerivation.swift b/Sources/Schema/ComplexValidatorDerivation.swift
index 475eac1..f4b2962 100644
--- a/Sources/Schema/ComplexValidatorDerivation.swift
+++ b/Sources/Schema/ComplexValidatorDerivation.swift
@@ -14,12 +14,24 @@ extension PureXML.Schema.ComplexValidator {
at path: [PureXML.Validation.PathKey],
namespaceBindings: [String: String],
) -> PureXML.Validation.ValidationError? {
- if let blocked = blockedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return blocked }
- if let notDerived = notDerivedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return notDerived }
- if let anyTypeSub = anyTypeForAnySimpleTypeError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return anyTypeSub }
- if let listUnion = listUnionSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) { return listUnion }
- if let abstractType = abstractXsiTypeError(child: child, at: path, namespaceBindings: namespaceBindings) { return abstractType }
- if case let .typeReference(name) = declared { return abstractTypeError(named: name, child: child, at: path) }
+ if let blocked = blockedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) {
+ return blocked
+ }
+ if let notDerived = notDerivedSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) {
+ return notDerived
+ }
+ if let anyTypeSub = anyTypeForAnySimpleTypeError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) {
+ return anyTypeSub
+ }
+ if let listUnion = listUnionSubstitutionError(declared: declared, child: child, at: path, namespaceBindings: namespaceBindings) {
+ return listUnion
+ }
+ if let abstractType = abstractXsiTypeError(child: child, at: path, namespaceBindings: namespaceBindings) {
+ return abstractType
+ }
+ if case let .typeReference(name) = declared {
+ return abstractTypeError(named: name, child: child, at: path)
+ }
return nil
}
@@ -99,7 +111,9 @@ extension PureXML.Schema.ComplexValidator {
case .simple, .complex: declared
case let .typeReference(reference): resolvedDeclaredType(reference)
}
- if case let .simple(type)? = resolved { return type.isAnySimpleType }
+ if case let .simple(type)? = resolved {
+ return type.isAnySimpleType
+ }
return false
}
@@ -157,7 +171,9 @@ extension PureXML.Schema.ComplexValidator {
/// Whether an element type resolves to a complex type.
static func isComplexType(_ type: PureXML.Schema.ElementType) -> Bool {
- if case .complex = type { return true }
+ if case .complex = type {
+ return true
+ }
return false
}
@@ -237,13 +253,17 @@ extension PureXML.Schema.ComplexValidator {
for member in members {
// The substitute IS a member: derived with no method of its own, so
// this path is unblocked and the substitution is permitted outright.
- if member == substitute { return false }
+ if member == substitute {
+ return false
+ }
guard let toMember = PureXML.Schema.XSDParser.derivationMethods(from: substitute, to: member, typeDerivation) else {
continue
}
anyConfirmedPath = true
// An unblocked path to any member makes the whole substitution valid.
- if toMember.isDisjoint(with: blocked) { return false }
+ if toMember.isDisjoint(with: blocked) {
+ return false
+ }
}
// Every confirmed member path is blocked (and at least one exists): bar it.
return anyConfirmedPath
@@ -325,8 +345,12 @@ extension PureXML.Schema.ComplexValidator {
/// namespaced backbone and the check stays silent rather than risk a false
/// positive.
static func derivationKey(fromReference reference: String) -> String {
- if reference.hasPrefix("type:") { return String(reference.dropFirst("type:".count)) }
- if reference.hasPrefix("{") || reference.contains(":") { return reference }
+ if reference.hasPrefix("type:") {
+ return String(reference.dropFirst("type:".count))
+ }
+ if reference.hasPrefix("{") || reference.contains(":") {
+ return reference
+ }
return key(PureXML.Model.QualifiedName(localName: reference, namespaceURI: nil))
}
}
diff --git a/Sources/Schema/ComplexValidatorHelpers.swift b/Sources/Schema/ComplexValidatorHelpers.swift
index 46d36cd..6cafb25 100644
--- a/Sources/Schema/ComplexValidatorHelpers.swift
+++ b/Sources/Schema/ComplexValidatorHelpers.swift
@@ -22,7 +22,9 @@ extension PureXML.Schema.ComplexValidator {
return wildcard.processContents
case let .group(group):
for member in group.particles {
- if let found = wildcard(in: member.term) { return found }
+ if let found = wildcard(in: member.term) {
+ return found
+ }
}
return nil
case .element:
@@ -39,7 +41,9 @@ extension PureXML.Schema.ComplexValidator {
fileprivate static func collectTypes(_ term: XSDTerm, into result: inout [String: XSDElementType]) {
switch term {
case let .element(name, type, _, _, _, _):
- if let type { result[key(name)] = type }
+ if let type {
+ result[key(name)] = type
+ }
case let .group(group):
for member in group.particles {
collectTypes(member.term, into: &result)
@@ -62,7 +66,9 @@ extension PureXML.Schema.ComplexValidator {
fileprivate static func collectValueConstraints(_ term: XSDTerm, into result: inout [String: PureXML.Schema.ValueConstraint]) {
switch term {
case let .element(name, _, _, valueConstraint, _, _):
- if let valueConstraint { result[key(name)] = valueConstraint }
+ if let valueConstraint {
+ result[key(name)] = valueConstraint
+ }
case let .group(group):
for member in group.particles {
collectValueConstraints(member.term, into: &result)
@@ -200,7 +206,13 @@ extension PureXML.Schema.ComplexValidator {
/// Whether the element has any child element or non-whitespace text.
static func hasContent(_ element: PureXML.Model.Element) -> Bool {
- !textContent(element).isEmpty || element.children.contains { if case .element = $0 { true } else { false } }
+ !textContent(element).isEmpty || element.children.contains {
+ if case .element = $0 {
+ true
+ } else {
+ false
+ }
+ }
}
}
@@ -236,7 +248,9 @@ extension PureXML.Schema.ComplexValidator {
/// Resolves a named type reference, including built-in datatypes and ur-types.
static func resolveNamedType(_ name: String, in types: [String: PureXML.Schema.ElementType]) -> PureXML.Schema.ElementType? {
- if let next = types[name] { return next }
+ if let next = types[name] {
+ return next
+ }
if name.hasPrefix("type:"), let (uri, local) = parseQualifiedTypeKey(name) {
if uri == PureXML.Schema.XSDParser.xsdNamespace, let builtin = urOrBuiltinType(named: local) {
return builtin
diff --git a/Sources/Schema/ComplexValidatorNilFixed.swift b/Sources/Schema/ComplexValidatorNilFixed.swift
new file mode 100644
index 0000000..fd59771
--- /dev/null
+++ b/Sources/Schema/ComplexValidatorNilFixed.swift
@@ -0,0 +1,62 @@
+extension PureXML.Schema.ComplexValidator {
+ /// The errors from an `xsi:nil="true"` element: rejecting it when the
+ /// element is not nillable, or when it carries content. Returns nil when
+ /// the element is not nilled.
+ func nilErrors(_ element: PureXML.Model.Element, fixedValue: String? = nil, at path: XSDPath) -> [XSDFinding]? {
+ let name = element.name.localName
+ // cvc-elt.3.1: a non-nillable element must carry no `xsi:nil` attribute at
+ // all, whatever its value (so `xsi:nil="false"` is equally forbidden, not
+ // only `xsi:nil="true"`).
+ if Self.nilAttributeValue(element) != nil, !nillableElements.contains(name) {
+ return [XSDFinding(.nilAttributeRequiresNillable, reason: "element '\(name)' is not nillable but has xsi:nil", at: path)]
+ }
+ guard Self.isNil(element) else { return nil }
+ if Self.hasContent(element) {
+ return [XSDFinding(.nilElementMustBeEmpty, reason: "nil element '\(name)' must be empty", at: path)]
+ }
+ // cvc-elt.3.2.2: a nilled element may not have a fixed {value constraint}.
+ // The matched particle's constraint is authoritative; the by-local-name map
+ // is a fallback for paths without a particle (e.g. the resolved-type entry).
+ if (fixedValue ?? elementConstraints[name]?.fixedValue) != nil {
+ return [XSDFinding(.nilElementNoFixedConstraint, reason: "nil element '\(name)' must not have a fixed value constraint", at: path)]
+ }
+ return []
+ }
+
+ /// The text an element's simple content is validated against: its character
+ /// content, or, when the element is empty, its `default`/`fixed` value (an
+ /// empty element takes that value, and the value, not the empty string, must
+ /// satisfy the type). The matched particle's constraint takes precedence over
+ /// the by-local-name `elementConstraints` map, exactly as the tree path's
+ /// `validateChild` resolves it.
+ func effectiveSimpleText(for element: PureXML.Model.Element, particleConstraint: PureXML.Schema.ValueConstraint? = nil) -> String {
+ let text = Self.rawTextContent(element)
+ let constraint = particleConstraint ?? elementConstraints[element.name.localName]
+ return text.isEmpty ? (constraint?.value ?? text) : text
+ }
+
+ /// The error from a `fixed` element value constraint: the element's text
+ /// must equal the fixed value in the element type's value space when known.
+ func elementFixedErrors(
+ _ element: PureXML.Model.Element,
+ valueType: PureXML.Schema.SimpleType? = nil,
+ particleFixed: String? = nil,
+ at path: XSDPath,
+ ) -> [XSDFinding] {
+ guard let fixed = particleFixed ?? elementConstraints[element.name.localName]?.fixedValue else { return [] }
+ // A fixed element's content is its fixed character value: it may not carry
+ // element children, even under a mixed content type (cvc-elt.5.2.2.2.1).
+ if !element.children.compactMap(\.element).isEmpty {
+ return [XSDFinding(.fixedElementNoChildren, reason: "fixed element '\(element.name.localName)' must not have element children", at: path)]
+ }
+ let text = Self.rawTextContent(element)
+ // An empty element takes the fixed value; only present content must equal it.
+ if text.isEmpty {
+ return []
+ }
+ if let valueType, valueType.valueMatches(text, literal: fixed) {
+ return []
+ }
+ return text == fixed ? [] : [XSDFinding(.fixedElementValueMatches, reason: "element '\(element.name.localName)' is fixed and must be '\(fixed)'", at: path)]
+ }
+}
diff --git a/Sources/Schema/ComplexValidatorStreaming.swift b/Sources/Schema/ComplexValidatorStreaming.swift
index 12efdc6..12176fa 100644
--- a/Sources/Schema/ComplexValidatorStreaming.swift
+++ b/Sources/Schema/ComplexValidatorStreaming.swift
@@ -17,6 +17,8 @@ public extension PureXML.Schema {
}
}
+extension PureXML.Schema.ComplexValidator: PureXML.Validation.Validatable {}
+
public extension PureXML.Schema.ComplexValidator {
// MARK: Streaming support
@@ -36,6 +38,19 @@ public extension PureXML.Schema.ComplexValidator {
}
}
+ /// The streaming path's document-scoped ID/IDREF check as a composable
+ /// ``PureXML/Validation`` value, reusing ``idErrors()`` (the same accumulator
+ /// the tree path's `idIdrefConsistent` rule reads) rather than the streaming
+ /// driver calling it imperatively outside the framework. The validator is its
+ /// own subject: by the time this runs (after the whole document has streamed
+ /// through ``shallowValidity``), its `idTracker` already holds every recorded
+ /// ID/IDREF.
+ static var idConsistency: PureXML.Validation.Validation {
+ .init(description: "Every streamed xs:ID is unique and every streamed xs:IDREF resolves to one") { context in
+ context.subject.idErrors()
+ }
+ }
+
/// The matched particle's `default`/`fixed` value constraint for a child of
/// `parent`'s content model, keyed by namespaced identity (so same-local-name
/// elements in different types do not collide as in the flat `elementConstraints`).
@@ -109,7 +124,9 @@ public extension PureXML.Schema.ComplexValidator {
guard case let .complex(complex) = parent else { return nil }
switch complex.content {
case let .elementOnly(particle), let .mixed(particle):
- if Self.elementTypes(in: particle.term)[Self.key(child)] != nil { return nil }
+ if Self.elementTypes(in: particle.term)[Self.key(child)] != nil {
+ return nil
+ }
guard Self.wildcardMatch(for: child, in: particle.term) == .strict,
types[PureXML.Schema.XSDParser.elementDeclarationKey(child)] == nil
else { return nil }
@@ -131,7 +148,9 @@ public extension PureXML.Schema.ComplexValidator {
return nil
case let .group(group):
for member in group.particles {
- if let match = wildcardMatch(for: name, in: member.term) { return match }
+ if let match = wildcardMatch(for: name, in: member.term) {
+ return match
+ }
}
return nil
}
@@ -160,28 +179,45 @@ public extension PureXML.Schema.ComplexValidator {
valueConstraint: PureXML.Schema.ValueConstraint? = nil,
at path: [PureXML.Validation.PathKey] = [],
) -> [PureXML.Validation.ValidationError] {
- var errors: [XSDFailure] = []
+ validateShallowTagged(element, as: type, valueConstraint: valueConstraint, at: path).map(\.error)
+ }
+
+ /// The tagged form of ``validateShallow(_:as:valueConstraint:at:)``. See
+ /// ``PureXML/Schema/ComplexValidator/validateTagged(_:against:at:namespaceBindings:)``.
+ private func validateShallowTagged(
+ _ element: PureXML.Model.Element,
+ as type: PureXML.Schema.ElementType,
+ valueConstraint: PureXML.Schema.ValueConstraint? = nil,
+ at path: [PureXML.Validation.PathKey] = [],
+ ) -> [XSDFinding] {
+ var errors: [XSDFinding] = []
switch type {
case let .simple(simple):
validateSimpleElement(element, simple, valueConstraint: valueConstraint, at: path, into: &errors)
case let .complex(complex):
validateAttributes(element, complex, at: path, into: &errors)
- if let nilErrors = nilErrors(element, at: path) { return errors + nilErrors }
+ if let nilErrors = nilErrors(element, at: path) {
+ return errors + nilErrors
+ }
shallowContent(element, complex.content, at: path, into: &errors)
// A simpleContent fixed value is compared in its simple type's value space
// (cvc-elt.5.2.2.1), matching the tree path's validate(_:against:).
- let simpleContentType: PureXML.Schema.SimpleType? = if case let .simpleContent(simple) = complex.content { simple } else { nil }
+ let simpleContentType: PureXML.Schema.SimpleType? = if case let .simpleContent(simple) = complex.content {
+ simple
+ } else {
+ nil
+ }
errors += elementFixedErrors(element, valueType: simpleContentType, at: path)
case .typeReference:
// The shared resolver also guards a circular chain, which would
// otherwise recurse here without terminating.
switch resolveReference(type) {
case let .unknown(name):
- return [XSDFailure(reason: "unknown type '\(name)'", at: path)]
+ return [XSDFinding(.typeReferenceResolves, reason: "unknown type '\(name)'", at: path)]
case let .circular(name):
- return [XSDFailure(reason: "circular type reference '\(name)'", at: path)]
+ return [XSDFinding(.typeReferenceResolves, reason: "circular type reference '\(name)'", at: path)]
case let .resolved(resolved):
- return validateShallow(element, as: resolved, valueConstraint: valueConstraint, at: path)
+ return validateShallowTagged(element, as: resolved, valueConstraint: valueConstraint, at: path)
}
}
return errors
@@ -192,21 +228,21 @@ public extension PureXML.Schema.ComplexValidator {
_ simple: PureXML.Schema.SimpleType,
valueConstraint: PureXML.Schema.ValueConstraint? = nil,
at path: XSDPath,
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) {
if let nilErrors = nilErrors(element, fixedValue: valueConstraint?.fixedValue, at: path) {
errors += nilErrors
return
}
if !element.children.compactMap(\.element).isEmpty {
- errors.append(XSDFailure(reason: "'\(element.name.localName)' must not have children", at: path))
+ errors.append(XSDFinding(.simpleTypeForbidsChildren, reason: "'\(element.name.localName)' must not have children", at: path))
}
// An empty element takes its `default`/`fixed` value; that value (not the
// empty string) is what must be valid against the type, matching the tree
// path's validateChild.
let text = Self.rawTextContent(element)
if let error = simple.validate(effectiveSimpleText(for: element, particleConstraint: valueConstraint)) {
- errors.append(XSDFailure(reason: "'\(element.name.localName)': \(error)", at: path))
+ errors.append(XSDFinding(.simpleTypeValueValid, reason: "'\(element.name.localName)': \(error)", at: path))
}
recordIDs(simple, value: text, at: path)
// Compare a present fixed value in the type's value space (e.g. "05" == "5"
@@ -215,17 +251,21 @@ public extension PureXML.Schema.ComplexValidator {
errors += elementFixedErrors(element, valueType: simple, particleFixed: valueConstraint?.fixedValue, at: path)
}
- private func shallowContent(_ element: PureXML.Model.Element, _ content: PureXML.Schema.ContentType, at path: XSDPath, into errors: inout [XSDFailure]) {
+ private func shallowContent(_ element: PureXML.Model.Element, _ content: PureXML.Schema.ContentType, at path: XSDPath, into errors: inout [XSDFinding]) {
let children = element.children.compactMap(\.element)
switch content {
case .empty:
- if !children.isEmpty { errors.append(XSDFailure(reason: "element must be empty", at: path)) }
+ if !children.isEmpty {
+ errors.append(XSDFinding(.emptyContentForbidsChildren, reason: "element must be empty", at: path))
+ }
rejectText(element, at: path, into: &errors)
case let .simpleContent(type):
- if !children.isEmpty { errors.append(XSDFailure(reason: "element must not have children", at: path)) }
+ if !children.isEmpty {
+ errors.append(XSDFinding(.simpleContentForbidsChildren, reason: "element must not have children", at: path))
+ }
let text = Self.rawTextContent(element)
if let error = type.validate(text) {
- errors.append(XSDFailure(reason: "content: \(error)", at: path))
+ errors.append(XSDFinding(.simpleContentTypeValid, reason: "content: \(error)", at: path))
}
recordIDs(type, value: text, at: path)
case let .elementOnly(particle):
@@ -236,7 +276,7 @@ public extension PureXML.Schema.ComplexValidator {
}
}
- private func shallowStructure(_ particle: PureXML.Schema.Particle, children: [PureXML.Model.Element], at path: XSDPath, into errors: inout [XSDFailure]) {
+ private func shallowStructure(_ particle: PureXML.Schema.Particle, children: [PureXML.Model.Element], at path: XSDPath, into errors: inout [XSDFinding]) {
if case let .group(group) = particle.term, group.compositor == .all {
allStructureErrors(group, children: children, groupOptional: particle.occurrenceRange.minimum.isZero, at: path, into: &errors)
} else {
diff --git a/Sources/Schema/ComplexValidatorWildcards.swift b/Sources/Schema/ComplexValidatorWildcards.swift
new file mode 100644
index 0000000..3bdd453
--- /dev/null
+++ b/Sources/Schema/ComplexValidatorWildcards.swift
@@ -0,0 +1,48 @@
+extension PureXML.Schema.ComplexValidator {
+ /// A global element declaration is always in the schema's target
+ /// namespace, so an instance element matches one only by full qualified
+ /// name. The namespaced key already covers a no-namespace global (its key
+ /// is `element:{}local`); matching an unqualified instance against a
+ /// namespaced global by bare local name would conflate namespaces, e.g.
+ /// accepting unqualified text where a namespaced `xs:short` is declared.
+ func globalElementDeclaration(for name: PureXML.Model.QualifiedName) -> PureXML.Schema.ElementType? {
+ types[PureXML.Schema.XSDParser.elementDeclarationKey(name)]
+ }
+
+ /// Validates a wildcard-matched child by its `processContents`: skip does
+ /// nothing, lax validates against a global declaration when one exists, and
+ /// strict requires one.
+ func validateWildcardChild(
+ _ child: PureXML.Model.Element,
+ processContents: PureXML.Schema.ProcessContents,
+ at path: XSDPath,
+ namespaceBindings: [String: String],
+ into errors: inout [XSDFinding],
+ ) {
+ switch processContents {
+ case .skip:
+ return
+ case .lax:
+ if let declaration = globalElementDeclaration(for: child.name) {
+ validateChild(child, against: declaration, at: path, namespaceBindings: namespaceBindings, into: &errors)
+ }
+ case .strict:
+ if let declaration = globalElementDeclaration(for: child.name) {
+ validateChild(child, against: declaration, at: path, namespaceBindings: namespaceBindings, into: &errors)
+ } else if let type = xsiDeclaredType(for: child, namespaceBindings: namespaceBindings) {
+ validateChild(child, against: type, at: path, namespaceBindings: namespaceBindings, into: &errors)
+ } else {
+ errors.append(XSDFinding(.wildcardElementUndeclared, reason: "no declaration for wildcard-matched element '\(child.name.localName)'", at: path))
+ }
+ }
+ }
+
+ /// The type named by a wildcard-matched element's `xsi:type`, when present.
+ func xsiDeclaredType(for child: PureXML.Model.Element, namespaceBindings: [String: String]) -> PureXML.Schema.ElementType? {
+ guard Self.xsiTypeAttributeValue(child) != nil else { return nil }
+ guard let reference = Self.xsiTypeReference(child, namespaceBindings: namespaceBindings),
+ let type = Self.resolveNamedType(reference, in: types)
+ else { return nil }
+ return type
+ }
+}
diff --git a/Sources/Schema/ComplexValidatorXSIType.swift b/Sources/Schema/ComplexValidatorXSIType.swift
index 2534b51..4fb8c87 100644
--- a/Sources/Schema/ComplexValidatorXSIType.swift
+++ b/Sources/Schema/ComplexValidatorXSIType.swift
@@ -7,20 +7,26 @@ extension PureXML.Schema.ComplexValidator {
for child: PureXML.Model.Element,
namespaceBindings: [String: String] = [:],
at path: XSDPath,
- into errors: inout [XSDFailure],
+ into errors: inout [XSDFinding],
) -> PureXML.Schema.ElementType? {
guard Self.xsiTypeAttributeValue(child) != nil else { return declared }
let reference = Self.xsiTypeReference(child, namespaceBindings: namespaceBindings) ?? Self.xsiTypeName(child) ?? ""
- if let resolved = Self.resolveNamedType(reference, in: types) { return resolved }
+ if let resolved = Self.resolveNamedType(reference, in: types) {
+ return resolved
+ }
let overriding = Self.xsiTypeName(child) ?? reference
if let builtin = PureXML.Schema.BuiltinType(rawValue: overriding) {
if xsiBuiltinSubstitutes(builtin, for: declared) {
return .simple(PureXML.Schema.SimpleType(base: builtin))
}
- errors.append(XSDFailure(reason: "xsi:type '\(overriding)' is not validly derived from the declared type of '\(child.name.localName)'", at: path))
+ errors.append(XSDFinding(
+ .xsiTypeOverrideResolves,
+ reason: "xsi:type '\(overriding)' is not validly derived from the declared type of '\(child.name.localName)'",
+ at: path,
+ ))
return nil
}
- errors.append(XSDFailure(reason: "unknown xsi:type '\(overriding)' on '\(child.name.localName)'", at: path))
+ errors.append(XSDFinding(.xsiTypeOverrideResolves, reason: "unknown xsi:type '\(overriding)' on '\(child.name.localName)'", at: path))
return nil
}
@@ -32,7 +38,9 @@ extension PureXML.Schema.ComplexValidator {
private func xsiBuiltinSubstitutes(_ xsi: PureXML.Schema.BuiltinType, for declared: PureXML.Schema.ElementType) -> Bool {
switch declared {
case let .typeReference(name):
- if name == "anyType" || name == "anySimpleType" || name.hasSuffix("}anyType") || name.hasSuffix("}anySimpleType") { return true }
+ if name == "anyType" || name == "anySimpleType" || name.hasSuffix("}anyType") || name.hasSuffix("}anySimpleType") {
+ return true
+ }
switch resolveReference(declared) {
case let .resolved(resolved): return xsiBuiltinSubstitutes(xsi, for: resolved)
case .unknown, .circular: return false
diff --git a/Sources/Schema/ContentMatcher.swift b/Sources/Schema/ContentMatcher.swift
index 6f27327..9dbbabd 100644
--- a/Sources/Schema/ContentMatcher.swift
+++ b/Sources/Schema/ContentMatcher.swift
@@ -140,7 +140,9 @@ extension PureXML.Schema {
func admissible(from current: Set) -> [TermLabel] {
var labels: [TermLabel] = []
for configuration in current where states[configuration.state].target != nil {
- if let label = states[configuration.state].label { labels.append(label) }
+ if let label = states[configuration.state].label {
+ labels.append(label)
+ }
}
return labels
}
@@ -203,7 +205,9 @@ extension PureXML.Schema {
next.insert(ContentConfiguration(state: target, counters: configuration.counters))
}
}
- if next.isEmpty { return nil }
+ if next.isEmpty {
+ return nil
+ }
return closure(next, caps: counterCaps(for: inputLength))
}
diff --git a/Sources/Schema/ContentModelDeterminism.swift b/Sources/Schema/ContentModelDeterminism.swift
index 7d19681..9b693e5 100644
--- a/Sources/Schema/ContentModelDeterminism.swift
+++ b/Sources/Schema/ContentModelDeterminism.swift
@@ -63,8 +63,12 @@ public extension PureXML.Schema {
/// foreign annotation content.
private static func forEachComplexType(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
- if local == "complexType" { visit(node) }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
+ if local == "complexType" {
+ visit(node)
+ }
for child in PureXML.Schema.XSDNode.elementChildren(node) {
forEachComplexType(child, visit)
}
diff --git a/Sources/Schema/ContentModelDeterminismCompositional.swift b/Sources/Schema/ContentModelDeterminismCompositional.swift
index 0ab17b7..a3c1cea 100644
--- a/Sources/Schema/ContentModelDeterminismCompositional.swift
+++ b/Sources/Schema/ContentModelDeterminismCompositional.swift
@@ -98,7 +98,9 @@ private struct CompWalk {
var identityConflict: String?
mutating func emit(_ set: [CompItem]) {
- if set.count > 1 { decisionSets.append(set) }
+ if set.count > 1 {
+ decisionSets.append(set)
+ }
}
/// Dedupe a decision set (first set or follow set) by particle identity. A repeated
@@ -156,13 +158,17 @@ private struct CompWalk {
}
private func labelName(_ label: PureXML.Schema.TermLabel) -> String {
- if case let .name(qname) = label { return qname.localName }
+ if case let .name(qname) = label {
+ return qname.localName
+ }
return "any"
}
mutating func summarize(_ node: PureXML.Model.TreeNode, visiting: Set) -> CompSummary {
let (minimum, maximum) = PureXML.Schema.XSDNode.occurrence(node)
- if let maximum, maximum <= 0 { return CompSummary(nullable: true, first: [], lastFollow: []) }
+ if let maximum, maximum <= 0 {
+ return CompSummary(nullable: true, first: [], lastFollow: [])
+ }
var body = bodySummary(node, visiting: visiting)
// A repeat edge folds the body's first into every end-item's follow. Kept in a
// separate method, not inline: `summarize` recurses on nesting depth, so any
@@ -227,7 +233,9 @@ private struct CompWalk {
var suffix: [CompItem] = []
for next in parts[(index + 1)...] {
suffix.append(contentsOf: next.first)
- if !next.nullable { break }
+ if !next.nullable {
+ break
+ }
}
suffixFirst[index] = suffix
}
@@ -239,7 +247,9 @@ private struct CompWalk {
var prefixFirsts: [[CompItem]] = []
for part in parts {
prefixFirsts.append(part.first)
- if !part.nullable { break }
+ if !part.nullable {
+ break
+ }
}
// Each trailing end-item carries its internal follow PLUS the suffix that follows
// it inside the sequence. Otherwise an end-item that is also followed internally
@@ -253,7 +263,9 @@ private struct CompWalk {
for entry in parts[index].lastFollow {
trailing.append((entry.item, dedupOnly([entry.follow, suffixFirst[index]])))
}
- if !parts[index].nullable { break }
+ if !parts[index].nullable {
+ break
+ }
}
return CompSummary(nullable: parts.allSatisfy(\.nullable), first: mergeList(prefixFirsts), lastFollow: mergeLastFollow(trailing))
}
@@ -293,8 +305,12 @@ private struct CompWalk {
private mutating func group(_ node: PureXML.Model.TreeNode, visiting: Set) -> CompSummary {
guard let ref = PureXML.Schema.XSDNode.attribute(node, "ref") else { return CompSummary(nullable: true, first: [], lastFollow: []) }
let name = PureXML.Schema.XSDNode.stripPrefix(ref)
- if visiting.contains(name) { return redefineSelfReference(name, visiting: visiting) }
- if let cached = groupCache[name] { return cached }
+ if visiting.contains(name) {
+ return redefineSelfReference(name, visiting: visiting)
+ }
+ if let cached = groupCache[name] {
+ return cached
+ }
guard let definition = context.groups[name], let model = modelChild(definition)
else { return CompSummary(nullable: true, first: [], lastFollow: []) }
// Summarize the group once: its internal decision sets are context-independent
diff --git a/Sources/Schema/ContentModelDeterminismConflict.swift b/Sources/Schema/ContentModelDeterminismConflict.swift
index 737ef10..3a2f33c 100644
--- a/Sources/Schema/ContentModelDeterminismConflict.swift
+++ b/Sources/Schema/ContentModelDeterminismConflict.swift
@@ -11,8 +11,12 @@ enum CompConflict {
for item in set {
switch item.label {
case let .name(qname):
- if let owner = nameOwner[qname], owner != item.particle { return qname.localName }
- if nameOwner[qname] == nil { nameOwner[qname] = item.particle }
+ if let owner = nameOwner[qname], owner != item.particle {
+ return qname.localName
+ }
+ if nameOwner[qname] == nil {
+ nameOwner[qname] = item.particle
+ }
case let .wildcard(card):
wildcards.append((card, item.particle))
}
@@ -20,7 +24,9 @@ enum CompConflict {
for (index, wildcard) in wildcards.enumerated() {
// A wildcard conflicts with any name it admits, from a distinct particle,
// and with any other wildcard it intersects, from a distinct particle.
- if nameOwner.contains(where: { wildcard.card.admits($0.key) && $0.value != wildcard.particle }) { return "any" }
+ if nameOwner.contains(where: { wildcard.card.admits($0.key) && $0.value != wildcard.particle }) {
+ return "any"
+ }
for other in wildcards[(index + 1)...] where other.particle != wildcard.particle && wildcardsOverlap(wildcard.card, other.card) {
return "any"
}
diff --git a/Sources/Schema/ContentModelEffectiveModel.swift b/Sources/Schema/ContentModelEffectiveModel.swift
index a8f1ce4..35d3349 100644
--- a/Sources/Schema/ContentModelEffectiveModel.swift
+++ b/Sources/Schema/ContentModelEffectiveModel.swift
@@ -3,10 +3,14 @@ extension PureXML.Schema.ContentModelDeterminism {
/// complex type's content model, reached through `complexContent` and its
/// `restriction`/`extension`, or nil for simple or empty content.
static func contentModelNode(_ complexType: XSDTree) -> XSDTree? {
- if let direct = modelGroupChild(complexType) { return direct }
+ if let direct = modelGroupChild(complexType) {
+ return direct
+ }
guard let complexContent = PureXML.Schema.XSDNode.firstChild(complexType, named: "complexContent") else { return nil }
for derivation in PureXML.Schema.XSDNode.elementChildren(complexContent) {
- if let model = modelGroupChild(derivation) { return model }
+ if let model = modelGroupChild(derivation) {
+ return model
+ }
}
return nil
}
diff --git a/Sources/Schema/ContentModelGroupDependency.swift b/Sources/Schema/ContentModelGroupDependency.swift
index 28f8f24..428e288 100644
--- a/Sources/Schema/ContentModelGroupDependency.swift
+++ b/Sources/Schema/ContentModelGroupDependency.swift
@@ -28,7 +28,9 @@ enum GroupDependencyOrder {
order.append(name)
continue
}
- if visited.contains(name) { continue }
+ if visited.contains(name) {
+ continue
+ }
visited.insert(name)
onPath.insert(name)
work.append((name, true))
diff --git a/Sources/Schema/ContentNFABuilder.swift b/Sources/Schema/ContentNFABuilder.swift
index b355201..f37ab3e 100644
--- a/Sources/Schema/ContentNFABuilder.swift
+++ b/Sources/Schema/ContentNFABuilder.swift
@@ -108,7 +108,11 @@ extension PureXML.Schema {
var nullable = true
for member in particles {
let part = particle(member)
- if let previous = last { addEpsilon(from: previous, to: part.start) } else { first = part.start }
+ if let previous = last {
+ addEpsilon(from: previous, to: part.start)
+ } else {
+ first = part.start
+ }
last = part.accept
fragmentCounters.append(contentsOf: part.counters)
nullable = nullable && part.nullable
diff --git a/Sources/Schema/ContentRTN.swift b/Sources/Schema/ContentRTN.swift
index 2f0eb81..dc2d8b8 100644
--- a/Sources/Schema/ContentRTN.swift
+++ b/Sources/Schema/ContentRTN.swift
@@ -139,7 +139,9 @@ extension PureXML.Schema {
/// The shared fragment for a group definition, compiled once and memoized.
/// Its accept is marked `isReturn`.
private mutating func groupFragment(_ group: PureXML.Schema.Group, id: Int) -> ContentNFAFragment {
- if let cached = fragments[id] { return cached }
+ if let cached = fragments[id] {
+ return cached
+ }
// `definitionID` groups are acyclic: XSD forbids a group referencing itself
// except through an element, and the parser's expansion breaks every cycle at
// an element boundary, so a fragment is never referenced during its own
@@ -176,7 +178,11 @@ extension PureXML.Schema {
var nullable = true
for member in particles {
let part = particle(member)
- if let previous = last { addEpsilon(from: previous, to: part.start) } else { first = part.start }
+ if let previous = last {
+ addEpsilon(from: previous, to: part.start)
+ } else {
+ first = part.start
+ }
last = part.accept
fragmentCounters.append(contentsOf: part.counters)
nullable = nullable && part.nullable
diff --git a/Sources/Schema/ContentRTNMatcher.swift b/Sources/Schema/ContentRTNMatcher.swift
index 923ea47..9fbcf44 100644
--- a/Sources/Schema/ContentRTNMatcher.swift
+++ b/Sources/Schema/ContentRTNMatcher.swift
@@ -109,7 +109,9 @@ extension PureXML.Schema {
next.insert(ContentRTNConfiguration(state: target, counters: configuration.counters, gss: configuration.gss))
}
}
- if next.isEmpty { return nil }
+ if next.isEmpty {
+ return nil
+ }
position += 1
return closure(next)
}
@@ -121,7 +123,9 @@ extension PureXML.Schema {
func admissible(from current: Set) -> [TermLabel] {
var labels: [TermLabel] = []
for configuration in current where rtn.states[configuration.state].target != nil {
- if let label = rtn.states[configuration.state].label { labels.append(label) }
+ if let label = rtn.states[configuration.state].label {
+ labels.append(label)
+ }
}
return labels
}
@@ -154,8 +158,12 @@ extension PureXML.Schema {
let state = configuration.state
guard let label = rtn.states[state].label, let target = rtn.states[state].target, label.matches(name) else { continue }
next.insert(ContentRTNConfiguration(state: target, counters: configuration.counters, gss: configuration.gss))
- if anyState.map({ state < $0 }) ?? true { anyState = state }
- if case .name = label, namedState.map({ state < $0 }) ?? true { namedState = state }
+ if anyState.map({ state < $0 }) ?? true {
+ anyState = state
+ }
+ if case .name = label, namedState.map({ state < $0 }) ?? true {
+ namedState = state
+ }
}
guard let chosenState = namedState ?? anyState, !next.isEmpty else {
result.append(contentsOf: Array(repeating: nil, count: names.count - result.count))
@@ -190,7 +198,9 @@ extension PureXML.Schema {
while true {
if !pending.isEmpty {
let configuration = pending.removeLast()
- if seen.insert(configuration).inserted { work.append(configuration) }
+ if seen.insert(configuration).inserted {
+ work.append(configuration)
+ }
continue
}
guard let configuration = work.popLast() else { break }
@@ -203,7 +213,9 @@ extension PureXML.Schema {
for call in state.calls {
let node = internNode(returnState: call.returnTo, savedCounters: configuration.counters, parent: configuration.gss)
let nextConfiguration = ContentRTNConfiguration(state: call.calleeStart, counters: configuration.counters, gss: node)
- if seen.insert(nextConfiguration).inserted { work.append(nextConfiguration) }
+ if seen.insert(nextConfiguration).inserted {
+ work.append(nextConfiguration)
+ }
drainPending(into: &seen, work: &work)
}
if state.isReturn, configuration.gss != 0 {
@@ -218,7 +230,9 @@ extension PureXML.Schema {
private func drainPending(into seen: inout Set, work: inout [ContentRTNConfiguration]) {
while let configuration = pending.popLast() {
- if seen.insert(configuration).inserted { work.append(configuration) }
+ if seen.insert(configuration).inserted {
+ work.append(configuration)
+ }
}
}
diff --git a/Sources/Schema/DateTimeParser.swift b/Sources/Schema/DateTimeParser.swift
index 3f415a2..5e2fed2 100644
--- a/Sources/Schema/DateTimeParser.swift
+++ b/Sources/Schema/DateTimeParser.swift
@@ -95,7 +95,9 @@ extension PureXML.Schema {
guard let hour = readFixed(2), consume(":"), let minute = readFixed(2), consume(":"),
let second = readFixed(2) else { return false }
guard (0 ... 24).contains(hour), (0 ... 59).contains(minute), (0 ... 59).contains(second) else { return false }
- if hour == 24, minute != 0 || second != 0 { return false }
+ if hour == 24, minute != 0 || second != 0 {
+ return false
+ }
value.hour = hour
value.minute = minute
value.second = second
@@ -107,12 +109,15 @@ extension PureXML.Schema {
}
private mutating func parseTimezone(into value: inout DateTimeValue) -> Bool {
- if consume("Z") { value.offsetMinutes = 0
+ if consume("Z") {
+ value.offsetMinutes = 0
return true
}
guard peek() == "+" || peek() == "-" else { return true }
let negative = consume("-")
- if !negative { advance() }
+ if !negative {
+ advance()
+ }
guard let hour = readFixed(2), consume(":"), let minute = readFixed(2) else { return false }
guard (0 ... 14).contains(hour), (0 ... 59).contains(minute), hour * 60 + minute <= 14 * 60 else {
return false
@@ -136,7 +141,9 @@ extension PureXML.Schema {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
private mutating func consume(_ literal: String) -> Bool {
diff --git a/Sources/Schema/DecimalValue.swift b/Sources/Schema/DecimalValue.swift
index 0299ef3..d953810 100644
--- a/Sources/Schema/DecimalValue.swift
+++ b/Sources/Schema/DecimalValue.swift
@@ -65,7 +65,9 @@ extension PureXML.Schema {
}
static func < (lhs: DecimalValue, rhs: DecimalValue) -> Bool {
- if lhs.negative != rhs.negative { return lhs.negative }
+ if lhs.negative != rhs.negative {
+ return lhs.negative
+ }
let magnitude = compareMagnitude(lhs, rhs)
return lhs.negative ? magnitude > 0 : magnitude < 0
}
@@ -75,7 +77,9 @@ extension PureXML.Schema {
if lhs.integerDigits.count != rhs.integerDigits.count {
return lhs.integerDigits.count < rhs.integerDigits.count ? -1 : 1
}
- if let order = lexicographic(lhs.integerDigits, rhs.integerDigits) { return order }
+ if let order = lexicographic(lhs.integerDigits, rhs.integerDigits) {
+ return order
+ }
return lexicographic(lhs.fractionDigits, rhs.fractionDigits) ?? 0
}
@@ -85,7 +89,9 @@ extension PureXML.Schema {
for (left, right) in zip(lhs, rhs) where left != right {
return left < right ? -1 : 1
}
- if lhs.count == rhs.count { return nil }
+ if lhs.count == rhs.count {
+ return nil
+ }
return lhs.count < rhs.count ? -1 : 1
}
}
diff --git a/Sources/Schema/DurationValue.swift b/Sources/Schema/DurationValue.swift
index adfaf99..ffe4802 100644
--- a/Sources/Schema/DurationValue.swift
+++ b/Sources/Schema/DurationValue.swift
@@ -36,9 +36,15 @@ extension PureXML.Schema {
}
// P < Q only when s+P < s+Q at every reference; > likewise; equal
// when all four coincide (which holds iff the components are equal).
- if sawLess, !sawGreater, !sawEqual { return .lessThan }
- if sawGreater, !sawLess, !sawEqual { return .greaterThan }
- if sawEqual, !sawLess, !sawGreater { return .equal }
+ if sawLess, !sawGreater, !sawEqual {
+ return .lessThan
+ }
+ if sawGreater, !sawLess, !sawEqual {
+ return .greaterThan
+ }
+ if sawEqual, !sawLess, !sawGreater {
+ return .equal
+ }
return .incomparable
}
diff --git a/Sources/Schema/IDTracker.swift b/Sources/Schema/IDTracker.swift
index 90e6e5e..3494da2 100644
--- a/Sources/Schema/IDTracker.swift
+++ b/Sources/Schema/IDTracker.swift
@@ -35,19 +35,25 @@ extension PureXML.Schema {
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 }
+ 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 }
+ 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 }
+ if case let .list(item) = variety {
+ return item.base == .idref
+ }
return false
}
}
diff --git a/Sources/Schema/IdentityFieldSimplicity.swift b/Sources/Schema/IdentityFieldSimplicity.swift
index 4486286..ef46374 100644
--- a/Sources/Schema/IdentityFieldSimplicity.swift
+++ b/Sources/Schema/IdentityFieldSimplicity.swift
@@ -38,7 +38,9 @@ extension PureXML.Schema.XSDParser {
var current: XSDTree? = start
for rawStep in path.split(separator: "/", omittingEmptySubsequences: false) {
let step = String(rawStep).trimmingXMLWhitespace()
- if step == "." { continue }
+ if step == "." {
+ continue
+ }
guard isSimpleNameTest(step), let node = current,
let child = directChildElement(named: PureXML.Schema.XSDNode.stripPrefix(step), of: node)
else { return nil }
@@ -92,7 +94,9 @@ extension PureXML.Schema.XSDParser {
}
return local == "anyType"
}
- if PureXML.Schema.XSDNode.firstChild(element, named: "simpleType") != nil { return false }
+ if PureXML.Schema.XSDNode.firstChild(element, named: "simpleType") != nil {
+ return false
+ }
if let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType") {
return PureXML.Schema.XSDNode.firstChild(complex, named: "simpleContent") == nil
}
diff --git a/Sources/Schema/IdentityFieldType.swift b/Sources/Schema/IdentityFieldType.swift
index 10b1ec4..d45a3f7 100644
--- a/Sources/Schema/IdentityFieldType.swift
+++ b/Sources/Schema/IdentityFieldType.swift
@@ -13,7 +13,9 @@ extension PureXML.Schema.IdentityValidator {
let fieldType = childElementType(named: field, in: targetComplex.content),
case let .complex(fieldComplex) = fieldType
else { return false }
- if case .simpleContent = fieldComplex.content { return false }
+ if case .simpleContent = fieldComplex.content {
+ return false
+ }
return true
}
diff --git a/Sources/Schema/IdentityTupleSet.swift b/Sources/Schema/IdentityTupleSet.swift
index 36cd0cb..cec44b2 100644
--- a/Sources/Schema/IdentityTupleSet.swift
+++ b/Sources/Schema/IdentityTupleSet.swift
@@ -49,7 +49,9 @@ struct TupleSet {
key.reserveCapacity(tuple.count)
for field in tuple {
guard let field, !field.string.contains(":") else { return nil }
- if let type = field.type, !type.comparesRawLexically { return nil }
+ if let type = field.type, !type.comparesRawLexically {
+ return nil
+ }
key.append(field.string)
}
return key
diff --git a/Sources/Schema/IdentityValidator.swift b/Sources/Schema/IdentityValidator.swift
index e37a392..551d019 100644
--- a/Sources/Schema/IdentityValidator.swift
+++ b/Sources/Schema/IdentityValidator.swift
@@ -49,11 +49,22 @@ extension PureXML.Schema {
_ root: PureXML.Model.TreeNode,
at path: [PureXML.Validation.PathKey] = [],
) -> [PureXML.Validation.ValidationError] {
+ validateTagged(root, at: path).map(\.error)
+ }
+
+ /// The tagged form of ``validate(_:at:)``: the same computation, with each
+ /// finding labeled by the decomposed identity rule it belongs to. The
+ /// public, untagged form is a thin `.map(\.error)` over this so behavior and
+ /// output stay identical for every existing caller.
+ func validateTagged(
+ _ root: PureXML.Model.TreeNode,
+ at path: [PureXML.Validation.PathKey] = [],
+ ) -> [XSDFinding] {
guard !constraints.isEmpty else { return [] }
// A selector or field XPath that does not compile is a schema-author
// error, reported once up front rather than silently disabling the
// constraint (the broken query still evaluates as no-match below).
- var issues: [PureXML.Validation.ValidationError] = compileErrors(at: path)
+ var issues: [XSDFinding] = compileErrors(at: path)
walk(root, at: path, scopes: [], into: &issues)
return issues
}
@@ -61,15 +72,19 @@ extension PureXML.Schema {
/// One located error per constraint XPath that fails to compile, in a
/// deterministic order. The failed compilations are cached, so the walk
/// afterwards treats those constraints as matching nothing.
- private func compileErrors(at path: [PureXML.Validation.PathKey]) -> [PureXML.Validation.ValidationError] {
- var errors: [PureXML.Validation.ValidationError] = []
+ private func compileErrors(at path: [PureXML.Validation.PathKey]) -> [XSDFinding] {
+ var errors: [XSDFinding] = []
for key in constraints.keys.sorted() {
for constraint in constraints[key] ?? [] {
if cache.query(constraint.selector) == nil {
- errors.append(.init(reason: "identity constraint '\(constraint.name)': invalid selector XPath '\(constraint.selector)'", at: path))
+ errors.append(XSDFinding(
+ .identityConstraintXPathsValid,
+ reason: "identity constraint '\(constraint.name)': invalid selector XPath '\(constraint.selector)'",
+ at: path,
+ ))
}
for field in constraint.fields where cache.query(field) == nil {
- errors.append(.init(reason: "identity constraint '\(constraint.name)': invalid field XPath '\(field)'", at: path))
+ errors.append(XSDFinding(.identityConstraintXPathsValid, reason: "identity constraint '\(constraint.name)': invalid field XPath '\(field)'", at: path))
}
}
}
@@ -80,7 +95,7 @@ extension PureXML.Schema {
_ node: PureXML.Model.TreeNode,
at path: [PureXML.Validation.PathKey],
scopes: [[String: [[FieldValue?]]]],
- into issues: inout [PureXML.Validation.ValidationError],
+ into issues: inout [XSDFinding],
) {
var frame: [String: [[FieldValue?]]] = [:]
let declared = node.name.flatMap { constraints[$0.localName] } ?? []
@@ -132,7 +147,7 @@ extension PureXML.Schema {
at node: PureXML.Model.TreeNode,
path: [PureXML.Validation.PathKey],
frame: inout [String: [[FieldValue?]]],
- into issues: inout [PureXML.Validation.ValidationError],
+ into issues: inout [XSDFinding],
) {
guard isNonRef(constraint) else { return }
var tuples: [[FieldValue?]] = []
@@ -145,15 +160,19 @@ extension PureXML.Schema {
}
let tuple = fieldTuple(constraint.fields, at: target, constraint: constraint)
if constraint.kind == .key, tuple.contains(where: { $0 == nil }) {
- issues.append(.init(
+ issues.append(XSDFinding(
+ .identityKeyFieldPresent,
reason: "key '\(constraint.name)': a field is missing",
at: targetPath + fieldStep(constraint.fields, at: target, namespaces: constraint.namespaceBindings),
))
continue
}
- if tuple.contains(where: { $0 == nil }) { continue }
+ if tuple.contains(where: { $0 == nil }) {
+ continue
+ }
if seen.contains(tuple) {
- issues.append(.init(
+ issues.append(XSDFinding(
+ .identityValueUnique,
reason: "\(label(constraint)) '\(constraint.name)': duplicate value",
at: targetPath + fieldStep(constraint.fields, at: target, namespaces: constraint.namespaceBindings),
))
@@ -170,7 +189,7 @@ extension PureXML.Schema {
at node: PureXML.Model.TreeNode,
path: [PureXML.Validation.PathKey],
scopes: [[String: [[FieldValue?]]]],
- into issues: inout [PureXML.Validation.ValidationError],
+ into issues: inout [XSDFinding],
) {
guard case let .keyref(refer) = constraint.kind else { return }
let keyTuples = scopes.reversed().compactMap { $0[refer] }.first ?? []
@@ -185,9 +204,12 @@ extension PureXML.Schema {
continue
}
let tuple = fieldTuple(constraint.fields, at: target, constraint: constraint)
- if tuple.contains(where: { $0 == nil }) { continue }
+ if tuple.contains(where: { $0 == nil }) {
+ continue
+ }
if !keys.contains(tuple) {
- issues.append(.init(
+ issues.append(XSDFinding(
+ .identityKeyrefResolves,
reason: "keyref '\(constraint.name)': no matching key '\(refer)'",
at: targetPath + fieldStep(constraint.fields, at: target, namespaces: constraint.namespaceBindings),
))
@@ -203,13 +225,14 @@ extension PureXML.Schema {
_ constraint: IdentityConstraint,
at target: PureXML.Model.TreeNode,
path targetPath: [PureXML.Validation.PathKey],
- ) -> PureXML.Validation.ValidationError? {
+ ) -> XSDFinding? {
let step = fieldStep(constraint.fields, at: target, namespaces: constraint.namespaceBindings)
for field in constraint.fields {
let selected = select(field, at: target, namespaces: constraint.namespaceBindings)
// cvc-identity-constraint.3: each field must evaluate to at most one node.
if selected.count > 1 {
- return .init(
+ return XSDFinding(
+ .identityFieldCardinality,
reason: "\(label(constraint)) '\(constraint.name)': field '\(field)' must select at most one node",
at: targetPath + step,
)
@@ -235,7 +258,8 @@ extension PureXML.Schema {
&& Self.xsiTypeLocalName(of: node) == nil
} ?? false
if element != nil, hasElementChildren || declaredNonSimple || fieldTargetIsComplexNonSimple(field, at: target) {
- return .init(
+ return XSDFinding(
+ .identityFieldMustBeSimple,
reason: "\(label(constraint)) '\(constraint.name)': field '\(field)' must identify a node with simple content",
at: targetPath + step,
)
@@ -270,7 +294,9 @@ extension PureXML.Schema {
if let defaulted = defaultedValue(field, value: value, type: type, constraint: constraint, at: node) {
return defaulted
}
- if let nodes = value.nodes, nodes.isEmpty { return nil }
+ if let nodes = value.nodes, nodes.isEmpty {
+ return nil
+ }
return FieldValue(string: value.string, type: type ?? Self.effectiveType(of: value), namespaceBindings: instanceNamespaces)
}
}
@@ -319,69 +345,3 @@ extension PureXML.Schema {
}
}
}
-
-extension PureXML.Schema.IdentityValidator {
- func simpleType(of declaration: PureXML.Schema.ElementType) -> PureXML.Schema.SimpleType? {
- switch declaration {
- case let .simple(simple): simple
- case let .complex(complex):
- if case let .simpleContent(simple) = complex.content { simple } else { nil }
- case .typeReference: nil
- }
- }
-
- static func effectiveType(of value: PureXML.XPath.Value) -> PureXML.Schema.SimpleType? {
- guard let nodes = value.nodes, case let .tree(treeNode)? = nodes.first,
- treeNode.kind == .element, let local = xsiTypeLocalName(of: treeNode),
- let builtin = PureXML.Schema.BuiltinType(rawValue: local)
- else { return nil }
- return PureXML.Schema.SimpleType(base: builtin)
- }
-
- /// The local name of a tree node's `xsi:type` attribute value, recognized by
- /// the schema-instance namespace or the conventional `xsi` prefix, or nil.
- static func xsiTypeLocalName(of node: PureXML.Model.TreeNode) -> String? {
- let schemaInstance = "http://www.w3.org/2001/XMLSchema-instance"
- guard let attribute = node.attributes.first(where: {
- $0.name.localName == "type" && ($0.name.namespaceURI == schemaInstance || $0.name.prefix == "xsi")
- }) else { return nil }
- return attribute.value.split(separator: ":").last.map(String.init) ?? attribute.value
- }
-
- func label(_ constraint: PureXML.Schema.IdentityConstraint) -> String {
- constraint.kind == .key ? "key" : "unique"
- }
-
- /// Whether a constraint collects tuples (`key`/`unique`) rather than
- /// referencing them (`keyref`).
- func isNonRef(_ constraint: PureXML.Schema.IdentityConstraint) -> Bool {
- if case .keyref = constraint.kind { return false }
- return true
- }
-
- /// The coding-path steps from the constraint-bearing `container` down to a
- /// selected `target`, so an identity error locates the actual offending
- /// element rather than the element that merely declares the constraint.
- /// Returns an empty path if `target` is not a descendant of `container`.
- func steps(from container: PureXML.Model.TreeNode, to target: PureXML.Model.TreeNode) -> [PureXML.Validation.PathKey] {
- var chain: [PureXML.Model.TreeNode] = []
- var current = target
- while current !== container {
- guard let parent = current.parent else { return [] }
- chain.append(current)
- current = parent
- }
- var steps: [PureXML.Validation.PathKey] = []
- var parent = container
- for node in chain.reversed() {
- let name = node.name?.description ?? ""
- if let index = stepIndex.position(of: node, under: parent, name: name) {
- steps.append(.element(name, index: index))
- } else {
- steps.append(.element(name))
- }
- parent = node
- }
- return steps
- }
-}
diff --git a/Sources/Schema/IdentityValidatorDefaults.swift b/Sources/Schema/IdentityValidatorDefaults.swift
index 24bec62..dce41cf 100644
--- a/Sources/Schema/IdentityValidatorDefaults.swift
+++ b/Sources/Schema/IdentityValidatorDefaults.swift
@@ -23,7 +23,9 @@ extension PureXML.Schema.IdentityValidator {
/// character content; any other field is empty only on an empty node-set.
func isFieldEmpty(_ field: String, value: PureXML.XPath.Value) -> Bool {
guard let nodes = value.nodes else { return false }
- if field.hasPrefix("@") { return nodes.isEmpty }
+ if field.hasPrefix("@") {
+ return nodes.isEmpty
+ }
if field == "." {
guard case let .tree(element)? = nodes.first, element.kind == .element else { return nodes.isEmpty }
return !element.children.contains { $0.kind == .text || $0.kind == .cdata }
@@ -41,7 +43,9 @@ extension PureXML.Schema.IdentityValidator {
) -> PureXML.Schema.ValueConstraint? {
if field == ".", let local = target.name?.localName {
let key = PureXML.Schema.XSDParser.identityFieldKey(constraint: constraint, field: field, targetLocal: local)
- if let value = fieldConstraints[key] { return value }
+ if let value = fieldConstraints[key] {
+ return value
+ }
}
return fieldConstraints[PureXML.Schema.XSDParser.identityFieldKey(constraint: constraint, field: field)]
}
diff --git a/Sources/Schema/IdentityValidatorHelpers.swift b/Sources/Schema/IdentityValidatorHelpers.swift
new file mode 100644
index 0000000..c037b76
--- /dev/null
+++ b/Sources/Schema/IdentityValidatorHelpers.swift
@@ -0,0 +1,71 @@
+extension PureXML.Schema.IdentityValidator {
+ func simpleType(of declaration: PureXML.Schema.ElementType) -> PureXML.Schema.SimpleType? {
+ switch declaration {
+ case let .simple(simple): simple
+ case let .complex(complex):
+ if case let .simpleContent(simple) = complex.content {
+ simple
+ } else {
+ nil
+ }
+ case .typeReference: nil
+ }
+ }
+
+ static func effectiveType(of value: PureXML.XPath.Value) -> PureXML.Schema.SimpleType? {
+ guard let nodes = value.nodes, case let .tree(treeNode)? = nodes.first,
+ treeNode.kind == .element, let local = xsiTypeLocalName(of: treeNode),
+ let builtin = PureXML.Schema.BuiltinType(rawValue: local)
+ else { return nil }
+ return PureXML.Schema.SimpleType(base: builtin)
+ }
+
+ /// The local name of a tree node's `xsi:type` attribute value, recognized by
+ /// the schema-instance namespace or the conventional `xsi` prefix, or nil.
+ static func xsiTypeLocalName(of node: PureXML.Model.TreeNode) -> String? {
+ let schemaInstance = "http://www.w3.org/2001/XMLSchema-instance"
+ guard let attribute = node.attributes.first(where: {
+ $0.name.localName == "type" && ($0.name.namespaceURI == schemaInstance || $0.name.prefix == "xsi")
+ }) else { return nil }
+ return attribute.value.split(separator: ":").last.map(String.init) ?? attribute.value
+ }
+
+ func label(_ constraint: PureXML.Schema.IdentityConstraint) -> String {
+ constraint.kind == .key ? "key" : "unique"
+ }
+
+ /// Whether a constraint collects tuples (`key`/`unique`) rather than
+ /// referencing them (`keyref`).
+ func isNonRef(_ constraint: PureXML.Schema.IdentityConstraint) -> Bool {
+ if case .keyref = constraint.kind {
+ return false
+ }
+ return true
+ }
+
+ /// The coding-path steps from the constraint-bearing `container` down to a
+ /// selected `target`, so an identity error locates the actual offending
+ /// element rather than the element that merely declares the constraint.
+ /// Returns an empty path if `target` is not a descendant of `container`.
+ func steps(from container: PureXML.Model.TreeNode, to target: PureXML.Model.TreeNode) -> [PureXML.Validation.PathKey] {
+ var chain: [PureXML.Model.TreeNode] = []
+ var current = target
+ while current !== container {
+ guard let parent = current.parent else { return [] }
+ chain.append(current)
+ current = parent
+ }
+ var steps: [PureXML.Validation.PathKey] = []
+ var parent = container
+ for node in chain.reversed() {
+ let name = node.name?.description ?? ""
+ if let index = stepIndex.position(of: node, under: parent, name: name) {
+ steps.append(.element(name, index: index))
+ } else {
+ steps.append(.element(name))
+ }
+ parent = node
+ }
+ return steps
+ }
+}
diff --git a/Sources/Schema/Lexical.swift b/Sources/Schema/Lexical.swift
index 8c1ce1f..0e23fe3 100644
--- a/Sources/Schema/Lexical.swift
+++ b/Sources/Schema/Lexical.swift
@@ -9,9 +9,13 @@ extension PureXML.Schema {
/// `float`/`double`: an optional-sign mantissa with an optional exponent,
/// or the special values `INF`, `-INF`, `NaN`.
static func isFloating(_ value: String) -> Bool {
- if value == "INF" || value == "-INF" || value == "NaN" { return true }
+ if value == "INF" || value == "-INF" || value == "NaN" {
+ return true
+ }
var characters = Substring(value)
- if characters.first == "+" || characters.first == "-" { characters = characters.dropFirst() }
+ if characters.first == "+" || characters.first == "-" {
+ characters = characters.dropFirst()
+ }
let exponentSplit = characters.split(separator: "e", omittingEmptySubsequences: false)
let altSplit = characters.split(separator: "E", omittingEmptySubsequences: false)
let parts = exponentSplit.count == 2 ? exponentSplit : altSplit
@@ -31,7 +35,9 @@ extension PureXML.Schema {
private static func isSignedInteger(_ value: Substring) -> Bool {
var digits = value
- if digits.first == "+" || digits.first == "-" { digits = digits.dropFirst() }
+ if digits.first == "+" || digits.first == "-" {
+ digits = digits.dropFirst()
+ }
return !digits.isEmpty && digits.allSatisfy(\.isNumber)
}
@@ -39,7 +45,9 @@ extension PureXML.Schema {
/// least one component and no empty `T`.
static func isDuration(_ value: String) -> Bool {
var rest = Substring(value)
- if rest.first == "-" { rest = rest.dropFirst() }
+ if rest.first == "-" {
+ rest = rest.dropFirst()
+ }
guard rest.first == "P" else { return false }
rest = rest.dropFirst()
let timeSplit = rest.split(separator: "T", omittingEmptySubsequences: false)
diff --git a/Sources/Schema/OccurrenceRange.swift b/Sources/Schema/OccurrenceRange.swift
index 1b7d26b..e8c2729 100644
--- a/Sources/Schema/OccurrenceRange.swift
+++ b/Sources/Schema/OccurrenceRange.swift
@@ -14,7 +14,9 @@ public extension PureXML.Schema {
public init?(lexical value: String) {
var raw = Substring(value.trimmingXMLWhitespace())
- if raw.first == "+" { raw = raw.dropFirst() }
+ if raw.first == "+" {
+ raw = raw.dropFirst()
+ }
guard !raw.isEmpty, raw.allSatisfy({ $0.isASCII && $0.isNumber }) else {
return nil
}
@@ -32,14 +34,18 @@ public extension PureXML.Schema {
func clamped(to limit: Int) -> Int {
precondition(limit >= 0, "occurrence clamp limit must be non-negative")
- if isGreaterThan(limit) { return limit }
+ if isGreaterThan(limit) {
+ return limit
+ }
return Int(digits) ?? limit
}
func isGreaterThan(_ value: Int) -> Bool {
precondition(value >= 0, "occurrence comparison value must be non-negative")
let other = String(value)
- if digits.count != other.count { return digits.count > other.count }
+ if digits.count != other.count {
+ return digits.count > other.count
+ }
return digits > other
}
@@ -69,7 +75,9 @@ public extension PureXML.Schema {
}
public var isZero: Bool {
- if case let .finite(value) = self { return value.isZero }
+ if case let .finite(value) = self {
+ return value.isZero
+ }
return false
}
diff --git a/Sources/Schema/ParticleNormalization.swift b/Sources/Schema/ParticleNormalization.swift
index c33bf9f..4841709 100644
--- a/Sources/Schema/ParticleNormalization.swift
+++ b/Sources/Schema/ParticleNormalization.swift
@@ -9,7 +9,9 @@ extension PureXML.Schema.Particle {
var members: [Self] = []
for child in group.particles {
let normalized = child.normalized()
- if normalized.maxOccurs == 0 { continue }
+ if normalized.maxOccurs == 0 {
+ continue
+ }
let splice = normalized.minOccurs == 1 && normalized.maxOccurs == 1
if case let .group(inner) = normalized.term, inner.compositor == group.compositor, splice {
members.append(contentsOf: inner.particles)
diff --git a/Sources/Schema/ParticleRestriction.swift b/Sources/Schema/ParticleRestriction.swift
index bb20d96..72622bf 100644
--- a/Sources/Schema/ParticleRestriction.swift
+++ b/Sources/Schema/ParticleRestriction.swift
@@ -187,7 +187,9 @@ extension PureXML.Schema {
/// has `maxOccurs=0`, or is a group all of whose members are content-free
/// (an empty group included). Its only legal content is the empty sequence.
static func contentFree(_ particle: Particle) -> Bool {
- if particle.maxOccurs == 0 { return true }
+ if particle.maxOccurs == 0 {
+ return true
+ }
switch particle.term {
case .element, .wildcard:
return false
@@ -216,13 +218,19 @@ extension PureXML.Schema {
matched = true
break
}
- if skippedMustBeEmptiable, !emptiable(candidate) { return false }
+ if skippedMustBeEmptiable, !emptiable(candidate) {
+ return false
+ }
+ }
+ if !matched {
+ return false
}
- if !matched { return false }
}
if skippedMustBeEmptiable {
while baseIndex < base.count {
- if !emptiable(base[baseIndex]) { return false }
+ if !emptiable(base[baseIndex]) {
+ return false
+ }
baseIndex += 1
}
}
@@ -253,7 +261,9 @@ extension PureXML.Schema {
/// Whether a particle can match no children at all.
static func emptiable(_ particle: Particle) -> Bool {
- if particle.minOccurs == 0 { return true }
+ if particle.minOccurs == 0 {
+ return true
+ }
switch particle.term {
case .element, .wildcard:
return false
diff --git a/Sources/Schema/ParticleRestrictionElement.swift b/Sources/Schema/ParticleRestrictionElement.swift
index 8a96b39..0579b39 100644
--- a/Sources/Schema/ParticleRestrictionElement.swift
+++ b/Sources/Schema/ParticleRestrictionElement.swift
@@ -50,7 +50,9 @@ extension PureXML.Schema.ParticleRestriction {
// declared union whose members merely subset the base union's is
// NOT a derivation (particlesIk011); structural member subsetting
// is not the spec's derivation relation.
- if typeDerivesByRestriction(restrictedTypeName, baseTypeName, derivation, types) { return true }
+ if typeDerivesByRestriction(restrictedTypeName, baseTypeName, derivation, types) {
+ return true
+ }
return baseMembers.contains { PureXML.Schema.XSDParser.isSimpleTypeRestrictionOK(derived: derivedSimple, base: $0) }
}
case (.complex, .simple):
@@ -62,7 +64,9 @@ extension PureXML.Schema.ParticleRestriction {
}
}
- if baseIsUnion(baseTypeName, types) { return true }
+ if baseIsUnion(baseTypeName, types) {
+ return true
+ }
// NameAndTypeOK.2.2: a restricting element's type must be validly derived from
// the base element's type EXCLUDING extension (and list/union), i.e. by
// restriction only. A type that reaches the base through an extension step is
@@ -79,7 +83,9 @@ extension PureXML.Schema.ParticleRestriction {
while let resolved = current, steps <= types.count {
switch resolved {
case let .simple(simple):
- if case .union = simple.variety { return true }
+ if case .union = simple.variety {
+ return true
+ }
return false
case .complex:
return false
@@ -112,11 +118,15 @@ extension PureXML.Schema.ParticleRestriction {
_ derivation: [String: PureXML.Schema.TypeDerivation],
_ types: [String: PureXML.Schema.ElementType],
) -> Bool {
- if isUrTypeName(base, types) { return true }
+ if isUrTypeName(base, types) {
+ return true
+ }
var current = derived
var visited: Set = []
while visited.insert(current).inserted {
- if current == base { return true }
+ if current == base {
+ return true
+ }
guard let step = derivation[current] else { break }
current = step.base
}
@@ -139,11 +149,15 @@ extension PureXML.Schema.ParticleRestriction {
_ derivation: [String: PureXML.Schema.TypeDerivation],
_ types: [String: PureXML.Schema.ElementType],
) -> Bool {
- if isUrTypeName(base, types) { return true }
+ if isUrTypeName(base, types) {
+ return true
+ }
var current = derived
var visited: Set = []
while visited.insert(current).inserted {
- if current == base { return true }
+ if current == base {
+ return true
+ }
guard let step = derivation[current], step.method == .restriction else { break }
current = step.base
}
diff --git a/Sources/Schema/RelaxNGCompactHelpers.swift b/Sources/Schema/RelaxNGCompactHelpers.swift
index 878d820..87b46d6 100644
--- a/Sources/Schema/RelaxNGCompactHelpers.swift
+++ b/Sources/Schema/RelaxNGCompactHelpers.swift
@@ -58,7 +58,9 @@ extension RNCParser {
private func qualifiedName(_ word: String, attribute: Bool) -> NameClass {
let parts = word.split(separator: ":", maxSplits: 1).map(String.init)
if parts.count == 2 {
- if parts[1] == "*" { return .nsName(namespaces[parts[0]] ?? "") }
+ if parts[1] == "*" {
+ return .nsName(namespaces[parts[0]] ?? "")
+ }
return .name(namespace: namespaces[parts[0]] ?? "", localName: parts[1])
}
return .name(namespace: attribute ? "" : defaultNamespace, localName: word)
@@ -78,7 +80,8 @@ extension RNCParser {
}
func wordValue() -> String {
- if case let .word(word) = peek() { advance()
+ if case let .word(word) = peek() {
+ advance()
return word
}
return ""
@@ -93,11 +96,14 @@ extension RNCParser {
}
func expectSymbol(_ symbol: String) {
- if peek() == .symbol(symbol) { advance() }
+ if peek() == .symbol(symbol) {
+ advance()
+ }
}
func expectString() -> String {
- if case let .string(value) = peek() { advance()
+ if case let .string(value) = peek() {
+ advance()
return concatenated(value)
}
return ""
diff --git a/Sources/Schema/RelaxNGCompactParser.swift b/Sources/Schema/RelaxNGCompactParser.swift
index c2972ff..b9fe836 100644
--- a/Sources/Schema/RelaxNGCompactParser.swift
+++ b/Sources/Schema/RelaxNGCompactParser.swift
@@ -50,24 +50,33 @@ extension RNCParser {
private func parseDefault() {
advance() // default
- if case .word("namespace") = peek() { parseNamespace(isDefault: true) }
+ if case .word("namespace") = peek() {
+ parseNamespace(isDefault: true)
+ }
}
private func parseNamespace(isDefault: Bool) {
advance() // namespace
var prefix = ""
- if case let .word(name) = peek(), name != "=" { prefix = name
+ if case let .word(name) = peek(), name != "=" {
+ prefix = name
advance()
}
expectSymbol("=")
let uri = expectString()
- if isDefault { defaultNamespace = uri }
- if !prefix.isEmpty { namespaces[prefix] = uri }
+ if isDefault {
+ defaultNamespace = uri
+ }
+ if !prefix.isEmpty {
+ namespaces[prefix] = uri
+ }
}
private func parseDatatypes() {
advance() // datatypes
- if case .word = peek() { advance() }
+ if case .word = peek() {
+ advance()
+ }
expectSymbol("=")
_ = expectString()
}
@@ -76,9 +85,13 @@ extension RNCParser {
private func looksLikeGrammar() -> Bool {
guard case let .word(word) = peek() else { return false }
- if word == "include" || word == "start" { return true }
+ if word == "include" || word == "start" {
+ return true
+ }
// A define is a word followed by one of =, |=, &=.
- if case let .symbol(symbol) = peek(1), symbol == "=" || symbol == "|=" || symbol == "&=" { return true }
+ if case let .symbol(symbol) = peek(1), symbol == "=" || symbol == "|=" || symbol == "&=" {
+ return true
+ }
return false
}
@@ -118,7 +131,11 @@ extension RNCParser {
var depth = 1
while position < tokens.count, depth > 0 {
let token = advance()
- if token == .symbol("[") { depth += 1 } else if token == .symbol("]") { depth -= 1 }
+ if token == .symbol("[") {
+ depth += 1
+ } else if token == .symbol("]") {
+ depth -= 1
+ }
}
}
}
@@ -148,7 +165,9 @@ extension RNCParser {
for (name, pattern) in sub.defines {
defines[name] = pattern
}
- if startPattern == nil { startPattern = sub.startPattern }
+ if startPattern == nil {
+ startPattern = sub.startPattern
+ }
visited = sub.visited
}
// An override block re-defines names after the include, overriding them.
@@ -212,7 +231,9 @@ extension RNCParser {
while peek() == .symbol(">"), peek(1) == .symbol(">") {
advance()
advance()
- if case .word = peek() { advance() }
+ if case .word = peek() {
+ advance()
+ }
skipAnnotation()
}
}
@@ -281,8 +302,11 @@ extension RNCParser {
guard word.contains(":") else { return .ref(word) }
let type = PureXML.Schema.BuiltinType(rawValue: strip(word)) ?? .string
var facets = PureXML.Schema.Facets()
- if peek() == .symbol("{") { parseParams(into: &facets) }
- if case let .string(value) = peek() { advance()
+ if peek() == .symbol("{") {
+ parseParams(into: &facets)
+ }
+ if case let .string(value) = peek() {
+ advance()
return .value(PureXML.Schema.SimpleType(base: type), concatenated(value))
}
return .data(PureXML.Schema.SimpleType(base: type, facets: facets))
@@ -297,12 +321,16 @@ extension RNCParser {
continue
}
advance()
- if peek() == .symbol("=") { advance() }
+ if peek() == .symbol("=") {
+ advance()
+ }
guard case let .string(value) = peek() else { continue }
advance()
PureXML.Schema.RelaxNGFacets.apply(name, value, into: &facets)
}
- if peek() == .symbol("}") { advance() }
+ if peek() == .symbol("}") {
+ advance()
+ }
}
private func parseElement() -> Pattern {
diff --git a/Sources/Schema/RelaxNGDerivative.swift b/Sources/Schema/RelaxNGDerivative.swift
index ad06f03..30d39b2 100644
--- a/Sources/Schema/RelaxNGDerivative.swift
+++ b/Sources/Schema/RelaxNGDerivative.swift
@@ -47,30 +47,54 @@ extension PureXML.Schema {
// MARK: Simplifying constructors
func choice(_ lhs: PureXML.Schema.Pattern, _ rhs: PureXML.Schema.Pattern) -> PureXML.Schema.Pattern {
- if case .notAllowed = lhs { return rhs }
- if case .notAllowed = rhs { return lhs }
+ if case .notAllowed = lhs {
+ return rhs
+ }
+ if case .notAllowed = rhs {
+ return lhs
+ }
return .choice(lhs, rhs)
}
private func group(_ lhs: PureXML.Schema.Pattern, _ rhs: PureXML.Schema.Pattern) -> PureXML.Schema.Pattern {
- if case .notAllowed = lhs { return .notAllowed }
- if case .notAllowed = rhs { return .notAllowed }
- if case .empty = lhs { return rhs }
- if case .empty = rhs { return lhs }
+ if case .notAllowed = lhs {
+ return .notAllowed
+ }
+ if case .notAllowed = rhs {
+ return .notAllowed
+ }
+ if case .empty = lhs {
+ return rhs
+ }
+ if case .empty = rhs {
+ return lhs
+ }
return .group(lhs, rhs)
}
private func interleave(_ lhs: PureXML.Schema.Pattern, _ rhs: PureXML.Schema.Pattern) -> PureXML.Schema.Pattern {
- if case .notAllowed = lhs { return .notAllowed }
- if case .notAllowed = rhs { return .notAllowed }
- if case .empty = lhs { return rhs }
- if case .empty = rhs { return lhs }
+ if case .notAllowed = lhs {
+ return .notAllowed
+ }
+ if case .notAllowed = rhs {
+ return .notAllowed
+ }
+ if case .empty = lhs {
+ return rhs
+ }
+ if case .empty = rhs {
+ return lhs
+ }
return .interleave(lhs, rhs)
}
private func after(_ lhs: PureXML.Schema.Pattern, _ rhs: PureXML.Schema.Pattern) -> PureXML.Schema.Pattern {
- if case .notAllowed = lhs { return .notAllowed }
- if case .notAllowed = rhs { return .notAllowed }
+ if case .notAllowed = lhs {
+ return .notAllowed
+ }
+ if case .notAllowed = rhs {
+ return .notAllowed
+ }
return .after(lhs, rhs)
}
@@ -155,7 +179,9 @@ extension PureXML.Schema {
}
private func valueMatches(_ content: PureXML.Schema.Pattern, _ value: String, env: [String: String]) -> Bool {
- if nullable(textDeriv(content, value, env: env)) { return true }
+ if nullable(textDeriv(content, value, env: env)) {
+ return true
+ }
// A whitespace-only attribute value also matches a pattern that
// matches the empty sequence, the same leniency element content
// gets for ignorable whitespace.
@@ -163,7 +189,9 @@ extension PureXML.Schema {
}
private func oneOrMoreP(_ inner: PureXML.Schema.Pattern) -> PureXML.Schema.Pattern {
- if case .notAllowed = inner { return .notAllowed }
+ if case .notAllowed = inner {
+ return .notAllowed
+ }
return .oneOrMore(inner)
}
}
@@ -244,7 +272,9 @@ extension PureXML.Schema.RelaxNGEngine {
let coalesced = Self.coalesceText(children)
// Clark's algorithm treats an empty element as optional empty text, so
// a data/value pattern whose lexical space admits "" matches .
- if coalesced.isEmpty { return choice(pattern, textDeriv(pattern, "", env: env)) }
+ if coalesced.isEmpty {
+ return choice(pattern, textDeriv(pattern, "", env: env))
+ }
if coalesced.count == 1, case let .text(string) = coalesced[0] {
let derived = textDeriv(pattern, string, env: env)
return Self.isWhitespace(string) ? choice(pattern, derived) : derived
@@ -276,13 +306,16 @@ extension PureXML.Schema.RelaxNGEngine {
case .comment, .processingInstruction:
continue
default:
- if !run.isEmpty { result.append(.text(run))
+ if !run.isEmpty {
+ result.append(.text(run))
run = ""
}
result.append(child)
}
}
- if !run.isEmpty { result.append(.text(run)) }
+ if !run.isEmpty {
+ result.append(.text(run))
+ }
return result
}
@@ -291,7 +324,9 @@ extension PureXML.Schema.RelaxNGEngine {
}
static func isWhitespaceText(_ node: PureXML.Model.Node) -> Bool {
- if case let .text(value) = node { return isWhitespace(value) }
+ if case let .text(value) = node {
+ return isWhitespace(value)
+ }
return false
}
@@ -341,7 +376,9 @@ extension PureXML.Schema.RelaxNGEngine {
switch event {
case let .startElement(name, attributes):
flushText(&state, closing: false)
- if !state.frames.isEmpty { state.frames[state.frames.count - 1].hadElementChild = true }
+ if !state.frames.isEmpty {
+ state.frames[state.frames.count - 1].hadElementChild = true
+ }
var env = state.frames.last?.env ?? Self.rootEnvironment
for attribute in attributes where Self.isNamespaceDeclaration(attribute) {
env[attribute.name.prefix == "xmlns" ? attribute.name.localName : ""] = attribute.value
@@ -353,11 +390,15 @@ extension PureXML.Schema.RelaxNGEngine {
state.current = startTagCloseDeriv(attributed)
state.frames.append(RelaxNGStreamFrame(env: env))
case let .characters(text), let .cdata(text):
- if !state.frames.isEmpty { state.frames[state.frames.count - 1].pendingText += text }
+ if !state.frames.isEmpty {
+ state.frames[state.frames.count - 1].pendingText += text
+ }
case .endElement:
flushText(&state, closing: true)
state.current = endTagDeriv(state.current)
- if !state.frames.isEmpty { state.frames.removeLast() }
+ if !state.frames.isEmpty {
+ state.frames.removeLast()
+ }
case .comment, .processingInstruction:
break
}
diff --git a/Sources/Schema/RelaxNGDiagnostics.swift b/Sources/Schema/RelaxNGDiagnostics.swift
index fbabd61..e365dd0 100644
--- a/Sources/Schema/RelaxNGDiagnostics.swift
+++ b/Sources/Schema/RelaxNGDiagnostics.swift
@@ -18,12 +18,16 @@ extension PureXML.Schema.RelaxNGEngine {
} else {
validateElement(opened, root, path, &errors)
}
- if errors.isEmpty { errors.append(.init(reason: "does not satisfy the RELAX NG schema", at: path)) }
+ if errors.isEmpty {
+ errors.append(.init(reason: "does not satisfy the RELAX NG schema", at: path))
+ }
return errors
}
private func isNotAllowed(_ pattern: Pattern) -> Bool {
- if case .notAllowed = pattern { return true }
+ if case .notAllowed = pattern {
+ return true
+ }
return false
}
@@ -46,7 +50,9 @@ extension PureXML.Schema.RelaxNGEngine {
if isNotAllowed(closed) {
// A genuine missing/extra attribute, unless an attribute error above
// already explains why the requirement is unsatisfied (no cascade).
- if errors.count == before { errors.append(.init(reason: "element <\(name)> is missing a required attribute", at: path)) }
+ if errors.count == before {
+ errors.append(.init(reason: "element <\(name)> is missing a required attribute", at: path))
+ }
return
}
// An attribute problem leaves the content model unreliable; stop here so a
diff --git a/Sources/Schema/RelaxNGGrammarCheck.swift b/Sources/Schema/RelaxNGGrammarCheck.swift
index ea65eff..bff9691 100644
--- a/Sources/Schema/RelaxNGGrammarCheck.swift
+++ b/Sources/Schema/RelaxNGGrammarCheck.swift
@@ -23,8 +23,12 @@ enum RelaxNGGrammarCheck {
static func check(_ node: RNGTree, in context: Context) -> String? {
guard let name = RNGNode.localName(node) else { return nil }
- if let problem = attributeProblem(node, name: name) { return problem }
- if let problem = textProblem(node, name: name) { return problem }
+ if let problem = attributeProblem(node, name: name) {
+ return problem
+ }
+ if let problem = textProblem(node, name: name) {
+ return problem
+ }
switch context {
case .pattern: return checkPattern(node, name: name)
case .nameClass: return checkNameClass(node, name: name)
@@ -102,7 +106,9 @@ enum RelaxNGGrammarCheck {
}
} else {
guard let nameClass = content.first else { return "<\(name)> needs a name class" }
- if let problem = check(nameClass, in: .nameClass) { return problem }
+ if let problem = check(nameClass, in: .nameClass) {
+ return problem
+ }
content = content.dropFirst()
}
if name == "element", content.isEmpty {
@@ -160,14 +166,20 @@ enum RelaxNGGrammarCheck {
for child in children {
switch RNGNode.localName(child) {
case "param":
- if sawExcept { return " may not follow in " }
+ if sawExcept {
+ return " may not follow in "
+ }
guard hasNCName(child, "name") else { return " needs a name attribute" }
case "except":
- if sawExcept { return " takes at most one " }
+ if sawExcept {
+ return " takes at most one "
+ }
sawExcept = true
let members = RNGNode.elementChildren(child)
guard !members.isEmpty else { return " in needs content" }
- if let problem = firstViolation(members, in: .dataExcept) { return problem }
+ if let problem = firstViolation(members, in: .dataExcept) {
+ return problem
+ }
default:
return "<\(RNGNode.localName(child) ?? "?")> is not allowed in "
}
diff --git a/Sources/Schema/RelaxNGGrammarCheckSections.swift b/Sources/Schema/RelaxNGGrammarCheckSections.swift
index 53ebf9a..a0b3796 100644
--- a/Sources/Schema/RelaxNGGrammarCheckSections.swift
+++ b/Sources/Schema/RelaxNGGrammarCheckSections.swift
@@ -34,7 +34,9 @@ extension RelaxNGGrammarCheck {
return "<\(name)> admits only an child"
}
}
- if children.count > 1 { return "<\(name)> takes at most one " }
+ if children.count > 1 {
+ return "<\(name)> takes at most one "
+ }
guard let except = children.first else { return nil }
let members = RNGNode.elementChildren(except)
guard !members.isEmpty else { return " needs content" }
@@ -61,7 +63,9 @@ extension RelaxNGGrammarCheck {
/// `start` takes exactly one pattern; `define` needs a name and at least
/// one pattern; both may carry a combine method.
private static func checkCombiningComponent(_ node: RNGTree, name: String, children: [RNGTree]) -> String? {
- if let problem = combineProblem(node) { return problem }
+ if let problem = combineProblem(node) {
+ return problem
+ }
if name == "start" {
guard children.count == 1 else { return " takes exactly one pattern" }
return check(children[0], in: .pattern)
@@ -82,7 +86,9 @@ extension RelaxNGGrammarCheck {
static func firstViolation(_ nodes: [RNGTree], in context: Context) -> String? {
for node in nodes {
- if let problem = check(node, in: context) { return problem }
+ if let problem = check(node, in: context) {
+ return problem
+ }
}
return nil
}
@@ -120,7 +126,9 @@ extension RelaxNGGrammarCheck {
/// of ALPHA *(ALPHA / DIGIT / "+" / "-" / "."), or the empty string for
/// the built-in library.
static func isDatatypeLibraryURI(_ value: String) -> Bool {
- if value.isEmpty { return true }
+ if value.isEmpty {
+ return true
+ }
guard !value.contains("#") else { return false }
// A percent must introduce a two-digit hex escape (RFC 2396).
var cursor = value.startIndex
diff --git a/Sources/Schema/RelaxNGNode.swift b/Sources/Schema/RelaxNGNode.swift
index f098117..8e2686b 100644
--- a/Sources/Schema/RelaxNGNode.swift
+++ b/Sources/Schema/RelaxNGNode.swift
@@ -27,7 +27,9 @@ enum RNGNode {
// only when the whole document is unqualified (leniency for
// namespace-less schemas); under a qualified parent it is foreign.
let uri = child.name?.namespaceURI
- if uri == relaxNGNamespace { return true }
+ if uri == relaxNGNamespace {
+ return true
+ }
return uri == nil && node.name?.namespaceURI == nil
}
}
@@ -84,7 +86,9 @@ enum RNGNode {
guard let colon = raw.firstIndex(of: ":") else { return nil }
let prefix = String(raw[.. String {
var current: Tree? = node
while let candidate = current {
- if let library = RNGNode.attribute(candidate, "datatypeLibrary") { return library }
+ if let library = RNGNode.attribute(candidate, "datatypeLibrary") {
+ return library
+ }
current = candidate.parent
}
return ""
diff --git a/Sources/Schema/RelaxNGRestrictionChecker.swift b/Sources/Schema/RelaxNGRestrictionChecker.swift
index f0be616..9d76a78 100644
--- a/Sources/Schema/RelaxNGRestrictionChecker.swift
+++ b/Sources/Schema/RelaxNGRestrictionChecker.swift
@@ -23,8 +23,12 @@ final class RelaxNGRestrictionChecker {
case let .choice(lhs, rhs):
let left = normalized(lhs)
let right = normalized(rhs)
- if case .notAllowed = left { return right }
- if case .notAllowed = right { return left }
+ if case .notAllowed = left {
+ return right
+ }
+ if case .notAllowed = right {
+ return left
+ }
return .choice(left, right)
case let .group(lhs, rhs):
return combineNormalized(normalized(lhs), normalized(rhs), make: { .group($0, $1) })
@@ -41,15 +45,21 @@ final class RelaxNGRestrictionChecker {
switch pattern {
case let .oneOrMore(inner):
let normalizedInner = normalized(inner)
- if case .notAllowed = normalizedInner { return .notAllowed }
+ if case .notAllowed = normalizedInner {
+ return .notAllowed
+ }
return .oneOrMore(normalizedInner)
case let .list(inner):
let normalizedInner = normalized(inner)
- if case .notAllowed = normalizedInner { return .notAllowed }
+ if case .notAllowed = normalizedInner {
+ return .notAllowed
+ }
return .list(normalizedInner)
case let .attribute(nameClass, content):
let normalizedContent = normalized(content)
- if case .notAllowed = normalizedContent { return .notAllowed }
+ if case .notAllowed = normalizedContent {
+ return .notAllowed
+ }
return .attribute(nameClass, normalizedContent)
case let .element(nameClass, content):
return .element(nameClass, normalized(content))
@@ -59,10 +69,18 @@ final class RelaxNGRestrictionChecker {
}
private static func combineNormalized(_ lhs: Pattern, _ rhs: Pattern, make: (Pattern, Pattern) -> Pattern) -> Pattern {
- if case .notAllowed = lhs { return .notAllowed }
- if case .notAllowed = rhs { return .notAllowed }
- if case .empty = lhs { return rhs }
- if case .empty = rhs { return lhs }
+ if case .notAllowed = lhs {
+ return .notAllowed
+ }
+ if case .notAllowed = rhs {
+ return .notAllowed
+ }
+ if case .empty = lhs {
+ return rhs
+ }
+ if case .empty = rhs {
+ return lhs
+ }
return make(lhs, rhs)
}
@@ -117,7 +135,9 @@ final class RelaxNGRestrictionChecker {
private func reaches(_ pattern: Pattern, target: String, visiting: Set) -> Bool {
switch pattern {
case let .ref(name):
- if name == target { return true }
+ if name == target {
+ return true
+ }
guard !visiting.contains(name) else { return false }
return reaches(resolve(name), target: target, visiting: visiting.union([name]))
case let .choice(lhs, rhs), let .group(lhs, rhs), let .interleave(lhs, rhs):
diff --git a/Sources/Schema/RelaxNGRestrictionOverlap.swift b/Sources/Schema/RelaxNGRestrictionOverlap.swift
index b4ea39a..f7392c6 100644
--- a/Sources/Schema/RelaxNGRestrictionOverlap.swift
+++ b/Sources/Schema/RelaxNGRestrictionOverlap.swift
@@ -26,9 +26,13 @@ extension RelaxNGRestrictionChecker {
/// the operands' classes to be pairwise disjoint (7.3).
private func combinedAttributeSets(_ lhs: Pattern, _ rhs: Pattern, crossCheck: Bool, visiting: Set) -> (classes: [NameClass], problem: String?) {
let left = attributeSets(lhs, visiting: visiting)
- if left.problem != nil { return left }
+ if left.problem != nil {
+ return left
+ }
let right = attributeSets(rhs, visiting: visiting)
- if right.problem != nil { return right }
+ if right.problem != nil {
+ return right
+ }
if crossCheck {
for first in left.classes {
for second in right.classes where nameClassesOverlap(first, second) {
diff --git a/Sources/Schema/RelaxNGRestrictionPaths.swift b/Sources/Schema/RelaxNGRestrictionPaths.swift
index 3daa762..9a8ca3a 100644
--- a/Sources/Schema/RelaxNGRestrictionPaths.swift
+++ b/Sources/Schema/RelaxNGRestrictionPaths.swift
@@ -22,9 +22,13 @@ extension RelaxNGRestrictionChecker {
switch pattern {
case let .element(nameClass, content):
seenElements += 1
- if seenElements > 10000 { return nil }
+ if seenElements > 10000 {
+ return nil
+ }
_ = nameClass
- if let problem = contentProblem(content) { return problem }
+ if let problem = contentProblem(content) {
+ return problem
+ }
queue.append(content)
case let .attribute(_, content):
queue.append(content)
@@ -48,11 +52,15 @@ extension RelaxNGRestrictionChecker {
/// group/interleave, and list (7.1), a computable content type (7.2),
/// distinct attributes (7.3), and interleave constraints (7.4).
func contentProblem(_ content: Pattern) -> String? {
- if let problem = pathProblem(content, flags: RelaxNGPathFlags(), visiting: []) { return problem }
+ if let problem = pathProblem(content, flags: RelaxNGPathFlags(), visiting: []) {
+ return problem
+ }
if contentType(content, visiting: []) == nil {
return "element content does not have a computable content type (7.2)"
}
- if let problem = attributeSets(content, visiting: []).problem { return problem }
+ if let problem = attributeSets(content, visiting: []).problem {
+ return problem
+ }
return interleaveProblem(content, visiting: [])
}
@@ -77,14 +85,20 @@ extension RelaxNGRestrictionChecker {
private func compositePathProblem(_ pattern: Pattern, flags: RelaxNGPathFlags, visiting: Set) -> String? {
switch pattern {
case let .list(inner):
- if flags.inList { return "list is not allowed inside list (7.1.3)" }
+ if flags.inList {
+ return "list is not allowed inside list (7.1.3)"
+ }
var nested = flags
nested.inList = true
return pathProblem(inner, flags: nested, visiting: visiting)
case let .interleave(lhs, rhs), let .group(lhs, rhs):
- if flags.inList, isInterleave(pattern) { return "interleave is not allowed inside list (7.1.3)" }
+ if flags.inList, isInterleave(pattern) {
+ return "interleave is not allowed inside list (7.1.3)"
+ }
var nested = flags
- if flags.underOneOrMore { nested.inOneOrMoreGroupOrInterleave = true }
+ if flags.underOneOrMore {
+ nested.inOneOrMoreGroupOrInterleave = true
+ }
return pathProblem(lhs, flags: nested, visiting: visiting) ?? pathProblem(rhs, flags: nested, visiting: visiting)
case let .choice(lhs, rhs):
return pathProblem(lhs, flags: flags, visiting: visiting) ?? pathProblem(rhs, flags: flags, visiting: visiting)
@@ -100,18 +114,24 @@ extension RelaxNGRestrictionChecker {
}
private func isInterleave(_ pattern: Pattern) -> Bool {
- if case .interleave = pattern { return true }
+ if case .interleave = pattern {
+ return true
+ }
return false
}
/// The attribute prohibitions of 7.1.1-7.1.3 and the infinite-name-class
/// rule of 7.3, then the attribute's own content under the inner flag.
private func attributePathProblem(_ nameClass: NameClass, _ content: Pattern, flags: RelaxNGPathFlags, visiting: Set) -> String? {
- if flags.inAttribute { return "attribute is not allowed inside attribute (7.1.1)" }
+ if flags.inAttribute {
+ return "attribute is not allowed inside attribute (7.1.1)"
+ }
if flags.inOneOrMoreGroupOrInterleave {
return "attribute is not allowed under oneOrMore with group or interleave (7.1.2)"
}
- if flags.inList { return "attribute is not allowed inside list (7.1.3)" }
+ if flags.inList {
+ return "attribute is not allowed inside list (7.1.3)"
+ }
if !flags.underOneOrMore, isInfinite(nameClass) {
return "an attribute with an infinite name class must be inside oneOrMore (7.3)"
}
@@ -123,8 +143,12 @@ extension RelaxNGRestrictionChecker {
/// In the simplified grammar a ref stands for an element, so the
/// prohibitions on ref under attribute and list translate to the element.
private func elementPathProblem(_ flags: RelaxNGPathFlags) -> String? {
- if flags.inAttribute { return "element is not allowed inside attribute (7.1.1)" }
- if flags.inList { return "element is not allowed inside list (7.1.3)" }
+ if flags.inAttribute {
+ return "element is not allowed inside attribute (7.1.1)"
+ }
+ if flags.inList {
+ return "element is not allowed inside list (7.1.3)"
+ }
return nil // Otherwise a new context, walked separately.
}
diff --git a/Sources/Schema/RelaxNGRestrictions.swift b/Sources/Schema/RelaxNGRestrictions.swift
index 11b5fe3..f59930b 100644
--- a/Sources/Schema/RelaxNGRestrictions.swift
+++ b/Sources/Schema/RelaxNGRestrictions.swift
@@ -16,7 +16,9 @@ enum RelaxNGRestrictions {
}
let checker = RelaxNGRestrictionChecker(defines: defines.mapValues(RelaxNGRestrictionChecker.normalized))
let normalizedStart = RelaxNGRestrictionChecker.normalized(start)
- if let problem = checker.startProblem(normalizedStart) { return problem }
+ if let problem = checker.startProblem(normalizedStart) {
+ return problem
+ }
return checker.elementProblems(normalizedStart)
}
}
diff --git a/Sources/Schema/RelaxNGSchema.swift b/Sources/Schema/RelaxNGSchema.swift
index ed83567..573ae7a 100644
--- a/Sources/Schema/RelaxNGSchema.swift
+++ b/Sources/Schema/RelaxNGSchema.swift
@@ -46,7 +46,9 @@ public extension PureXML.Schema {
/// is invalid, falls back to the tree ``errors(in:)`` pass so diagnostics
/// stay located rather than a bare false.
public func errors(streaming xml: String, limits: PureXML.Parsing.Limits = .default) throws -> [PureXML.Validation.ValidationError] {
- if try validate(streaming: xml, limits: limits) { return [] }
+ if try validate(streaming: xml, limits: limits) {
+ return []
+ }
return try errors(in: xml)
}
diff --git a/Sources/Schema/SchemaAllGroupPlacement.swift b/Sources/Schema/SchemaAllGroupPlacement.swift
index 2b60c86..df11eb6 100644
--- a/Sources/Schema/SchemaAllGroupPlacement.swift
+++ b/Sources/Schema/SchemaAllGroupPlacement.swift
@@ -16,7 +16,9 @@ extension PureXML.Schema.XSDParser {
var redefined: Set = []
for container in containers where AllNode.localName(container) == "redefine" {
for group in AllNode.children(container, named: "group") {
- if let name = AllNode.attribute(group, "name") { redefined.insert(name) }
+ if let name = AllNode.attribute(group, "name") {
+ redefined.insert(name)
+ }
}
}
var allGroups: Set = []
diff --git a/Sources/Schema/SchemaAttributeRestriction.swift b/Sources/Schema/SchemaAttributeRestriction.swift
index 9b55cdd..4dbac9f 100644
--- a/Sources/Schema/SchemaAttributeRestriction.swift
+++ b/Sources/Schema/SchemaAttributeRestriction.swift
@@ -86,8 +86,12 @@ extension PureXML.Schema.XSDParser {
// attribute wildcard must admit it, else the restriction adds an
// attribute the base never permitted (ctO004: a no-namespace attribute
// over a `##other` wildcard).
- if isProhibited(derived.name, under: restrictionNode, context) { return nil }
- if baseWildcard?.admits(derived.name) == true { return nil }
+ if isProhibited(derived.name, under: restrictionNode, context) {
+ return nil
+ }
+ if baseWildcard?.admits(derived.name) == true {
+ return nil
+ }
return "attribute '\(derived.name.localName)' is not in the base type and is not admitted by its attribute wildcard, so a restriction may not add it"
}
if base.required, !derived.required {
diff --git a/Sources/Schema/SchemaAttributeUniqueness.swift b/Sources/Schema/SchemaAttributeUniqueness.swift
index 286b5ad..cf5a99f 100644
--- a/Sources/Schema/SchemaAttributeUniqueness.swift
+++ b/Sources/Schema/SchemaAttributeUniqueness.swift
@@ -199,7 +199,9 @@ extension PureXML.Schema.XSDParser {
var idCount = 0
for declaration in declarations where !declaration.isProhibited && seenIDs.insert(declaration.id).inserted {
counts[declaration.name, default: 0] += 1
- if declaration.isID { idCount += 1 }
+ if declaration.isID {
+ idCount += 1
+ }
}
var errors: [String] = []
for name in counts.keys.sorted(by: { ($0.localName, $0.namespaceURI ?? "") < ($1.localName, $1.namespaceURI ?? "") }) where (counts[name] ?? 0) > 1 {
diff --git a/Sources/Schema/SchemaContentDerivation.swift b/Sources/Schema/SchemaContentDerivation.swift
index b237908..ad7e7d8 100644
--- a/Sources/Schema/SchemaContentDerivation.swift
+++ b/Sources/Schema/SchemaContentDerivation.swift
@@ -242,15 +242,23 @@ extension PureXML.Schema.XSDParser {
_ types: [String: PureXML.Schema.ElementType],
) -> Bool {
if let inline = ContentDerivNode.firstChild(element, named: "complexType") {
- if ContentDerivNode.firstChild(inline, named: "simpleContent") != nil { return false }
- if ["true", "1"].contains(ContentDerivNode.attribute(inline, "mixed")) { return false }
- if ContentDerivNode.firstChild(inline, named: "complexContent") != nil { return false }
+ if ContentDerivNode.firstChild(inline, named: "simpleContent") != nil {
+ return false
+ }
+ if ["true", "1"].contains(ContentDerivNode.attribute(inline, "mixed")) {
+ return false
+ }
+ if ContentDerivNode.firstChild(inline, named: "complexContent") != nil {
+ return false
+ }
return ["all", "sequence", "choice", "group"].contains { ContentDerivNode.firstChild(inline, named: $0) != nil }
}
guard let typeName = ContentDerivNode.attribute(element, "type") else { return false }
let namespace = ContentDerivNode.referenceNamespace(typeName, bindings)
let local = ContentDerivNode.stripPrefix(typeName)
- if namespace == xsdNamespace { return false }
+ if namespace == xsdNamespace {
+ return false
+ }
guard namespace == context.targetNamespace, case let .complex(complex)? = types[local] else { return false }
switch complex.content {
case .elementOnly, .empty: return true
@@ -300,8 +308,12 @@ extension PureXML.Schema.XSDParser {
/// value matters only when the extension adds element content; otherwise the
/// base content is inherited unchanged and there is no mixedness conflict.
private static func declaredMixed(_ complexContent: XSDTree, addsElementContent: Bool) -> Bool? {
- if let own = ContentDerivNode.attribute(complexContent, "mixed") { return own == "true" || own == "1" }
- if let parent = complexContent.parent, let outer = ContentDerivNode.attribute(parent, "mixed") { return outer == "true" || outer == "1" }
+ if let own = ContentDerivNode.attribute(complexContent, "mixed") {
+ return own == "true" || own == "1"
+ }
+ if let parent = complexContent.parent, let outer = ContentDerivNode.attribute(parent, "mixed") {
+ return outer == "true" || outer == "1"
+ }
return addsElementContent ? false : nil
}
}
diff --git a/Sources/Schema/SchemaDerivationControl.swift b/Sources/Schema/SchemaDerivationControl.swift
index 9e646ff..dd31880 100644
--- a/Sources/Schema/SchemaDerivationControl.swift
+++ b/Sources/Schema/SchemaDerivationControl.swift
@@ -41,10 +41,16 @@ extension PureXML.Schema.XSDParser {
/// control).
private static func derivationListError(_ raw: String, allowed: Set) -> String? {
let tokens = raw.split(whereSeparator: \.isWhitespace).map(String.init)
- if tokens == ["#all"] { return nil }
+ if tokens == ["#all"] {
+ return nil
+ }
for token in tokens {
- if token == "#all" { return "'#all' may not be combined with other tokens" }
- if !allowed.contains(token) { return "'\(token)' is not one of \(allowed.sorted().joined(separator: ", "))" }
+ if token == "#all" {
+ return "'#all' may not be combined with other tokens"
+ }
+ if !allowed.contains(token) {
+ return "'\(token)' is not one of \(allowed.sorted().joined(separator: ", "))"
+ }
}
return nil
}
diff --git a/Sources/Schema/SchemaDocument.swift b/Sources/Schema/SchemaDocument.swift
index f75fa89..0b002e4 100644
--- a/Sources/Schema/SchemaDocument.swift
+++ b/Sources/Schema/SchemaDocument.swift
@@ -34,7 +34,9 @@ public extension PureXML.Schema {
/// Every compile-time finding when the schema is inconsistent.
public var inconsistentFindings: [PureXML.Validation.ValidationError]? {
- if case let .inconsistent(findings) = self { return findings }
+ if case let .inconsistent(findings) = self {
+ return findings
+ }
return nil
}
}
@@ -251,26 +253,39 @@ public extension PureXML.Schema {
driver.consume(event)
}
var errors = driver.finish()
- errors += validator.idErrors()
+ // Document-scoped ID/IDREF and identity-constraint checking run once the
+ // whole document has streamed through; both go through the same named
+ // ``PureXML/Validation`` rules the tree path uses (``idConsistency``,
+ // `XSD.identityConstraintRules`), not a bare imperative call, so the
+ // streaming path stays inside the framework like every other validator.
+ errors += PureXML.Validation.Validator.blank
+ .validating(PureXML.Schema.ComplexValidator.idConsistency)
+ .errors(for: validator, in: ())
if !constraints.isEmpty, let identityNode {
errors += identityErrors(in: identityNode)
}
return errors
}
- /// Identity-constraint findings for an already-parsed instance tree.
+ /// Identity-constraint findings for an already-parsed instance tree, run
+ /// through the same decomposed ``PureXML/Validation/XSD`` identity rules
+ /// the tree path uses.
private func identityErrors(in node: PureXML.Model.Node) -> [PureXML.Validation.ValidationError] {
guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
return []
}
- return PureXML.Schema.IdentityValidator(
- constraints: constraints,
- fieldTypes: identityFieldTypes,
- fieldConstraints: identityFieldConstraints,
- nonSimpleFields: identityNonSimpleFields,
+ let context = PureXML.Validation.XSDContext(
types: types,
+ constraints: constraints,
+ rootDeclaration: nil,
+ identityFieldTypes: identityFieldTypes,
+ identityFieldConstraints: identityFieldConstraints,
+ identityNonSimpleFields: identityNonSimpleFields,
)
- .validate(PureXML.Model.TreeNode(.element(root)), at: [.element(root.name.description)])
+ return PureXML.Validation.Validator.defaults(
+ reference: PureXML.Validation.XSD.identityConstraintRules.map { PureXML.Validation.AnyValidation($0) },
+ )
+ .errors(for: .element(root), in: context)
}
/// Validates an already-parsed node tree, so a caller can validate the same
diff --git a/Sources/Schema/SchemaFacetApplicability.swift b/Sources/Schema/SchemaFacetApplicability.swift
index e5319af..54c1511 100644
--- a/Sources/Schema/SchemaFacetApplicability.swift
+++ b/Sources/Schema/SchemaFacetApplicability.swift
@@ -169,7 +169,9 @@ extension PureXML.Schema.XSDParser {
case let .simple(simple):
return simple
case let .complex(complex):
- if case let .simpleContent(simple) = complex.content { return simple }
+ if case let .simpleContent(simple) = complex.content {
+ return simple
+ }
return nil
case .typeReference:
return nil
@@ -178,8 +180,12 @@ extension PureXML.Schema.XSDParser {
private static func forEachValueConstrained(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
- if local == "element" || local == "attribute" { visit(node) }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
+ if local == "element" || local == "attribute" {
+ visit(node)
+ }
for child in PureXML.Schema.XSDNode.elementChildren(node) {
forEachValueConstrained(child, visit)
}
@@ -226,8 +232,12 @@ extension PureXML.Schema.XSDParser {
private static func forEachSimpleType(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
- if local == "simpleType" { visit(node) }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
+ if local == "simpleType" {
+ visit(node)
+ }
for child in PureXML.Schema.XSDNode.elementChildren(node) {
forEachSimpleType(child, visit)
}
@@ -277,7 +287,9 @@ extension PureXML.Schema.XSDParser {
private static func forEachRestrictionInSimpleType(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace, local == "simpleType" {
for child in PureXML.Schema.XSDNode.elementChildren(node) {
if child.name?.namespaceURI == xsdNamespace, PureXML.Schema.XSDNode.localName(child) == "restriction" {
@@ -330,7 +342,9 @@ extension PureXML.Schema.XSDParser {
) -> Bool {
let (prefix, local) = splitQName(base)
let uri = prefix.map { bindings[$0] } ?? bindings[""]
- if uri == xsdNamespace { return local == "anySimpleType" }
+ if uri == xsdNamespace {
+ return local == "anySimpleType"
+ }
guard uri == target, !visited.contains(local),
let complexType = globalComplexType(named: local, schema),
let simpleContent = PureXML.Schema.XSDNode.firstChild(complexType, named: "simpleContent"),
@@ -349,7 +363,9 @@ extension PureXML.Schema.XSDParser {
private static func forEachSimpleContentRestriction(_ node: XSDTree, _ visit: (XSDTree) -> Void) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if local == "simpleContent", let restriction = PureXML.Schema.XSDNode.firstChild(node, named: "restriction") {
visit(restriction)
}
diff --git a/Sources/Schema/SchemaIDAttribute.swift b/Sources/Schema/SchemaIDAttribute.swift
index c4322c6..e1a5bbe 100644
--- a/Sources/Schema/SchemaIDAttribute.swift
+++ b/Sources/Schema/SchemaIDAttribute.swift
@@ -23,7 +23,9 @@ extension PureXML.Schema.XSDParser {
// `appinfo` and `documentation` hold arbitrary foreign content whose own
// `id` attributes are not xs:ID; do not descend into them.
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if let id = unprefixedID(node) {
if !PureXML.Schema.Lexical.isNCName(id) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
diff --git a/Sources/Schema/SchemaIDValueConstraint.swift b/Sources/Schema/SchemaIDValueConstraint.swift
index c2eb9e7..53fae18 100644
--- a/Sources/Schema/SchemaIDValueConstraint.swift
+++ b/Sources/Schema/SchemaIDValueConstraint.swift
@@ -21,7 +21,9 @@ extension PureXML.Schema.XSDParser {
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let local = IDVNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if let error = idValueConstraintError(node, context) {
findings.append(PureXML.Schema.SchemaLocatedFinding(reason: error, node: node))
}
diff --git a/Sources/Schema/SchemaIdentityXPath.swift b/Sources/Schema/SchemaIdentityXPath.swift
index ab751e8..c3c1f0a 100644
--- a/Sources/Schema/SchemaIdentityXPath.swift
+++ b/Sources/Schema/SchemaIdentityXPath.swift
@@ -42,7 +42,9 @@ extension PureXML.Schema.XSDParser {
for case let .name(name) in tokens {
guard let colon = name.firstIndex(of: ":") else { continue }
let prefix = String(name[name.startIndex ..< colon])
- if !prefix.isEmpty { prefixes.append(prefix) }
+ if !prefix.isEmpty {
+ prefixes.append(prefix)
+ }
}
return prefixes
}
diff --git a/Sources/Schema/SchemaNameUniqueness.swift b/Sources/Schema/SchemaNameUniqueness.swift
index 78a79fa..6ff5f9d 100644
--- a/Sources/Schema/SchemaNameUniqueness.swift
+++ b/Sources/Schema/SchemaNameUniqueness.swift
@@ -54,7 +54,9 @@ extension PureXML.Schema.XSDParser {
else {
return false
}
- if prevLocation == location { return true }
+ if prevLocation == location {
+ return true
+ }
// A circular import reloads the root schema under a schemaLocation while
// the compile root is appended last; treat that as one document, not a clash.
let rootIndex = containers.count - 1
@@ -134,7 +136,9 @@ extension PureXML.Schema.XSDParser {
/// (`import`/`include`/`redefine`), which the default compile does not load,
/// so a `refer` into them is never flagged.
private static func keyrefReferFindings(_ schema: XSDTree, _ containers: [XSDTree], _ context: PureXML.Schema.XSDContext) -> [PureXML.Schema.SchemaLocatedFinding] {
- if skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) { return [] }
+ if skipsCrossDocumentRules(schema, compositionLoaded: context.compositionLoaded) {
+ return []
+ }
var keyArities: [String: Int] = [:]
var keyrefs: [KeyrefInfo] = []
let sources = context.compositionLoaded ? containers : [schema]
@@ -166,7 +170,9 @@ extension PureXML.Schema.XSDParser {
keyArities: inout [String: Int],
keyrefs: inout [KeyrefInfo],
) {
- if let local = PureXML.Schema.XSDNode.localName(node), local == "appinfo" || local == "documentation" { return }
+ if let local = PureXML.Schema.XSDNode.localName(node), local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace, let local = PureXML.Schema.XSDNode.localName(node) {
if local == "key" || local == "unique", let name = PureXML.Schema.XSDNode.attribute(node, "name")?.trimmingXMLWhitespace() {
let arity = PureXML.Schema.XSDNode.elementChildren(node)
@@ -203,7 +209,9 @@ extension PureXML.Schema.XSDParser {
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
func walk(_ node: XSDTree, namespaceURI: String?) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if let name = identityConstraintName(node) {
let key = KeyrefNameKey(targetNamespace: namespaceURI, name: name)
if !seen.insert(key).inserted {
@@ -238,8 +246,12 @@ extension PureXML.Schema.XSDParser {
}
private static func isStructurallyEqual(_ lhs: XSDTree, _ rhs: XSDTree) -> Bool {
- if lhs.name != rhs.name { return false }
- if lhs.attributes.count != rhs.attributes.count { return false }
+ if lhs.name != rhs.name {
+ return false
+ }
+ if lhs.attributes.count != rhs.attributes.count {
+ return false
+ }
for attr in lhs.attributes {
guard let rhsVal = rhs.attributes.first(where: { $0.name == attr.name })?.value,
rhsVal == attr.value
@@ -247,7 +259,9 @@ extension PureXML.Schema.XSDParser {
}
let lhsChildren = PureXML.Schema.XSDNode.elementChildren(lhs)
let rhsChildren = PureXML.Schema.XSDNode.elementChildren(rhs)
- if lhsChildren.count != rhsChildren.count { return false }
+ if lhsChildren.count != rhsChildren.count {
+ return false
+ }
guard !zip(lhsChildren, rhsChildren).contains(where: { !isStructurallyEqual($0, $1) }) else { return false }
return true
}
diff --git a/Sources/Schema/SchemaNestedDefinition.swift b/Sources/Schema/SchemaNestedDefinition.swift
index 5ecabc7..1f64d6b 100644
--- a/Sources/Schema/SchemaNestedDefinition.swift
+++ b/Sources/Schema/SchemaNestedDefinition.swift
@@ -21,7 +21,9 @@ extension PureXML.Schema.XSDParser {
let kind = NestedNode.localName(node)
// Foreign content inside an annotation is not the schema's own and is not
// structurally checked.
- if kind == "appinfo" || kind == "documentation" { return }
+ if kind == "appinfo" || kind == "documentation" {
+ return
+ }
if isNestedNamedDefinition(node, kind, parentIsTopLevel: parentIsTopLevel) {
findings.append(PureXML.Schema.SchemaLocatedFinding(
reason: "a nested '\(kind ?? "")' definition may not have a 'name'; only a top-level definition is named",
diff --git a/Sources/Schema/SchemaNotationValidity.swift b/Sources/Schema/SchemaNotationValidity.swift
index 8e718a2..2aff745 100644
--- a/Sources/Schema/SchemaNotationValidity.swift
+++ b/Sources/Schema/SchemaNotationValidity.swift
@@ -65,7 +65,9 @@ extension PureXML.Schema.XSDParser {
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace, local == "restriction" {
validateNotationRestriction(node, context: context, declaredNotations: declaredNotations, into: &findings)
diff --git a/Sources/Schema/SchemaRedefineAttributeGroupRestriction.swift b/Sources/Schema/SchemaRedefineAttributeGroupRestriction.swift
index a6365c1..1be9c0e 100644
--- a/Sources/Schema/SchemaRedefineAttributeGroupRestriction.swift
+++ b/Sources/Schema/SchemaRedefineAttributeGroupRestriction.swift
@@ -54,7 +54,9 @@ extension PureXML.Schema.XSDParser {
switch RedefineNode.localName(member) {
case "attribute":
guard let attrName = RedefineNode.attribute(member, "name") else { return nil }
- if RedefineNode.attribute(member, "use") != "prohibited" { names.insert(attrName) }
+ if RedefineNode.attribute(member, "use") != "prohibited" {
+ names.insert(attrName)
+ }
case "anyAttribute", "attributeGroup":
return nil
default:
@@ -78,7 +80,9 @@ extension PureXML.Schema.XSDParser {
// value (a restriction may not relax the original's fixed constraint).
var redefined: [String: XSDTree] = [:]
for attribute in children where RedefineNode.localName(attribute) == "attribute" {
- if let attrName = RedefineNode.attribute(attribute, "name") { redefined[attrName] = attribute }
+ if let attrName = RedefineNode.attribute(attribute, "name") {
+ redefined[attrName] = attribute
+ }
}
let use: (String) -> String? = { redefined[$0].map { RedefineNode.attribute($0, "use") ?? "optional" } }
let hasReference = children.contains { RedefineNode.localName($0) == "attributeGroup" }
@@ -187,7 +191,9 @@ extension PureXML.Schema.XSDParser {
/// resolution, so the self-reference duplicate check (which compares by local name)
/// is sound only for no-namespace attributes; a qualified one is not compared.
private static func isNoNamespaceAttribute(_ attribute: XSDTree) -> Bool {
- if RedefineNode.attribute(attribute, "form") == "qualified" { return false }
+ if RedefineNode.attribute(attribute, "form") == "qualified" {
+ return false
+ }
return RedefineNode.attribute(RedefineNode.schemaOwner(attribute), "attributeFormDefault") != "qualified"
}
diff --git a/Sources/Schema/SchemaRedefineSelfReference.swift b/Sources/Schema/SchemaRedefineSelfReference.swift
index ed4490c..3c148c3 100644
--- a/Sources/Schema/SchemaRedefineSelfReference.swift
+++ b/Sources/Schema/SchemaRedefineSelfReference.swift
@@ -71,7 +71,9 @@ extension PureXML.Schema.XSDParser {
var base: [String: XSDTree] = [:]
for container in containers where RedefineNode.localName(container) != "redefine" {
for component in RedefineNode.children(container, named: kind) {
- if let name = RedefineNode.attribute(component, "name") { base[namespacedKey(name, owner: component)] = component }
+ if let name = RedefineNode.attribute(component, "name") {
+ base[namespacedKey(name, owner: component)] = component
+ }
}
}
return base
@@ -136,8 +138,12 @@ extension PureXML.Schema.XSDParser {
/// "pointless particle" whose normalization in the redefine-restriction check is
/// contested.
private static func hasPointlessParticle(_ particle: PureXML.Schema.Particle) -> Bool {
- if particle.maxOccurs == 0 { return true }
- if case let .group(group) = particle.term { return group.particles.contains(where: hasPointlessParticle) }
+ if particle.maxOccurs == 0 {
+ return true
+ }
+ if case let .group(group) = particle.term {
+ return group.particles.contains(where: hasPointlessParticle)
+ }
return false
}
@@ -161,7 +167,9 @@ extension PureXML.Schema.XSDParser {
case "element", "complexType", "simpleType", "attribute":
continue
case kind:
- if RedefineNode.attribute(child, "ref") != nil { nodes.append(child) }
+ if RedefineNode.attribute(child, "ref") != nil {
+ nodes.append(child)
+ }
default:
nodes += boundedSelfReferenceNodes(child, kind)
}
diff --git a/Sources/Schema/SchemaReferenceForeignPools.swift b/Sources/Schema/SchemaReferenceForeignPools.swift
index 34eef66..785e9b2 100644
--- a/Sources/Schema/SchemaReferenceForeignPools.swift
+++ b/Sources/Schema/SchemaReferenceForeignPools.swift
@@ -22,7 +22,9 @@ extension PureXML.Schema.XSDParser {
let container = containers[index]
guard PureXML.Schema.XSDNode.localName(container) != "redefine" else { continue }
let namespaceURI = namespaceMap[index] ?? mainTargetNamespace
- if namespacesMatch(namespaceURI, mainTargetNamespace) { continue }
+ if namespacesMatch(namespaceURI, mainTargetNamespace) {
+ continue
+ }
var pools = result[namespaceURI, default: [:]]
insertNamedGlobals(from: container, into: &pools)
result[namespaceURI] = pools
diff --git a/Sources/Schema/SchemaReferenceResolution.swift b/Sources/Schema/SchemaReferenceResolution.swift
index fdc3d43..4f51f8b 100644
--- a/Sources/Schema/SchemaReferenceResolution.swift
+++ b/Sources/Schema/SchemaReferenceResolution.swift
@@ -60,8 +60,12 @@ extension PureXML.Schema.XSDParser {
// target namespace, it cannot name a type that lives there, so it is
// undeclared (XSTS addB009 / test66059). This mirrors the same branch in
// `isUndeclaredReferenceRef`.
- if context.chameleonNamespace, context.types.contains(localPart) { return false }
- if !(context.targetNamespace == nil || context.targetNamespace == "") { return true }
+ if context.chameleonNamespace, context.types.contains(localPart) {
+ return false
+ }
+ if !(context.targetNamespace == nil || context.targetNamespace == "") {
+ return true
+ }
return !context.types.contains(localPart)
}
return false
@@ -92,8 +96,12 @@ extension PureXML.Schema.XSDParser {
return true
}
if uri == nil || uri == "" {
- if context.chameleonNamespace, context.pools[poolName]?.contains(localPart) == true { return false }
- if !(context.targetNamespace == nil || context.targetNamespace == "") { return true }
+ if context.chameleonNamespace, context.pools[poolName]?.contains(localPart) == true {
+ return false
+ }
+ if !(context.targetNamespace == nil || context.targetNamespace == "") {
+ return true
+ }
return context.pools[poolName]?.contains(localPart) != true
}
return false
diff --git a/Sources/Schema/SchemaReferences.swift b/Sources/Schema/SchemaReferences.swift
index 60f1435..7eb9196 100644
--- a/Sources/Schema/SchemaReferences.swift
+++ b/Sources/Schema/SchemaReferences.swift
@@ -179,7 +179,9 @@ extension PureXML.Schema.XSDParser {
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
let bindings = mergedNamespaceBindings(on: node, inherited: inheritedBindings)
let nodeContext = ResolutionContext(
types: resolutionContext.check.types,
@@ -256,7 +258,9 @@ extension PureXML.Schema.XSDParser {
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace {
switch local {
@@ -329,7 +333,9 @@ extension PureXML.Schema.XSDParser {
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace {
for attribute in ["type", "base", "itemType"] {
@@ -362,7 +368,9 @@ extension PureXML.Schema.XSDParser {
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
let isSimpleContentRestriction = node.name?.namespaceURI == xsdNamespace && local == "restriction"
&& node.parent?.name?.namespaceURI == xsdNamespace
diff --git a/Sources/Schema/SchemaSimpleTypeFinal.swift b/Sources/Schema/SchemaSimpleTypeFinal.swift
index 9be6d8e..1dd3865 100644
--- a/Sources/Schema/SchemaSimpleTypeFinal.swift
+++ b/Sources/Schema/SchemaSimpleTypeFinal.swift
@@ -108,7 +108,9 @@ extension PureXML.Schema.XSDParser {
guard let raw, !raw.isEmpty else { return [] }
var tokens: Set = []
for token in raw.split(whereSeparator: \.isWhitespace) {
- if token == "#all" { return ["restriction", "list", "union"] }
+ if token == "#all" {
+ return ["restriction", "list", "union"]
+ }
tokens.insert(String(token))
}
return tokens
diff --git a/Sources/Schema/SchemaSimpleTypeValidity.swift b/Sources/Schema/SchemaSimpleTypeValidity.swift
index fc0b2c1..d099e8f 100644
--- a/Sources/Schema/SchemaSimpleTypeValidity.swift
+++ b/Sources/Schema/SchemaSimpleTypeValidity.swift
@@ -9,7 +9,9 @@ extension PureXML.Schema.XSDParser {
func check(_ node: XSDTree) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
if node.name?.namespaceURI == xsdNamespace, local == "list" {
let item: PureXML.Schema.SimpleType = if let itemType = PureXML.Schema.XSDNode.attribute(node, "itemType") {
@@ -104,7 +106,9 @@ extension PureXML.Schema.XSDParser {
}
private static func isListOrUnionContainingList(_ type: PureXML.Schema.SimpleType) -> Bool {
- if type.isAnySimpleType { return true }
+ if type.isAnySimpleType {
+ return true
+ }
switch type.variety {
case .list:
return true
diff --git a/Sources/Schema/SchemaSimpleTypeVariety.swift b/Sources/Schema/SchemaSimpleTypeVariety.swift
index eda6e44..25a8546 100644
--- a/Sources/Schema/SchemaSimpleTypeVariety.swift
+++ b/Sources/Schema/SchemaSimpleTypeVariety.swift
@@ -13,7 +13,9 @@ extension PureXML.Schema.XSDParser {
init(schema: XSDTree, bindings: [String: String], target: String?) {
var byName: [String: XSDTree] = [:]
for child in PureXML.Schema.XSDNode.elementChildren(schema) where PureXML.Schema.XSDNode.localName(child) == "simpleType" {
- if let name = PureXML.Schema.XSDNode.attribute(child, "name"), byName[name] == nil { byName[name] = child }
+ if let name = PureXML.Schema.XSDNode.attribute(child, "name"), byName[name] == nil {
+ byName[name] = child
+ }
}
self.byName = byName
self.bindings = bindings
@@ -33,8 +35,12 @@ extension PureXML.Schema.XSDParser {
}
private static func simpleTypeVariety(_ simpleType: XSDTree, _ lookup: VarietyLookup, _ visited: Set, _ cache: inout [String: String?]) -> String? {
- if PureXML.Schema.XSDNode.firstChild(simpleType, named: "list") != nil { return "list" }
- if PureXML.Schema.XSDNode.firstChild(simpleType, named: "union") != nil { return "union" }
+ if PureXML.Schema.XSDNode.firstChild(simpleType, named: "list") != nil {
+ return "list"
+ }
+ if PureXML.Schema.XSDNode.firstChild(simpleType, named: "union") != nil {
+ return "union"
+ }
if let restriction = PureXML.Schema.XSDNode.firstChild(simpleType, named: "restriction") {
return restrictionBaseVariety(restriction, lookup, visited, &cache)
}
@@ -52,7 +58,9 @@ extension PureXML.Schema.XSDParser {
if uri == xsdNamespace {
return builtinListTypes.contains(local) ? "list" : nil
}
- if let cached = cache[local] { return cached }
+ if let cached = cache[local] {
+ return cached
+ }
guard uri == lookup.target, !visited.contains(local), let declared = lookup.byName[local] else { return nil }
let variety = simpleTypeVariety(declared, lookup, visited.union([local]), &cache)
cache[local] = variety
diff --git a/Sources/Schema/SchemaStructure.swift b/Sources/Schema/SchemaStructure.swift
index 2ca13ff..e126698 100644
--- a/Sources/Schema/SchemaStructure.swift
+++ b/Sources/Schema/SchemaStructure.swift
@@ -100,7 +100,9 @@ extension PureXML.Schema.XSDParser {
let names = children.filter { $0.name?.namespaceURI == xsdNamespace }.compactMap(PureXML.Schema.XSDNode.localName)
append(componentSpecificErrors(node, local: local, names: names), at: node, into: &findings)
}
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
for child in children {
collectStructure(child, bindings: currentBindings, into: &findings)
}
diff --git a/Sources/Schema/SchemaStructureHelpers.swift b/Sources/Schema/SchemaStructureHelpers.swift
index 5fbb32f..e20471e 100644
--- a/Sources/Schema/SchemaStructureHelpers.swift
+++ b/Sources/Schema/SchemaStructureHelpers.swift
@@ -11,7 +11,9 @@ extension PureXML.Schema.XSDParser {
let trimmed = qname.trimmingXMLWhitespace()
guard PureXML.Schema.Lexical.isQName(trimmed) else { return nil }
if let prefix = PureXML.Schema.XSDNode.prefix(trimmed) {
- if prefix == "xml" { return nil }
+ if prefix == "xml" {
+ return nil
+ }
if bindings[prefix] == nil {
return "prefix '\(prefix)' is not bound"
}
@@ -31,7 +33,9 @@ extension PureXML.Schema.XSDParser {
static func isNonNegativeInteger(_ value: String) -> Bool {
var digits = Substring(value)
- if digits.first == "+" { digits = digits.dropFirst() }
+ if digits.first == "+" {
+ digits = digits.dropFirst()
+ }
return !digits.isEmpty && digits.allSatisfy { $0.isASCII && $0.isNumber }
}
@@ -88,7 +92,9 @@ extension PureXML.Schema.XSDParser {
let isAll = PureXML.Schema.XSDNode.elementChildren(group).contains {
$0.name?.namespaceURI == xsd && PureXML.Schema.XSDNode.localName($0) == "all"
}
- if isAll { allGroupNames.insert(name) }
+ if isAll {
+ allGroupNames.insert(name)
+ }
}
guard !allGroupNames.isEmpty else { return [] }
var findings: [PureXML.Schema.SchemaLocatedFinding] = []
@@ -121,7 +127,9 @@ extension PureXML.Schema.XSDParser {
static func canonicalMagnitude(_ value: String) -> Substring {
var digits = Substring(value)
- if digits.first == "+" { digits = digits.dropFirst() }
+ if digits.first == "+" {
+ digits = digits.dropFirst()
+ }
let trimmed = digits.drop { $0 == "0" }
return trimmed.isEmpty ? "0" : trimmed
}
@@ -135,7 +143,9 @@ extension PureXML.Schema.XSDParser {
}
var seenFacet = false
for name in names {
- if name == "annotation" { continue }
+ if name == "annotation" {
+ continue
+ }
if name == "simpleType" {
if seenFacet {
errors.append("inline simpleType must appear before facets in restriction")
@@ -215,7 +225,9 @@ extension PureXML.Schema.XSDParser {
}
var seenIdentityConstraint = false
for name in names {
- if name == "annotation" { continue }
+ if name == "annotation" {
+ continue
+ }
if name == "simpleType" || name == "complexType" {
if seenIdentityConstraint {
errors.append("inline type definition must appear before identity constraints in element")
@@ -253,7 +265,9 @@ extension PureXML.Schema.XSDParser {
}
var seenAnyAttribute = false
for name in names {
- if name == "annotation" { continue }
+ if name == "annotation" {
+ continue
+ }
if name == "attribute" || name == "attributeGroup" {
if seenAnyAttribute {
errors.append("attribute and attributeGroup references must appear before anyAttribute in attributeGroup")
diff --git a/Sources/Schema/SchemaSubstitutionType.swift b/Sources/Schema/SchemaSubstitutionType.swift
index 4f09cd3..2bf7835 100644
--- a/Sources/Schema/SchemaSubstitutionType.swift
+++ b/Sources/Schema/SchemaSubstitutionType.swift
@@ -77,7 +77,9 @@ extension PureXML.Schema.XSDParser {
let headNamespace = SubTypeNode.referenceNamespace(headReference, bindings)
let headKey = GlobalElementKey(namespaceURI: headNamespace, name: head)
guard let headType = globalElementType[headKey] else { return [] }
- if isInlineListOrUnion(element) { return [] }
+ if isInlineListOrUnion(element) {
+ return []
+ }
let inlineDeriv = inlineTypeDerivation(element)
var isDerived: Bool
let memberType: String
@@ -252,9 +254,15 @@ extension PureXML.Schema.XSDParser {
/// Whether a member type can derive from the simple ur-type. Unknown types are
/// left undecided so the reference-resolution rule, not this check, owns them.
private static func memberTypeHasSimpleContent(_ name: String, _ types: [String: PureXML.Schema.ElementType]) -> Bool? {
- if name == "anySimpleType" { return true }
- if name == "anyType" { return false }
- if PureXML.Schema.BuiltinType(rawValue: name) != nil { return true }
+ if name == "anySimpleType" {
+ return true
+ }
+ if name == "anyType" {
+ return false
+ }
+ if PureXML.Schema.BuiltinType(rawValue: name) != nil {
+ return true
+ }
var current: PureXML.Schema.ElementType? = types[name]
var steps = 0
while let resolved = current, steps <= types.count {
@@ -262,7 +270,9 @@ extension PureXML.Schema.XSDParser {
case .simple:
return true
case let .complex(complex):
- if case .simpleContent = complex.content { return true }
+ if case .simpleContent = complex.content {
+ return true
+ }
return false
case let .typeReference(key):
current = types[key]
diff --git a/Sources/Schema/SchemaUndeclaredNamespace.swift b/Sources/Schema/SchemaUndeclaredNamespace.swift
index cb6843c..5b3f775 100644
--- a/Sources/Schema/SchemaUndeclaredNamespace.swift
+++ b/Sources/Schema/SchemaUndeclaredNamespace.swift
@@ -14,7 +14,9 @@ extension PureXML.Schema.XSDParser {
/// `b`, making `b:b` resolvable), so the whole check stands down to stay lenient.
static func undeclaredNamespaceReferenceFindings(_ schema: XSDTree, containers: [XSDTree]) -> [PureXML.Schema.SchemaLocatedFinding] {
let sources = containers.isEmpty ? [schema] : containers
- if hasLocatedExternal(sources) { return [] }
+ if hasLocatedExternal(sources) {
+ return []
+ }
var declared: Set = [xsdNamespace]
declared.formUnion(unloadedReferenceNamespaces)
for container in sources where PureXML.Schema.XSDNode.localName(container) == "schema" {
@@ -46,7 +48,9 @@ extension PureXML.Schema.XSDParser {
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
let bindings = mergedNamespaceBindings(on: node, inherited: inheritedBindings)
if node.name?.namespaceURI == xsdNamespace {
for qname in foreignReferenceQNames(of: node) {
diff --git a/Sources/Schema/SchemaWildcardNamespace.swift b/Sources/Schema/SchemaWildcardNamespace.swift
index 9a40967..520515d 100644
--- a/Sources/Schema/SchemaWildcardNamespace.swift
+++ b/Sources/Schema/SchemaWildcardNamespace.swift
@@ -23,7 +23,9 @@ extension PureXML.Schema.XSDParser {
private static func validWildcardNamespace(_ raw: String) -> Bool {
let tokens = raw.split(whereSeparator: \.isWhitespace).map(String.init)
// `##any`/`##other` are valid only as the whole value, never as a list item.
- if tokens == ["##any"] || tokens == ["##other"] { return true }
+ if tokens == ["##any"] || tokens == ["##other"] {
+ return true
+ }
return tokens.allSatisfy { token in
switch token {
case "##targetNamespace", "##local": true
diff --git a/Sources/Schema/SchemaXSIAttribute.swift b/Sources/Schema/SchemaXSIAttribute.swift
index cdf6d5a..3acbad9 100644
--- a/Sources/Schema/SchemaXSIAttribute.swift
+++ b/Sources/Schema/SchemaXSIAttribute.swift
@@ -25,7 +25,9 @@ extension PureXML.Schema.XSDParser {
into findings: inout [PureXML.Schema.SchemaLocatedFinding],
) {
let local = PureXML.Schema.XSDNode.localName(node)
- if local == "appinfo" || local == "documentation" { return }
+ if local == "appinfo" || local == "documentation" {
+ return
+ }
let isSchema = node.name?.namespaceURI == xsdNamespace && local == "schema"
let isAttribute = node.name?.namespaceURI == xsdNamespace && local == "attribute"
if isAttribute, PureXML.Schema.XSDNode.attribute(node, "ref") == nil, let name = PureXML.Schema.XSDNode.attribute(node, "name") {
diff --git a/Sources/Schema/SimpleType.swift b/Sources/Schema/SimpleType.swift
index dd01dd0..b3cc7c3 100644
--- a/Sources/Schema/SimpleType.swift
+++ b/Sources/Schema/SimpleType.swift
@@ -120,17 +120,27 @@ public extension PureXML.Schema {
let normalized = Self.process(lexical, whiteSpace: facets.whiteSpace ?? .collapse)
let tokens = normalized.isEmpty ? [] : normalized.split(separator: " ").map(String.init)
for token in tokens {
- if let error = item.validate(token) { return error }
+ if let error = item.validate(token) {
+ return error
+ }
+ }
+ if let exact = facets.length, tokens.count != exact {
+ return "list length \(tokens.count) is not \(exact)"
+ }
+ if let minimum = facets.minLength, tokens.count < minimum {
+ return "list length \(tokens.count) is below \(minimum)"
+ }
+ if let maximum = facets.maxLength, tokens.count > maximum {
+ return "list length \(tokens.count) is above \(maximum)"
}
- if let exact = facets.length, tokens.count != exact { return "list length \(tokens.count) is not \(exact)" }
- if let minimum = facets.minLength, tokens.count < minimum { return "list length \(tokens.count) is below \(minimum)" }
- if let maximum = facets.maxLength, tokens.count > maximum { return "list length \(tokens.count) is above \(maximum)" }
if facets.minLength == nil, facets.length == nil {
if isBuiltinList, tokens.isEmpty {
return "list must not be empty"
}
}
- if let error = patternError(normalized) { return error }
+ if let error = patternError(normalized) {
+ return error
+ }
if let enumeration = facets.enumeration, !enumeration.contains(normalized) {
return "'\(normalized)' is not in the enumeration"
}
@@ -147,7 +157,9 @@ public extension PureXML.Schema {
/// exact pairwise comparison.
var comparesRawLexically: Bool {
guard case .atomic = variety, base.primitive.comparesLexically else { return false }
- if case .preserve = facets.whiteSpace ?? base.whiteSpace { return true }
+ if case .preserve = facets.whiteSpace ?? base.whiteSpace {
+ return true
+ }
return false
}
@@ -213,12 +225,16 @@ public extension PureXML.Schema {
// Pattern matches the lexical value; enumeration compares in the
// value space of whichever member admits each value, so `01` equals an
// enumerated `1` for an integer member, matching the atomic path.
- if let error = patternError(lexical) { return error }
+ if let error = patternError(lexical) {
+ return error
+ }
if let enumeration = facets.enumeration {
let inEnumeration = enumeration.contains { candidate in
members.contains { $0.valueMatches(lexical, literal: candidate) }
}
- if !inEnumeration { return "'\(lexical)' is not in the enumeration" }
+ if !inEnumeration {
+ return "'\(lexical)' is not in the enumeration"
+ }
}
return nil
}
@@ -258,8 +274,12 @@ public extension PureXML.Schema {
guard lower != nil || upper != nil, let decimal = DecimalValue(value, allowFraction: false) else {
return nil
}
- if let lower, decimal < lower { return "\(value) is below the minimum for \(base.rawValue)" }
- if let upper, decimal > upper { return "\(value) is above the maximum for \(base.rawValue)" }
+ if let lower, decimal < lower {
+ return "\(value) is below the minimum for \(base.rawValue)"
+ }
+ if let upper, decimal > upper {
+ return "\(value) is above the maximum for \(base.rawValue)"
+ }
return nil
}
@@ -268,19 +288,31 @@ public extension PureXML.Schema {
// items depending on the type; for QName the spec leaves the unit
// unspecified (XSD 1.0 Datatypes 4.3.1), so like Xerces and the XSTS
// NIST oracle we do not constrain QName values by length.
- if case .qName = primitive { return nil }
- if case .notation = primitive { return nil }
+ if case .qName = primitive {
+ return nil
+ }
+ if case .notation = primitive {
+ return nil
+ }
let length = primitive.measuredLength(value)
- if let exact = facets.length, length != exact { return "length \(length) is not \(exact)" }
- if let minimum = facets.minLength, length < minimum { return "length \(length) is below \(minimum)" }
- if let maximum = facets.maxLength, length > maximum { return "length \(length) is above \(maximum)" }
+ if let exact = facets.length, length != exact {
+ return "length \(length) is not \(exact)"
+ }
+ if let minimum = facets.minLength, length < minimum {
+ return "length \(length) is below \(minimum)"
+ }
+ if let maximum = facets.maxLength, length > maximum {
+ return "length \(length) is above \(maximum)"
+ }
return nil
}
private func digitsError(_ value: String) -> String? {
guard facets.totalDigits != nil || facets.fractionDigits != nil,
let decimal = DecimalValue(value, allowFraction: true) else { return nil }
- if let total = facets.totalDigits, decimal.totalDigits > total { return "more than \(total) total digits" }
+ if let total = facets.totalDigits, decimal.totalDigits > total {
+ return "more than \(total) total digits"
+ }
if let fraction = facets.fractionDigits, decimal.fractionDigits.count > fraction {
return "more than \(fraction) fraction digits"
}
@@ -306,14 +338,18 @@ public extension PureXML.Schema {
let whiteSpace = facets.whiteSpace ?? base.whiteSpace
let matched = enumeration.contains { candidate in
let normalized = Self.process(candidate, whiteSpace: whiteSpace)
- if let lhs = primitive.ordered(value), let rhs = primitive.ordered(normalized) { return lhs == rhs }
+ if let lhs = primitive.ordered(value), let rhs = primitive.ordered(normalized) {
+ return lhs == rhs
+ }
return normalized == value
}
return matched ? nil : "'\(value)' is not in the enumeration"
}
private func rangeError(_ value: String, _ primitive: Primitive) -> String? {
- if case .duration = primitive { return durationRangeError(value) }
+ if case .duration = primitive {
+ return durationRangeError(value)
+ }
guard let actual = primitive.ordered(value) else { return nil }
if let bound = facets.minInclusive, let limit = primitive.ordered(bound), actual < limit {
return "\(value) is below the inclusive minimum"
diff --git a/Sources/Schema/StepSiblingIndex.swift b/Sources/Schema/StepSiblingIndex.swift
index f429dac..46be413 100644
--- a/Sources/Schema/StepSiblingIndex.swift
+++ b/Sources/Schema/StepSiblingIndex.swift
@@ -32,7 +32,9 @@ final class StepSiblingIndex {
}
// The grammar predicates a step only when the name is ambiguous; a single
// same-name child keeps the bare `name`, so drop a lone entry.
- if ordinal < 2 { table = [:] }
+ if ordinal < 2 {
+ table = [:]
+ }
positions[parentIdentity, default: [:]][name] = table
return table[ObjectIdentifier(node)]
}
diff --git a/Sources/Schema/StreamingXSDValidator.swift b/Sources/Schema/StreamingXSDValidator.swift
index 1e03478..13e7920 100644
--- a/Sources/Schema/StreamingXSDValidator.swift
+++ b/Sources/Schema/StreamingXSDValidator.swift
@@ -38,7 +38,9 @@ public extension PureXML.Validation {
case let .startElement(name, attributes):
open(name, attributes)
case let .characters(text), let .cdata(text):
- if !stack.isEmpty { stack[stack.count - 1].text += text }
+ if !stack.isEmpty {
+ stack[stack.count - 1].text += text
+ }
case .endElement:
close()
case .comment, .processingInstruction:
diff --git a/Sources/Schema/XPathQueryCache.swift b/Sources/Schema/XPathQueryCache.swift
index ad783e9..16298bd 100644
--- a/Sources/Schema/XPathQueryCache.swift
+++ b/Sources/Schema/XPathQueryCache.swift
@@ -5,7 +5,9 @@ final class XPathQueryCache {
private var queries: [String: PureXML.XPath.Query?] = [:]
func query(_ xpath: String) -> PureXML.XPath.Query? {
- if let cached = queries[xpath] { return cached }
+ if let cached = queries[xpath] {
+ return cached
+ }
let compiled = try? PureXML.XPath.Query(xpath)
queries[xpath] = compiled
return compiled
diff --git a/Sources/Schema/XSDAttributeParser.swift b/Sources/Schema/XSDAttributeParser.swift
index d1cce9b..82d8951 100644
--- a/Sources/Schema/XSDAttributeParser.swift
+++ b/Sources/Schema/XSDAttributeParser.swift
@@ -19,7 +19,9 @@ extension PureXML.Schema.XSDParser {
for child in PureXML.Schema.XSDNode.elementChildren(node) {
switch PureXML.Schema.XSDNode.localName(child) {
case "attribute":
- if let use = attributeUse(child, context) { uses.append(use) }
+ if let use = attributeUse(child, context) {
+ uses.append(use)
+ }
case "attributeGroup":
guard let ref = PureXML.Schema.XSDNode.attribute(child, "ref") else { break }
let name = PureXML.Schema.XSDNode.stripPrefix(ref)
@@ -112,9 +114,15 @@ extension PureXML.Schema.XSDParser {
} else {
use.name = PureXML.Model.QualifiedName(localName: refName, namespaceURI: namespace)
}
- if PureXML.Schema.XSDNode.attribute(node, "use") == "required" { use.required = true }
- if PureXML.Schema.XSDNode.attribute(node, "use") == "prohibited" { use.prohibited = true }
- if let constraint = valueConstraint(of: node) { use.valueConstraint = constraint }
+ if PureXML.Schema.XSDNode.attribute(node, "use") == "required" {
+ use.required = true
+ }
+ if PureXML.Schema.XSDNode.attribute(node, "use") == "prohibited" {
+ use.prohibited = true
+ }
+ if let constraint = valueConstraint(of: node) {
+ use.valueConstraint = constraint
+ }
return use
}
diff --git a/Sources/Schema/XSDCompletions.swift b/Sources/Schema/XSDCompletions.swift
index 6a35d25..b046e72 100644
--- a/Sources/Schema/XSDCompletions.swift
+++ b/Sources/Schema/XSDCompletions.swift
@@ -52,14 +52,28 @@ public extension PureXML.Schema {
AttributeCompletion(name: $0.name.localName, required: $0.required, present: present.contains($0.name.localName))
}
let childNames = element.children.compactMap { child -> PureXML.Model.QualifiedName? in
- if case let .element(inner) = child { return inner.name }
+ if case let .element(inner) = child {
+ return inner.name
+ }
return nil
}
switch complex.content {
case let .elementOnly(particle), let .mixed(particle):
let (labels, complete) = ContentRTNMatcher(ContentRTNBuilder.build(particle)).follow(after: childNames)
- let names = labels.compactMap { if case let .name(name) = $0 { name.description } else { nil } }
- let anyElement = labels.contains { if case .wildcard = $0 { true } else { false } }
+ let names = labels.compactMap {
+ if case let .name(name) = $0 {
+ name.description
+ } else {
+ nil
+ }
+ }
+ let anyElement = labels.contains {
+ if case .wildcard = $0 {
+ true
+ } else {
+ false
+ }
+ }
return Completions(elements: names, complete: complete, allowsAnyElement: anyElement, attributes: attributes)
case .empty, .simpleContent:
return Completions(elements: [], complete: true, allowsAnyElement: false, attributes: attributes)
@@ -112,7 +126,9 @@ public extension PureXML.Schema {
private static func collect(_ term: Term, into result: inout [String: ElementType]) {
switch term {
case let .element(name, type, _, _, _, _):
- if let type { result[name.description] = type }
+ if let type {
+ result[name.description] = type
+ }
case let .group(group):
for member in group.particles {
collect(member.term, into: &result)
diff --git a/Sources/Schema/XSDComplexContent.swift b/Sources/Schema/XSDComplexContent.swift
index 6aae0d3..e4cbe63 100644
--- a/Sources/Schema/XSDComplexContent.swift
+++ b/Sources/Schema/XSDComplexContent.swift
@@ -18,7 +18,9 @@ extension PureXML.Schema.XSDParser {
if let base = resolvedBase(derivationNode, context) {
var attributes: [PureXML.Schema.AttributeUse] = []
for baseAttr in base.attributes {
- if isProhibited(baseAttr.name, under: derivationNode, context) { continue }
+ if isProhibited(baseAttr.name, under: derivationNode, context) {
+ continue
+ }
if let derived = allowedOwnAttributes.first(where: { $0.name == baseAttr.name }) {
attributes.append(derived)
} else {
@@ -181,7 +183,9 @@ extension PureXML.Schema.XSDParser {
/// type is the redefining occurrence of `baseName`, otherwise the ordinary
/// declaration. Returns nil when resolving would form a derivation cycle.
private static func baseTypeNode(named baseName: String, _ context: PureXML.Schema.XSDContext) -> XSDTree? {
- if let redefined = redefinedBaseNode(named: baseName, context) { return redefined }
+ if let redefined = redefinedBaseNode(named: baseName, context) {
+ return redefined
+ }
guard !context.visitingTypes.contains(baseName) else { return nil }
return context.complexTypeNodes[baseName]
}
@@ -204,7 +208,11 @@ extension PureXML.Schema.XSDParser {
/// The content of an extension: the base content's particle followed by the
/// extension's own particle, in a sequence. Mixed if either side is mixed.
private static func extendedContent(base: XSDContentType, own: XSDParticle?, mixed: Bool) -> XSDContentType {
- let baseMixed = if case .mixed = base { true } else { false }
+ let baseMixed = if case .mixed = base {
+ true
+ } else {
+ false
+ }
let combined = combine(particle(of: base), own)
return content(combined, mixed: mixed || baseMixed)
}
diff --git a/Sources/Schema/XSDContext.swift b/Sources/Schema/XSDContext.swift
index a32e271..8c46546 100644
--- a/Sources/Schema/XSDContext.swift
+++ b/Sources/Schema/XSDContext.swift
@@ -29,7 +29,9 @@ extension PureXML.Schema {
static func schemaOwner(_ node: XSDTree) -> XSDTree {
var current: XSDTree? = node
while let element = current {
- if localName(element) == "schema" { return element }
+ if localName(element) == "schema" {
+ return element
+ }
current = element.parent
}
return node
@@ -64,7 +66,9 @@ extension PureXML.Schema {
/// default-namespace binding (nil when none is declared).
static func referenceNamespace(_ qualified: String, _ bindings: [String: String]) -> String? {
if let prefix = prefix(qualified) {
- if prefix == "xml" { return "http://www.w3.org/XML/1998/namespace" }
+ if prefix == "xml" {
+ return "http://www.w3.org/XML/1998/namespace"
+ }
return bindings[prefix]
}
return bindings[""]
diff --git a/Sources/Schema/XSDDerivation.swift b/Sources/Schema/XSDDerivation.swift
index 51ad335..1c9e18a 100644
--- a/Sources/Schema/XSDDerivation.swift
+++ b/Sources/Schema/XSDDerivation.swift
@@ -156,7 +156,9 @@ extension PureXML.Schema.XSDParser {
var current = start
var seen: Set = []
while !redefined.contains(current), !urTypeNames.contains(current), let base = graph[current] {
- if base == start { return true }
+ if base == start {
+ return true
+ }
guard seen.insert(base).inserted else { return false }
current = base
}
@@ -182,7 +184,9 @@ extension PureXML.Schema.XSDParser {
var names: Set = []
for container in containers where XSDDerivNode.localName(container) == "redefine" {
for definition in XSDDerivNode.children(container, named: kind) {
- if let name = XSDDerivNode.attribute(definition, "name") { names.insert(name) }
+ if let name = XSDDerivNode.attribute(definition, "name") {
+ names.insert(name)
+ }
}
}
return names
@@ -235,7 +239,9 @@ extension PureXML.Schema.XSDParser {
for container in containers {
for definition in descendants(container, named: kind) {
guard let name = XSDDerivNode.attribute(definition, "name") else { continue }
- if nodeOf[name] == nil { nodeOf[name] = definition }
+ if nodeOf[name] == nil {
+ nodeOf[name] = definition
+ }
for ref in boundedReferences(definition, kind) where XSDDerivNode.referenceNamespace(ref, bindings) == target {
adjacency[name, default: []].append(XSDDerivNode.stripPrefix(ref))
}
@@ -259,7 +265,9 @@ extension PureXML.Schema.XSDParser {
case "element", "complexType", "simpleType", "attribute":
continue
case kind:
- if let ref = XSDDerivNode.attribute(child, "ref") { references.append(ref) }
+ if let ref = XSDDerivNode.attribute(child, "ref") {
+ references.append(ref)
+ }
default:
references += boundedReferences(child, kind)
}
@@ -273,8 +281,12 @@ extension PureXML.Schema.XSDParser {
var visited: Set = []
var stack = adjacency[start] ?? []
while let node = stack.popLast() {
- if node == start { return true }
- if sinks.contains(node) { continue }
+ if node == start {
+ return true
+ }
+ if sinks.contains(node) {
+ continue
+ }
guard visited.insert(node).inserted else { continue }
stack += adjacency[node] ?? []
}
diff --git a/Sources/Schema/XSDDerivationTables.swift b/Sources/Schema/XSDDerivationTables.swift
index 5cf8c36..1e5375a 100644
--- a/Sources/Schema/XSDDerivationTables.swift
+++ b/Sources/Schema/XSDDerivationTables.swift
@@ -51,7 +51,9 @@ extension PureXML.Schema.XSDParser {
private static func gatherSimpleType(_ type: XSDTree, in namespace: String?, into tables: inout DerivationTables) {
guard let name = XSDDerivNode.attribute(type, "name") else { return }
let finalSet = methodSet(XSDDerivNode.attribute(type, "final"))
- if !finalSet.isEmpty { tables.typeFinal[name] = finalSet }
+ if !finalSet.isEmpty {
+ tables.typeFinal[name] = finalSet
+ }
// A union's named member types: record them so the xsi:type-block check
// can follow a derivation that reaches this union through a member
// (cos-st-derived-OK 2.2.4). Inline (anonymous) members carry no name to
@@ -62,7 +64,9 @@ extension PureXML.Schema.XSDParser {
let memberKeys = members.split(whereSeparator: \.isWhitespace).map {
resolvedKey(of: String($0), on: union, default: namespace)
}
- if !memberKeys.isEmpty { tables.nsUnionMembers[derivationKey(name, in: namespace)] = memberKeys }
+ if !memberKeys.isEmpty {
+ tables.nsUnionMembers[derivationKey(name, in: namespace)] = memberKeys
+ }
}
guard let restriction = XSDDerivNode.firstChild(type, named: "restriction"),
let base = XSDDerivNode.attribute(restriction, "base") else { return }
@@ -86,9 +90,15 @@ extension PureXML.Schema.XSDParser {
tables.nsTypeBlock[key] = block
}
let finalSet = finalMethods(of: type)
- if !finalSet.isEmpty { tables.typeFinal[name] = finalSet }
- if let derivation = derivationInfo(type) { tables.typeDerivation[name] = derivation }
- if let derivation = namespacedDerivationInfo(type, in: namespace) { tables.nsTypeDerivation[key] = derivation }
+ if !finalSet.isEmpty {
+ tables.typeFinal[name] = finalSet
+ }
+ if let derivation = derivationInfo(type) {
+ tables.typeDerivation[name] = derivation
+ }
+ if let derivation = namespacedDerivationInfo(type, in: namespace) {
+ tables.nsTypeDerivation[key] = derivation
+ }
}
private static func gatherElement(_ element: XSDTree, in namespace: String?, into tables: inout DerivationTables) {
@@ -101,7 +111,9 @@ extension PureXML.Schema.XSDParser {
// so its namespaced key matches the instance element's qualified name.
let elementNamespace = isGlobal ? namespace : localElementNamespace(element, in: namespace)
let key = derivationKey(name, in: elementNamespace)
- if XSDDerivNode.attribute(element, "abstract") == "true" { tables.abstractElements.insert(name) }
+ if XSDDerivNode.attribute(element, "abstract") == "true" {
+ tables.abstractElements.insert(name)
+ }
let block = blockMethods(of: element, admitting: [.extension, .restriction, .substitution])
if !block.isEmpty {
tables.elementBlock[name] = block
@@ -109,7 +121,9 @@ extension PureXML.Schema.XSDParser {
}
if isGlobal {
let finalSet = finalMethods(of: element)
- if !finalSet.isEmpty { tables.elementFinal[name] = finalSet }
+ if !finalSet.isEmpty {
+ tables.elementFinal[name] = finalSet
+ }
}
if let type = XSDDerivNode.attribute(element, "type") {
tables.elementTypeNames[name] = XSDDerivNode.stripPrefix(type)
diff --git a/Sources/Schema/XSDFinding.swift b/Sources/Schema/XSDFinding.swift
new file mode 100644
index 0000000..651a5d2
--- /dev/null
+++ b/Sources/Schema/XSDFinding.swift
@@ -0,0 +1,15 @@
+/// A validation finding tagged with the decomposed rule it belongs to.
+struct XSDFinding {
+ let rule: XSDRuleToken
+ let error: XSDFailure
+
+ init(_ rule: XSDRuleToken, reason: String, at path: XSDPath) {
+ self.rule = rule
+ error = XSDFailure(reason: reason, at: path)
+ }
+
+ init(_ rule: XSDRuleToken, _ error: XSDFailure) {
+ self.rule = rule
+ self.error = error
+ }
+}
diff --git a/Sources/Schema/XSDFindingsCache.swift b/Sources/Schema/XSDFindingsCache.swift
new file mode 100644
index 0000000..8e90d8a
--- /dev/null
+++ b/Sources/Schema/XSDFindingsCache.swift
@@ -0,0 +1,12 @@
+/// Memoizes the one full tagged content-validity walk, and the one full tagged
+/// identity-constraint walk, per ``PureXML/Validation/XSDContext`` instance.
+/// Each decomposed rule's `check` reads this cache (computing it on first use)
+/// and filters by its own token, so registering the whole decomposed rule set
+/// together still walks the tree exactly once, not once per rule. A fresh
+/// `XSDContext` (built once per top-level `validate` call, see
+/// `SchemaDocument.validate(_:)`) gets a fresh cache, so there is no staleness
+/// risk across documents.
+final class XSDFindingsCache: @unchecked Sendable {
+ var contentFindings: [XSDFinding]?
+ var identityFindings: [XSDFinding]?
+}
diff --git a/Sources/Schema/XSDParserHelpers.swift b/Sources/Schema/XSDParserHelpers.swift
index c03aa4a..1c60b3d 100644
--- a/Sources/Schema/XSDParserHelpers.swift
+++ b/Sources/Schema/XSDParserHelpers.swift
@@ -7,8 +7,12 @@ extension PureXML.Schema.XSDParser {
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 }
+ if PureXML.Schema.XSDNode.attribute(element, "nillable") == "true" {
+ nillable.insert(name)
+ }
+ if let constraint = valueConstraint(of: element) {
+ constraints[name] = constraint
+ }
}
}
return (nillable, constraints)
@@ -22,7 +26,9 @@ extension PureXML.Schema.XSDParser {
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 }
+ if !constraints.isEmpty {
+ map[name, default: []] += constraints
+ }
}
}
return map
@@ -121,7 +127,9 @@ extension PureXML.Schema.XSDParser {
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") where PureXML.Schema.XSDNode.attribute(attribute, "name") == attributeName {
- if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context), let value = use.valueConstraint { return value }
+ if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context), let value = use.valueConstraint {
+ return value
+ }
}
}
guard let target = selectorTargetLocalName(constraint.selector),
@@ -129,7 +137,9 @@ extension PureXML.Schema.XSDParser {
let complex = PureXML.Schema.XSDNode.firstChild(element, named: "complexType")
else { return nil }
for attribute in descendants(complex, named: "attribute") where PureXML.Schema.XSDNode.attribute(attribute, "name") == attributeName {
- if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context), let value = use.valueConstraint { return value }
+ if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context), let value = use.valueConstraint {
+ return value
+ }
}
return nil
}
@@ -144,13 +154,17 @@ extension PureXML.Schema.XSDParser {
}
guard let complex = PureXML.Schema.XSDNode.firstChild(host, named: "complexType") else { return nil }
for child in descendants(complex, named: "element") where PureXML.Schema.XSDNode.attribute(child, "name") == local {
- if let value = valueConstraint(of: child) { return value }
+ if let value = valueConstraint(of: child) {
+ return value
+ }
}
return nil
}
static func identityFieldKey(constraint: PureXML.Schema.IdentityConstraint, field: String, targetLocal: String? = nil) -> String {
- if let targetLocal { return "\(constraint.name)|\(field)|\(targetLocal)" }
+ if let targetLocal {
+ return "\(constraint.name)|\(field)|\(targetLocal)"
+ }
return "\(constraint.name)|\(field)"
}
@@ -174,7 +188,9 @@ extension PureXML.Schema.XSDParser {
) -> PureXML.Schema.SimpleType? {
if field.hasPrefix("@") {
let attributeName = identityFieldAttributeName(field)
- if let type = attributeType(named: attributeName, under: host, context) { return type }
+ if let type = attributeType(named: attributeName, under: host, context) {
+ return type
+ }
// The selector's target may be a global element referenced from the
// host rather than nested under it; the attribute is then on that
// element's own complex type, so field values compare in its value
@@ -185,7 +201,9 @@ extension PureXML.Schema.XSDParser {
else { return nil }
for attribute in descendants(complex, named: "attribute") {
guard PureXML.Schema.XSDNode.attribute(attribute, "name") == attributeName else { continue }
- if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context) { return use.type }
+ if let use = PureXML.Schema.XSDParser.attributeUse(attribute, context) {
+ return use.type
+ }
}
return nil
}
@@ -288,7 +306,9 @@ extension PureXML.Schema.XSDParser {
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) }
+ if PureXML.Schema.XSDNode.localName(child) == name {
+ result.append(child)
+ }
result += descendants(child, named: name)
}
return result
@@ -343,7 +363,9 @@ extension PureXML.Schema.XSDParser {
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 }
+ if let name = PureXML.Schema.XSDNode.attribute(node, "name") {
+ index[name] = node
+ }
}
return index
}
@@ -363,8 +385,12 @@ extension PureXML.Schema.XSDParser {
}
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) }
+ 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
}
}
diff --git a/Sources/Schema/XSDParserParticles.swift b/Sources/Schema/XSDParserParticles.swift
index e35aa65..f3ae2fa 100644
--- a/Sources/Schema/XSDParserParticles.swift
+++ b/Sources/Schema/XSDParserParticles.swift
@@ -47,7 +47,9 @@ extension PureXML.Schema.XSDParser {
}
var particles: [Particle] = []
for child in XSDNode.elementChildren(node) {
- if let member = particle(child, context) { particles.append(member) }
+ if let member = particle(child, context) {
+ particles.append(member)
+ }
}
// An `all` group's own `minOccurs="0"` makes the GROUP optional (it occurs
// zero or one times), not its members optional: when the group is present
diff --git a/Sources/Schema/XSDRuleToken.swift b/Sources/Schema/XSDRuleToken.swift
new file mode 100644
index 0000000..20a1c82
--- /dev/null
+++ b/Sources/Schema/XSDRuleToken.swift
@@ -0,0 +1,49 @@
+/// Identifies which decomposed XSD instance-validation rule a finding belongs
+/// to. XSD content validation and identity-constraint validation are each one
+/// interdependent recursive walk (the #101 decision: a child's effective type
+/// is resolved from its parent's content model and position, then rewritten by
+/// `xsi:type` and derivation controls mid-walk, so the constraint categories
+/// cannot be split into independently-invoked walks without either duplicating
+/// the recursion or changing semantics). Tagging every finding at its origin,
+/// instead, lets the walk run once while still exposing each constraint
+/// category as its own named, independently removable, independently testable
+/// ``PureXML/Validation/Validation`` value: see ``XSDFindingsCache``.
+enum XSDRuleToken: String, CaseIterable, Sendable {
+ // MARK: Content validity
+
+ case nilAttributeRequiresNillable
+ case nilElementMustBeEmpty
+ case nilElementNoFixedConstraint
+ case fixedElementNoChildren
+ case fixedElementValueMatches
+ case emptyContentForbidsChildren
+ case simpleContentForbidsChildren
+ case simpleContentTypeValid
+ case textForbidden
+ case wildcardElementUndeclared
+ case simpleTypeDisallowedAttribute
+ case simpleTypeForbidsChildren
+ case simpleTypeValueValid
+ case typeReferenceResolves
+ case contentModelStructure
+ case attributeTypeValid
+ case attributeFixedMatches
+ case attributeRequiredPresent
+ case attributeSingleID
+ case attributeDeclaredOrPermitted
+ case wildcardAttributeUndeclared
+ case wildcardAttributeTypeValid
+ case wildcardAttributeFixedMatches
+ case xsiTypeSubstitutionValid
+ case xsiTypeOverrideResolves
+ case idIdrefConsistent
+
+ // MARK: Identity constraints
+
+ case identityConstraintXPathsValid
+ case identityKeyFieldPresent
+ case identityValueUnique
+ case identityKeyrefResolves
+ case identityFieldCardinality
+ case identityFieldMustBeSimple
+}
diff --git a/Sources/Schema/XSDSimpleParser+Facets.swift b/Sources/Schema/XSDSimpleParser+Facets.swift
index 97314bb..9312f5f 100644
--- a/Sources/Schema/XSDSimpleParser+Facets.swift
+++ b/Sources/Schema/XSDSimpleParser+Facets.swift
@@ -13,7 +13,9 @@ extension PureXML.Schema.XSDSimpleParser {
/// malformed; the canonical form orders two values by length then lexically.
static func canonicalNonNegativeInteger(_ lexical: String) -> String? {
var digits = Substring(lexical.trimmingXMLWhitespace())
- if digits.first == "+" { digits = digits.dropFirst() }
+ if digits.first == "+" {
+ digits = digits.dropFirst()
+ }
guard !digits.isEmpty, digits.allSatisfy({ $0.isASCII && $0.isNumber }) else { return nil }
let trimmed = digits.drop { $0 == "0" }
return trimmed.isEmpty ? "0" : String(trimmed)
@@ -30,9 +32,15 @@ extension PureXML.Schema.XSDSimpleParser {
let uri = XSDNode.referenceNamespace(typeName, context.namespaceBindings)
if uri == PureXML.Schema.XSDParser.xsdNamespace {
- if local == "anySimpleType" { return PureXML.Schema.SimpleType(base: .string, isAnySimpleType: true) }
- if let builtin = PureXML.Schema.BuiltinType(rawValue: local) { return PureXML.Schema.SimpleType(base: builtin) }
- if let item = listBuiltinItem(local) { return .list(item: PureXML.Schema.SimpleType(base: item), isBuiltinList: true) }
+ if local == "anySimpleType" {
+ return PureXML.Schema.SimpleType(base: .string, isAnySimpleType: true)
+ }
+ if let builtin = PureXML.Schema.BuiltinType(rawValue: local) {
+ return PureXML.Schema.SimpleType(base: builtin)
+ }
+ if let item = listBuiltinItem(local) {
+ return .list(item: PureXML.Schema.SimpleType(base: item), isBuiltinList: true)
+ }
}
return context.simpleTypes[local] ?? PureXML.Schema.SimpleType(base: .string)
}
@@ -88,7 +96,9 @@ extension PureXML.Schema.XSDSimpleParser {
private static func applyStringFacet(_ name: String?, _ value: String?, into facets: inout PureXML.Schema.Facets) {
switch name {
- case "enumeration": if let value { facets.enumeration = (facets.enumeration ?? []) + [value] }
+ case "enumeration": if let value {
+ facets.enumeration = (facets.enumeration ?? []) + [value]
+ }
case "minInclusive": facets.minInclusive = value
case "maxInclusive": facets.maxInclusive = value
case "minExclusive": facets.minExclusive = value
diff --git a/Sources/Schema/XSDSimpleTypeOrder.swift b/Sources/Schema/XSDSimpleTypeOrder.swift
index d87b584..3ffcbb3 100644
--- a/Sources/Schema/XSDSimpleTypeOrder.swift
+++ b/Sources/Schema/XSDSimpleTypeOrder.swift
@@ -6,7 +6,9 @@ extension PureXML.Schema.XSDParser {
static func simpleTypeNodesByName(_ named: [PureXML.Model.TreeNode]) -> [String: PureXML.Model.TreeNode] {
var byName: [String: PureXML.Model.TreeNode] = [:]
for node in named {
- if let name = XSDNode.attribute(node, "name"), byName[name] == nil { byName[name] = node }
+ if let name = XSDNode.attribute(node, "name"), byName[name] == nil {
+ byName[name] = node
+ }
}
return byName
}
@@ -36,7 +38,9 @@ extension PureXML.Schema.XSDParser {
order.append(name)
continue
}
- if visited.contains(name) { continue }
+ if visited.contains(name) {
+ continue
+ }
visited.insert(name)
onPath.insert(name)
work.append((name, true))
diff --git a/Sources/Schema/XSDValidations.swift b/Sources/Schema/XSDValidations.swift
index 11d8556..d307492 100644
--- a/Sources/Schema/XSDValidations.swift
+++ b/Sources/Schema/XSDValidations.swift
@@ -17,6 +17,11 @@ public extension PureXML.Validation {
public let identityFieldTypes: [String: PureXML.Schema.SimpleType]
public let identityFieldConstraints: [String: PureXML.Schema.ValueConstraint]
public let identityNonSimpleFields: Set
+ /// Memoizes the one tagged content-validity walk and the one tagged
+ /// identity-constraint walk for this context, so the decomposed rule set
+ /// below (``XSD``) walks the tree once, not once per rule. See
+ /// ``XSDFindingsCache``.
+ let findingsCache = XSDFindingsCache()
public init(
types: [String: PureXML.Schema.ElementType],
@@ -53,77 +58,267 @@ public extension PureXML.Validation {
}
public extension PureXML.Validation {
- /// ``XSDContext``, in the OpenAPIKit idiom: each rule is a named, removable
- /// ``Validation`` value with a positive description, and the ``validator()``
- /// composes them; failures are located ``ValidationError``s.
+ /// ``XSDContext``, in the OpenAPIKit idiom: each constraint category is a
+ /// named, removable ``Validation`` value with a positive description, and
+ /// ``validator()`` composes them; failures are located ``ValidationError``s.
///
- /// Scope note (the #101 decision). XSD content validation is *one* recursive
- /// ``Validation`` (``contentValidity``) rather than a separate rule per
- /// constraint, because XSD is a recursive type system: a child's type is
- /// resolved from its parent's content model and position, then rewritten by
- /// `xsi:type` and the derivation controls mid-walk. The constraint categories
- /// (attributes, content model, derivation, nillability, fixed values) are
- /// interdependent through that resolution, so they cannot be split into
- /// independent rules without either changing semantics or duplicating the
- /// recursion. This is the sanctioned multi-error form (the rule's `check`
- /// returns one located error per violation). Each category is isolation-tested
- /// through crafted inputs so it is verifiable on its own, which is the
- /// testability the idiom asks for. Identity constraints, which *are*
- /// independent, are a separate rule (``identityConstraints``).
+ /// Decomposition boundary (the #101 decision, extended). XSD content
+ /// validation and identity-constraint validation are each one recursive walk:
+ /// a child's type is resolved from its parent's content model and position,
+ /// then rewritten by `xsi:type` and the derivation controls mid-walk, and
+ /// identity's `key`/`unique`/`keyref` share one scope stack threaded through
+ /// one recursive descent. Splitting either walk into independently-INVOKED
+ /// rules would force either duplicating that recursion (a correctness risk:
+ /// two walks can drift) or fabricating rules that look independently
+ /// removable but are not.
+ ///
+ /// Instead, every finding is tagged at its origin with the decomposed rule it
+ /// belongs to (``XSDRuleToken``), the ONE walk is memoized per ``XSDContext``
+ /// (``XSDFindingsCache``), and each rule below reads the memoized, tagged
+ /// result and filters by its own token. The walk still runs exactly once when
+ /// the full rule set is registered together; each rule is still independently
+ /// named, described, removable via `withoutValidating`, and isolation-tested
+ /// (a rule tested alone via `.blank.validating(rule)` still triggers the one
+ /// full walk and returns just its own slice, so isolation tests are correct
+ /// without any special-casing).
enum XSD {
- /// The document element is valid against its declared XSD type.
- static var contentValidity: Validation {
+ /// Every content-validity finding for the document root, tagged by rule,
+ /// computed once and cached on `context.document.findingsCache`.
+ private static func contentFindings(for context: ValidationContext) -> [XSDFinding] {
+ if let cached = context.document.findingsCache.contentFindings {
+ return cached
+ }
+ guard case let .element(root) = context.subject, let declaration = context.document.rootDeclaration else {
+ context.document.findingsCache.contentFindings = []
+ return []
+ }
+ let schema = context.document
+ let validator = PureXML.Schema.ComplexValidator(
+ types: schema.types,
+ globalAttributes: schema.globalAttributes,
+ nillableElements: schema.nillableElements,
+ elementConstraints: schema.elementConstraints,
+ abstractTypes: schema.abstractTypes,
+ typeBlock: schema.typeBlock,
+ elementBlock: schema.elementBlock,
+ typeDerivation: schema.typeDerivation,
+ unionMembers: schema.unionMembers,
+ )
+ var findings = validator.validateTagged(root, as: declaration, at: [.element(root.name.description)])
+ // Document-scoped ID uniqueness and IDREF resolution, gathered during
+ // the typed walk above, reported once the tree is seen.
+ findings += validator.idErrors().map { XSDFinding(.idIdrefConsistent, $0) }
+ context.document.findingsCache.contentFindings = findings
+ return findings
+ }
+
+ /// Every identity-constraint finding for the document root, tagged by
+ /// rule, computed once and cached on `context.document.findingsCache`.
+ private static func identityFindings(for context: ValidationContext) -> [XSDFinding] {
+ if let cached = context.document.findingsCache.identityFindings {
+ return cached
+ }
+ guard case let .element(root) = context.subject else {
+ context.document.findingsCache.identityFindings = []
+ return []
+ }
+ let findings = PureXML.Schema.IdentityValidator(
+ constraints: context.document.constraints,
+ fieldTypes: context.document.identityFieldTypes,
+ fieldConstraints: context.document.identityFieldConstraints,
+ nonSimpleFields: context.document.identityNonSimpleFields,
+ types: context.document.types,
+ ).validateTagged(PureXML.Model.TreeNode(context.subject), at: [.element(root.name.description)])
+ context.document.findingsCache.identityFindings = findings
+ return findings
+ }
+
+ /// One decomposed content-validity rule: `description` names the correct
+ /// state, `token` selects its slice of the one memoized tagged walk.
+ private static func contentRule(_ token: XSDRuleToken, _ description: String) -> Validation {
.init(
- description: "The document element is valid against its XSD type",
+ description: description,
check: { context in
- guard case let .element(root) = context.subject, let declaration = context.document.rootDeclaration else {
- return []
- }
- let schema = context.document
- let validator = PureXML.Schema.ComplexValidator(
- types: schema.types,
- globalAttributes: schema.globalAttributes,
- nillableElements: schema.nillableElements,
- elementConstraints: schema.elementConstraints,
- abstractTypes: schema.abstractTypes,
- typeBlock: schema.typeBlock,
- elementBlock: schema.elementBlock,
- typeDerivation: schema.typeDerivation,
- unionMembers: schema.unionMembers,
- )
- var errors = validator.validate(root, as: declaration, at: [.element(root.name.description)])
- // Document-scoped ID uniqueness and IDREF resolution, gathered
- // during the typed walk above, reported once the tree is seen.
- errors += validator.idErrors()
- return errors
+ guard case .element = context.subject else { return [] }
+ return contentFindings(for: context).filter { $0.rule == token }.map(\.error)
},
when: { $0.codingPath.isEmpty },
)
}
- /// The document's XSD identity constraints (`unique`, `key`, `keyref`) hold.
- static var identityConstraints: Validation {
+ /// One decomposed identity-constraint rule. See ``contentRule(_:_:)``.
+ private static func identityRule(_ token: XSDRuleToken, _ description: String) -> Validation {
.init(
- description: "XSD identity constraints hold",
- check: { context -> [PureXML.Validation.ValidationError] in
- guard case let .element(root) = context.subject else { return [] }
- return PureXML.Schema.IdentityValidator(
- constraints: context.document.constraints,
- fieldTypes: context.document.identityFieldTypes,
- fieldConstraints: context.document.identityFieldConstraints,
- nonSimpleFields: context.document.identityNonSimpleFields,
- types: context.document.types,
- ).validate(PureXML.Model.TreeNode(context.subject), at: [.element(root.name.description)])
+ description: description,
+ check: { context in
+ guard case .element = context.subject else { return [] }
+ return identityFindings(for: context).filter { $0.rule == token }.map(\.error)
},
when: { $0.codingPath.isEmpty },
)
}
- /// A validator combining the content-model and identity-constraint rules.
+ // MARK: Content validity
+
+ static var nilAttributeRequiresNillable: Validation {
+ contentRule(.nilAttributeRequiresNillable, "An element carries xsi:nil only when it is declared nillable")
+ }
+
+ static var nilElementMustBeEmpty: Validation {
+ contentRule(.nilElementMustBeEmpty, "A nilled element has no content")
+ }
+
+ static var nilElementNoFixedConstraint: Validation {
+ contentRule(.nilElementNoFixedConstraint, "A nilled element carries no fixed value constraint")
+ }
+
+ static var fixedElementNoChildren: Validation {
+ contentRule(.fixedElementNoChildren, "A fixed element has no element children")
+ }
+
+ static var fixedElementValueMatches: Validation {
+ contentRule(.fixedElementValueMatches, "A fixed element's content equals its fixed value")
+ }
+
+ static var emptyContentForbidsChildren: Validation {
+ contentRule(.emptyContentForbidsChildren, "An empty-content element has no element children")
+ }
+
+ static var simpleContentForbidsChildren: Validation {
+ contentRule(.simpleContentForbidsChildren, "A simple-content element has no element children")
+ }
+
+ static var simpleContentTypeValid: Validation {
+ contentRule(.simpleContentTypeValid, "A simple-content element's text is valid against its type")
+ }
+
+ static var textForbidden: Validation {
+ contentRule(.textForbidden, "An element-only or empty-content element carries no text")
+ }
+
+ static var wildcardElementUndeclared: Validation {
+ contentRule(.wildcardElementUndeclared, "A strict wildcard-matched element has a global declaration")
+ }
+
+ static var simpleTypeDisallowedAttribute: Validation {
+ contentRule(.simpleTypeDisallowedAttribute, "A simple-typed element carries only schema-instance and namespace attributes")
+ }
+
+ static var simpleTypeForbidsChildren: Validation {
+ contentRule(.simpleTypeForbidsChildren, "A simple-typed element has no element children")
+ }
+
+ static var simpleTypeValueValid: Validation {
+ contentRule(.simpleTypeValueValid, "A simple-typed element's value is valid against its type")
+ }
+
+ static var typeReferenceResolves: Validation {
+ contentRule(.typeReferenceResolves, "A type reference resolves to a known, non-circular type")
+ }
+
+ static var contentModelStructure: Validation {
+ contentRule(.contentModelStructure, "An element's children conform to its content model")
+ }
+
+ static var attributeTypeValid: Validation {
+ contentRule(.attributeTypeValid, "A declared attribute's value is valid against its type")
+ }
+
+ static var attributeFixedMatches: Validation {
+ contentRule(.attributeFixedMatches, "A declared attribute's value equals its fixed value")
+ }
+
+ static var attributeRequiredPresent: Validation {
+ contentRule(.attributeRequiredPresent, "A required attribute is present")
+ }
+
+ static var attributeSingleID: Validation {
+ contentRule(.attributeSingleID, "An element carries at most one attribute of type ID")
+ }
+
+ static var attributeDeclaredOrPermitted: Validation {
+ contentRule(.attributeDeclaredOrPermitted, "An attribute is declared or permitted by a wildcard")
+ }
+
+ static var wildcardAttributeUndeclared: Validation {
+ contentRule(.wildcardAttributeUndeclared, "A strict wildcard-matched attribute has a global declaration")
+ }
+
+ static var wildcardAttributeTypeValid: Validation {
+ contentRule(.wildcardAttributeTypeValid, "A wildcard-matched attribute's value is valid against its type")
+ }
+
+ static var wildcardAttributeFixedMatches: Validation {
+ contentRule(.wildcardAttributeFixedMatches, "A wildcard-matched attribute's value equals its fixed value")
+ }
+
+ static var xsiTypeSubstitutionValid: Validation {
+ contentRule(.xsiTypeSubstitutionValid, "An xsi:type substitution is validly derived and not blocked")
+ }
+
+ static var xsiTypeOverrideResolves: Validation {
+ contentRule(.xsiTypeOverrideResolves, "An xsi:type override names a known, validly derived type")
+ }
+
+ static var idIdrefConsistent: Validation {
+ contentRule(.idIdrefConsistent, "Every xs:ID is unique and every xs:IDREF resolves to one")
+ }
+
+ /// Every decomposed content-validity rule, in application order.
+ static var contentValidityRules: [Validation] {
+ [
+ nilAttributeRequiresNillable, nilElementMustBeEmpty, nilElementNoFixedConstraint,
+ fixedElementNoChildren, fixedElementValueMatches, emptyContentForbidsChildren,
+ simpleContentForbidsChildren, simpleContentTypeValid, textForbidden,
+ wildcardElementUndeclared, simpleTypeDisallowedAttribute, simpleTypeForbidsChildren,
+ simpleTypeValueValid, typeReferenceResolves, contentModelStructure,
+ attributeTypeValid, attributeFixedMatches, attributeRequiredPresent,
+ attributeSingleID, attributeDeclaredOrPermitted, wildcardAttributeUndeclared,
+ wildcardAttributeTypeValid, wildcardAttributeFixedMatches, xsiTypeSubstitutionValid,
+ xsiTypeOverrideResolves, idIdrefConsistent,
+ ]
+ }
+
+ // MARK: Identity constraints
+
+ static var identityConstraintXPathsValid: Validation {
+ identityRule(.identityConstraintXPathsValid, "An identity constraint's selector and field XPaths compile")
+ }
+
+ static var identityKeyFieldPresent: Validation {
+ identityRule(.identityKeyFieldPresent, "A key constraint's fields are all present")
+ }
+
+ static var identityValueUnique: Validation {
+ identityRule(.identityValueUnique, "A key or unique constraint's values are distinct")
+ }
+
+ static var identityKeyrefResolves: Validation {
+ identityRule(.identityKeyrefResolves, "A keyref constraint's value matches a key or unique in scope")
+ }
+
+ static var identityFieldCardinality: Validation {
+ identityRule(.identityFieldCardinality, "An identity constraint field selects at most one node")
+ }
+
+ static var identityFieldMustBeSimple: Validation {
+ identityRule(.identityFieldMustBeSimple, "An identity constraint field identifies a node with simple content")
+ }
+
+ /// Every decomposed identity-constraint rule, in application order.
+ static var identityConstraintRules: [Validation] {
+ [
+ identityConstraintXPathsValid, identityKeyFieldPresent, identityValueUnique,
+ identityKeyrefResolves, identityFieldCardinality, identityFieldMustBeSimple,
+ ]
+ }
+
+ /// A validator combining every decomposed content-model and
+ /// identity-constraint rule.
static func validator() -> Validator {
Validator.defaults(
- nonReference: [AnyValidation(contentValidity)],
- reference: [AnyValidation(identityConstraints)],
+ nonReference: contentValidityRules.map { AnyValidation($0) },
+ reference: identityConstraintRules.map { AnyValidation($0) },
)
}
}
diff --git a/Sources/Schema/XSDWildcardParser.swift b/Sources/Schema/XSDWildcardParser.swift
index f0ec239..3556731 100644
--- a/Sources/Schema/XSDWildcardParser.swift
+++ b/Sources/Schema/XSDWildcardParser.swift
@@ -83,7 +83,9 @@ extension PureXML.Schema.XSDParser {
case "##other":
// `##other` = any namespace name other than the target, never absent.
// With no target namespace it is simply "any non-absent namespace".
- if let target = context.targetNamespace, !target.isEmpty { return .notNamespace(target) }
+ if let target = context.targetNamespace, !target.isEmpty {
+ return .notNamespace(target)
+ }
return .notAbsent
default:
let uris = (value ?? "").split(whereSeparator: \.isWhitespace).map { token -> String in
diff --git a/Sources/Validation/AttributeListParser.swift b/Sources/Validation/AttributeListParser.swift
index d8aeb9b..6242640 100644
--- a/Sources/Validation/AttributeListParser.swift
+++ b/Sources/Validation/AttributeListParser.swift
@@ -154,7 +154,9 @@ extension PureXML.Validation {
value.append(character)
advance()
}
- if peek() == quote { advance() }
+ if peek() == quote {
+ advance()
+ }
return value
}
@@ -179,7 +181,9 @@ extension PureXML.Validation {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
private mutating func skipSpace() {
diff --git a/Sources/Validation/BuiltinValidation.swift b/Sources/Validation/BuiltinValidation.swift
index 225e008..3d31ab0 100644
--- a/Sources/Validation/BuiltinValidation.swift
+++ b/Sources/Validation/BuiltinValidation.swift
@@ -81,16 +81,6 @@ public extension PureXML.Validation {
HTML.uniqueIdentifiers
}
- // MARK: XSD instance
-
- public static var xsdContentValidity: Validation {
- XSD.contentValidity
- }
-
- public static var xsdIdentityConstraints: Validation {
- XSD.identityConstraints
- }
-
// MARK: XSD schema consistency
public static var xsdFinalRespected: Validation {
@@ -113,6 +103,10 @@ public extension PureXML.Validation {
PureXML.Schema.ComplexValidator.shallowValidity
}
+ public static var xsdStreamingIdConsistency: Validation {
+ PureXML.Schema.ComplexValidator.idConsistency
+ }
+
/// Stable ids for each shipped builtin rule (matches ``BuiltinValidation`` properties).
public static let allRuleIDs: [String] = [
"uniqueAttributes",
@@ -133,12 +127,43 @@ public extension PureXML.Validation {
"htmlVoidElementsAreEmpty",
"htmlRequiredParent",
"htmlUniqueIdentifiers",
- "xsdContentValidity",
- "xsdIdentityConstraints",
+ "xsdNilAttributeRequiresNillable",
+ "xsdNilElementMustBeEmpty",
+ "xsdNilElementNoFixedConstraint",
+ "xsdFixedElementNoChildren",
+ "xsdFixedElementValueMatches",
+ "xsdEmptyContentForbidsChildren",
+ "xsdSimpleContentForbidsChildren",
+ "xsdSimpleContentTypeValid",
+ "xsdTextForbidden",
+ "xsdWildcardElementUndeclared",
+ "xsdSimpleTypeDisallowedAttribute",
+ "xsdSimpleTypeForbidsChildren",
+ "xsdSimpleTypeValueValid",
+ "xsdTypeReferenceResolves",
+ "xsdContentModelStructure",
+ "xsdAttributeTypeValid",
+ "xsdAttributeFixedMatches",
+ "xsdAttributeRequiredPresent",
+ "xsdAttributeSingleID",
+ "xsdAttributeDeclaredOrPermitted",
+ "xsdWildcardAttributeUndeclared",
+ "xsdWildcardAttributeTypeValid",
+ "xsdWildcardAttributeFixedMatches",
+ "xsdXsiTypeSubstitutionValid",
+ "xsdXsiTypeOverrideResolves",
+ "xsdIdIdrefConsistent",
+ "xsdIdentityConstraintXPathsValid",
+ "xsdIdentityKeyFieldPresent",
+ "xsdIdentityValueUnique",
+ "xsdIdentityKeyrefResolves",
+ "xsdIdentityFieldCardinality",
+ "xsdIdentityFieldMustBeSimple",
"xsdFinalRespected",
"xsdRestrictionsAreSubsets",
"conformanceMatchesExpected",
"xsdStreamingShallowValidity",
+ "xsdStreamingIdConsistency",
]
/// Every builtin rule description, in deterministic order, for configuration-pin tests.
@@ -162,12 +187,43 @@ public extension PureXML.Validation {
htmlVoidElementsAreEmpty.description,
htmlRequiredParent.description,
htmlUniqueIdentifiers.description,
- xsdContentValidity.description,
- xsdIdentityConstraints.description,
+ xsdNilAttributeRequiresNillable.description,
+ xsdNilElementMustBeEmpty.description,
+ xsdNilElementNoFixedConstraint.description,
+ xsdFixedElementNoChildren.description,
+ xsdFixedElementValueMatches.description,
+ xsdEmptyContentForbidsChildren.description,
+ xsdSimpleContentForbidsChildren.description,
+ xsdSimpleContentTypeValid.description,
+ xsdTextForbidden.description,
+ xsdWildcardElementUndeclared.description,
+ xsdSimpleTypeDisallowedAttribute.description,
+ xsdSimpleTypeForbidsChildren.description,
+ xsdSimpleTypeValueValid.description,
+ xsdTypeReferenceResolves.description,
+ xsdContentModelStructure.description,
+ xsdAttributeTypeValid.description,
+ xsdAttributeFixedMatches.description,
+ xsdAttributeRequiredPresent.description,
+ xsdAttributeSingleID.description,
+ xsdAttributeDeclaredOrPermitted.description,
+ xsdWildcardAttributeUndeclared.description,
+ xsdWildcardAttributeTypeValid.description,
+ xsdWildcardAttributeFixedMatches.description,
+ xsdXsiTypeSubstitutionValid.description,
+ xsdXsiTypeOverrideResolves.description,
+ xsdIdIdrefConsistent.description,
+ xsdIdentityConstraintXPathsValid.description,
+ xsdIdentityKeyFieldPresent.description,
+ xsdIdentityValueUnique.description,
+ xsdIdentityKeyrefResolves.description,
+ xsdIdentityFieldCardinality.description,
+ xsdIdentityFieldMustBeSimple.description,
xsdFinalRespected.description,
xsdRestrictionsAreSubsets.description,
conformanceMatchesExpected.description,
xsdStreamingShallowValidity.description,
+ xsdStreamingIdConsistency.description,
]
}
}
diff --git a/Sources/Validation/BuiltinValidationXSDInstance.swift b/Sources/Validation/BuiltinValidationXSDInstance.swift
new file mode 100644
index 0000000..74d3c6b
--- /dev/null
+++ b/Sources/Validation/BuiltinValidationXSDInstance.swift
@@ -0,0 +1,137 @@
+/// The decomposed XSD instance-validation rules (content validity and identity
+/// constraints), split out of ``BuiltinValidation``'s main declaration to keep
+/// each file's type body under the length limit. See ``PureXML/Validation/XSD``
+/// for why these are decomposed the way they are.
+public extension PureXML.Validation.BuiltinValidation {
+ // MARK: XSD instance: content validity
+
+ static var xsdNilAttributeRequiresNillable: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.nilAttributeRequiresNillable
+ }
+
+ static var xsdNilElementMustBeEmpty: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.nilElementMustBeEmpty
+ }
+
+ static var xsdNilElementNoFixedConstraint: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.nilElementNoFixedConstraint
+ }
+
+ static var xsdFixedElementNoChildren: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.fixedElementNoChildren
+ }
+
+ static var xsdFixedElementValueMatches: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.fixedElementValueMatches
+ }
+
+ static var xsdEmptyContentForbidsChildren: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.emptyContentForbidsChildren
+ }
+
+ static var xsdSimpleContentForbidsChildren: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.simpleContentForbidsChildren
+ }
+
+ static var xsdSimpleContentTypeValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.simpleContentTypeValid
+ }
+
+ static var xsdTextForbidden: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.textForbidden
+ }
+
+ static var xsdWildcardElementUndeclared: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.wildcardElementUndeclared
+ }
+
+ static var xsdSimpleTypeDisallowedAttribute: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.simpleTypeDisallowedAttribute
+ }
+
+ static var xsdSimpleTypeForbidsChildren: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.simpleTypeForbidsChildren
+ }
+
+ static var xsdSimpleTypeValueValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.simpleTypeValueValid
+ }
+
+ static var xsdTypeReferenceResolves: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.typeReferenceResolves
+ }
+
+ static var xsdContentModelStructure: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.contentModelStructure
+ }
+
+ static var xsdAttributeTypeValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.attributeTypeValid
+ }
+
+ static var xsdAttributeFixedMatches: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.attributeFixedMatches
+ }
+
+ static var xsdAttributeRequiredPresent: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.attributeRequiredPresent
+ }
+
+ static var xsdAttributeSingleID: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.attributeSingleID
+ }
+
+ static var xsdAttributeDeclaredOrPermitted: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.attributeDeclaredOrPermitted
+ }
+
+ static var xsdWildcardAttributeUndeclared: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.wildcardAttributeUndeclared
+ }
+
+ static var xsdWildcardAttributeTypeValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.wildcardAttributeTypeValid
+ }
+
+ static var xsdWildcardAttributeFixedMatches: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.wildcardAttributeFixedMatches
+ }
+
+ static var xsdXsiTypeSubstitutionValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.xsiTypeSubstitutionValid
+ }
+
+ static var xsdXsiTypeOverrideResolves: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.xsiTypeOverrideResolves
+ }
+
+ static var xsdIdIdrefConsistent: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.idIdrefConsistent
+ }
+
+ // MARK: XSD instance: identity constraints
+
+ static var xsdIdentityConstraintXPathsValid: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityConstraintXPathsValid
+ }
+
+ static var xsdIdentityKeyFieldPresent: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityKeyFieldPresent
+ }
+
+ static var xsdIdentityValueUnique: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityValueUnique
+ }
+
+ static var xsdIdentityKeyrefResolves: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityKeyrefResolves
+ }
+
+ static var xsdIdentityFieldCardinality: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityFieldCardinality
+ }
+
+ static var xsdIdentityFieldMustBeSimple: PureXML.Validation.Validation {
+ PureXML.Validation.XSD.identityFieldMustBeSimple
+ }
+}
diff --git a/Sources/Validation/ContentModelParser.swift b/Sources/Validation/ContentModelParser.swift
index 90d6d23..846612a 100644
--- a/Sources/Validation/ContentModelParser.swift
+++ b/Sources/Validation/ContentModelParser.swift
@@ -17,8 +17,12 @@ extension PureXML.Validation {
private mutating func parseModel() -> ContentModel {
skipSpace()
- if matchKeyword("EMPTY") { return .empty }
- if matchKeyword("ANY") { return .any }
+ if matchKeyword("EMPTY") {
+ return .empty
+ }
+ if matchKeyword("ANY") {
+ return .any
+ }
guard peek() == "(" else { return .any }
// Look inside for the #PCDATA marker to distinguish mixed/PCDATA from
@@ -37,14 +41,17 @@ extension PureXML.Validation {
var names: [String] = []
while true {
skipSpace()
- if peek() == ")" { advance()
+ if peek() == ")" {
+ advance()
break
}
if peek() == "|" {
advance()
skipSpace()
let name = parseName()
- if !name.isEmpty { names.append(name) }
+ if !name.isEmpty {
+ names.append(name)
+ }
} else {
advance()
}
@@ -67,7 +74,9 @@ extension PureXML.Validation {
items.append(parseParticle())
}
skipSpace()
- if peek() == ")" { advance() }
+ if peek() == ")" {
+ advance()
+ }
let occurrence = consumeOccurrence()
return separator == "|" ? .choice(items, occurrence) : .sequence(items, occurrence)
}
@@ -118,7 +127,9 @@ extension PureXML.Validation {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
private mutating func skipSpace() {
diff --git a/Sources/Validation/DTDDefaults.swift b/Sources/Validation/DTDDefaults.swift
index e1c9d62..7977ae8 100644
--- a/Sources/Validation/DTDDefaults.swift
+++ b/Sources/Validation/DTDDefaults.swift
@@ -33,7 +33,9 @@ public extension PureXML.Validation.DTDSchema {
let present = result.attributes.contains {
$0.name.description == declaration.name || $0.name.localName == declaration.name
}
- if !present { result.attributes.append(PureXML.Model.Attribute(declaration.name, value)) }
+ if !present {
+ result.attributes.append(PureXML.Model.Attribute(declaration.name, value))
+ }
}
return result
}
diff --git a/Sources/Validation/DTDStandaloneValidity.swift b/Sources/Validation/DTDStandaloneValidity.swift
index 4317c05..9878b6d 100644
--- a/Sources/Validation/DTDStandaloneValidity.swift
+++ b/Sources/Validation/DTDStandaloneValidity.swift
@@ -58,7 +58,9 @@ extension PureXML.Validation.DTD {
}
// Present attribute whose externally-declared non-CDATA type calls for
// normalization the standalone processor would not perform.
- if case .cdata = declaration.type { return nil }
+ if case .cdata = declaration.type {
+ return nil
+ }
guard supplied.value != normalize(supplied.value, for: declaration.type) else { return nil }
return DTDFailure(
reason: "standalone document depends on the externally-declared normalization of attribute '\(declaration.name)' on <\(element.name.description)>",
diff --git a/Sources/Validation/DTDValidationHelpers.swift b/Sources/Validation/DTDValidationHelpers.swift
index ad354af..2d6e63b 100644
--- a/Sources/Validation/DTDValidationHelpers.swift
+++ b/Sources/Validation/DTDValidationHelpers.swift
@@ -112,7 +112,9 @@ extension PureXML.Validation.DTD {
/// arrived through a character reference is data, not separation. CDATA
/// values pass through untouched.
static func normalize(_ value: String, for type: PureXML.Validation.AttributeType) -> String {
- if case .cdata = type { return value }
+ if case .cdata = type {
+ return value
+ }
return value.split(separator: " ").joined(separator: " ")
}
diff --git a/Sources/Validation/Schematron.swift b/Sources/Validation/Schematron.swift
index e7ea29c..b9a1aa6 100644
--- a/Sources/Validation/Schematron.swift
+++ b/Sources/Validation/Schematron.swift
@@ -182,7 +182,9 @@ public extension PureXML.Validation {
for id in assertion.diagnostics {
guard let parts = diagnostics[id] else { continue }
let rendered = render(parts, at: node, variables: variables, functions)
- if !rendered.isEmpty { text += " " + rendered }
+ if !rendered.isEmpty {
+ text += " " + rendered
+ }
}
return text
}
diff --git a/Sources/Validation/SchematronParser.swift b/Sources/Validation/SchematronParser.swift
index 0424e0d..167064d 100644
--- a/Sources/Validation/SchematronParser.swift
+++ b/Sources/Validation/SchematronParser.swift
@@ -45,7 +45,9 @@ extension PureXML.Validation {
let patternNodes = descendants(root, localName: "pattern")
var templates: [String: PureXML.Model.TreeNode] = [:]
for node in patternNodes where attribute(node, "abstract") == "true" {
- if let id = attribute(node, "id") { templates[id] = node }
+ if let id = attribute(node, "id") {
+ templates[id] = node
+ }
}
var patterns: [SchematronPattern] = []
for node in patternNodes where attribute(node, "abstract") != "true" {
@@ -63,7 +65,9 @@ extension PureXML.Validation {
private static func parameters(_ node: PureXML.Model.TreeNode) -> [String: String] {
var result: [String: String] = [:]
for param in children(node, localName: "param") {
- if let name = attribute(param, "name"), let value = attribute(param, "value") { result[name] = value }
+ if let name = attribute(param, "name"), let value = attribute(param, "value") {
+ result[name] = value
+ }
}
return result
}
diff --git a/Sources/Validation/StreamingDTDValidator.swift b/Sources/Validation/StreamingDTDValidator.swift
index 62e0ef0..11a820b 100644
--- a/Sources/Validation/StreamingDTDValidator.swift
+++ b/Sources/Validation/StreamingDTDValidator.swift
@@ -32,7 +32,9 @@ public extension PureXML.Validation {
DTD.tokenizedAttributeTypes,
DTD.notationAttributes,
]
- if strict { rules.append(DTD.undeclaredElement) }
+ if strict {
+ rules.append(DTD.undeclaredElement)
+ }
self.rules = rules
}
@@ -118,7 +120,9 @@ public extension PureXML.Validation {
switch declaration.type {
case .id:
idCounts[value, default: 0] += 1
- if idCounts[value] == 2 { idDuplicatePath[value] = frame.path }
+ if idCounts[value] == 2 {
+ idDuplicatePath[value] = frame.path
+ }
case .idReference:
pendingReferences.append(StreamingIDReference(value: value, element: name, path: frame.path))
case .idReferences:
diff --git a/Sources/Validation/ValidationCombinators.swift b/Sources/Validation/ValidationCombinators.swift
index 31b342b..3d2bafc 100644
--- a/Sources/Validation/ValidationCombinators.swift
+++ b/Sources/Validation/ValidationCombinators.swift
@@ -25,7 +25,9 @@ func || (
) -> (VContext) -> [VError] {
{ context in
let left = lhs(context)
- if left.isEmpty { return [] }
+ if left.isEmpty {
+ return []
+ }
let right = rhs(context)
return right.isEmpty ? [] : left + right
}
@@ -126,3 +128,21 @@ func lookup(
return validations.flatMap { $0.apply(to: resolved, at: context.codingPath, in: context.document) }
}
}
+
+/// Resolve an optional named reference from the document: a nil reference is
+/// not an error, a named-but-unresolved reference errors, else validate the
+/// resolved value.
+func unwrapAndLookup(
+ _ path: KeyPath,
+ name: KeyPath,
+ missing: @escaping (String) -> String,
+ into validations: VRule...,
+) -> (VContext) -> [VError] {
+ { context in
+ guard let key = context.subject[keyPath: name] else { return [] }
+ guard let resolved = context.document[keyPath: path][key] else {
+ return [VError(reason: missing(key), at: context.codingPath)]
+ }
+ return validations.flatMap { $0.apply(to: resolved, at: context.codingPath, in: context.document) }
+ }
+}
diff --git a/Sources/Validation/Validator.swift b/Sources/Validation/Validator.swift
index a635dcc..85b3731 100644
--- a/Sources/Validation/Validator.swift
+++ b/Sources/Validation/Validator.swift
@@ -150,7 +150,9 @@ public extension PureXML.Validation {
public func validate(_ node: PureXML.Model.Node, in document: Document, strict: Bool = true) throws {
let split = PureXML.Validation.splitFindings(findings(for: node, in: document))
let toThrow = strict ? split.errors + split.warnings : split.errors
- if !toThrow.isEmpty { throw ValidationErrorCollection(values: toThrow) }
+ if !toThrow.isEmpty {
+ throw ValidationErrorCollection(values: toThrow)
+ }
}
/// Validates `root` and its subtree in document order. The walk is
diff --git a/Sources/XInclude/URIReference.swift b/Sources/XInclude/URIReference.swift
index 09e6426..9cd8864 100644
--- a/Sources/XInclude/URIReference.swift
+++ b/Sources/XInclude/URIReference.swift
@@ -63,9 +63,13 @@ extension PureXML.XInclude {
var input = Substring(path)
var output = ""
while !input.isEmpty {
- if input.hasPrefix("../") { input = input.dropFirst(3) } else if input.hasPrefix("./") {
+ if input.hasPrefix("../") {
+ input = input.dropFirst(3)
+ } else if input.hasPrefix("./") {
input = input.dropFirst(2)
- } else if input.hasPrefix("/./") { input = "/" + input.dropFirst(3) } else if input == "/." {
+ } else if input.hasPrefix("/./") {
+ input = "/" + input.dropFirst(3)
+ } else if input == "/." {
input = "/"
} else if input.hasPrefix("/../") {
input = "/" + input.dropFirst(4)
@@ -101,11 +105,19 @@ extension PureXML.XInclude {
private func recomposed() -> String {
var result = ""
- if let scheme { result += scheme + ":" }
- if let authority { result += "//" + authority }
+ if let scheme {
+ result += scheme + ":"
+ }
+ if let authority {
+ result += "//" + authority
+ }
result += path
- if let query { result += "?" + query }
- if let fragment { result += "#" + fragment }
+ if let query {
+ result += "?" + query
+ }
+ if let fragment {
+ result += "#" + fragment
+ }
return result
}
diff --git a/Sources/XInclude/XIncludeProcessor.swift b/Sources/XInclude/XIncludeProcessor.swift
index 61d772c..febb9e4 100644
--- a/Sources/XInclude/XIncludeProcessor.swift
+++ b/Sources/XInclude/XIncludeProcessor.swift
@@ -103,7 +103,9 @@ private struct XIncludeRun {
let resolved = PureXML.XInclude.URIReference.resolve(href, against: elementBase)
let isText = attribute(element, "parse") == "text"
let xpointer = attribute(element, "xpointer")
- if isText, xpointer != nil { throw PureXML.XInclude.XIncludeError.textWithFragment }
+ if isText, xpointer != nil {
+ throw PureXML.XInclude.XIncludeError.textWithFragment
+ }
// A resource already on its own inclusion chain is a cycle.
guard !context.visited.contains(resolved) else { throw PureXML.XInclude.XIncludeError.cycle(resolved) }
guard let content = load(request(element, uri: resolved, isText: isText)) else {
@@ -138,7 +140,9 @@ private struct XIncludeRun {
private func selectedNodes(from parsed: Node, xpointer: String?) -> [Node] {
guard let xpointer else { return [documentElement(of: parsed) ?? parsed] }
let selections = (try? PureXML.XPointer.evaluate(xpointer, over: parsed)) ?? []
- if !selections.isEmpty { return selections.map(Self.node) }
+ if !selections.isEmpty {
+ return selections.map(Self.node)
+ }
// No node-selecting scheme matched: fall back to the XPointer range model
// (range()/range-to()/string-range()) and include each range's content.
let ranges = (try? PureXML.XPointer.evaluateRanges(xpointer, over: parsed)) ?? []
@@ -147,7 +151,13 @@ private struct XIncludeRun {
private func documentElement(of parsed: Node) -> Node? {
guard case let .document(children) = parsed else { return nil }
- return children.first { if case .element = $0 { true } else { false } }
+ return children.first {
+ if case .element = $0 {
+ true
+ } else {
+ false
+ }
+ }
}
private static func node(_ selection: PureXML.XPath.Selection) -> Node {
diff --git a/Sources/XPath/AxisNavigation.swift b/Sources/XPath/AxisNavigation.swift
index 58b89d9..f71679b 100644
--- a/Sources/XPath/AxisNavigation.swift
+++ b/Sources/XPath/AxisNavigation.swift
@@ -100,7 +100,9 @@ extension PureXML.XPath {
/// tested first, then its descendants.
static func descendantsOrSelf(of tree: PureXML.Model.TreeNode, where keep: (PureXML.Model.TreeNode) -> Bool) -> [Node] {
var result: [Node] = []
- if keep(tree) { result.append(.tree(tree)) }
+ if keep(tree) {
+ result.append(.tree(tree))
+ }
appendDescendants(of: tree, where: keep, into: &result)
return result
}
@@ -114,7 +116,9 @@ extension PureXML.XPath {
// pushed reversed so they pop in document order.
var stack = Array(tree.children.reversed())
while let node = stack.popLast() {
- if keep(node) { result.append(.tree(node)) }
+ if keep(node) {
+ result.append(.tree(node))
+ }
if !node.children.isEmpty {
stack.append(contentsOf: node.children.reversed())
}
@@ -207,7 +211,9 @@ extension PureXML.XPath {
rootedAt root: PureXML.Model.TreeNode,
cache: DocumentNavigationCache?,
) -> (nodes: [Node], index: [Node: Int]) {
- if let cache, let hit = cache.byRoot[ObjectIdentifier(root)] { return hit }
+ if let cache, let hit = cache.byRoot[ObjectIdentifier(root)] {
+ return hit
+ }
var nodes: [Node] = []
appendSubtree(root, into: &nodes)
var index: [Node: Int] = [:]
@@ -264,7 +270,9 @@ extension PureXML.XPath {
for attribute in element.attributes {
let name = attribute.name
if name.prefix == nil, name.localName == "xmlns" {
- if bindings[""] == nil { bindings[""] = attribute.value }
+ if bindings[""] == nil {
+ bindings[""] = attribute.value
+ }
} else if name.prefix == "xmlns", bindings[name.localName] == nil {
bindings[name.localName] = attribute.value
}
diff --git a/Sources/XPath/Compiler.swift b/Sources/XPath/Compiler.swift
index 7e84b9a..d80d0b9 100644
--- a/Sources/XPath/Compiler.swift
+++ b/Sources/XPath/Compiler.swift
@@ -74,8 +74,12 @@ extension PureXML.XPath {
private mutating func relationalOperator() -> BinaryOperator? {
skipSpace()
- if consume("<=") { return .lessOrEqual }
- if consume(">=") { return .greaterOrEqual }
+ if consume("<=") {
+ return .lessOrEqual
+ }
+ if consume(">=") {
+ return .greaterOrEqual
+ }
if peek() == "<" {
advance()
return .lessThan
@@ -169,7 +173,9 @@ extension PureXML.XPath {
/// character), used to read operator keywords in operator position.
func peekKeyword(_ word: String) -> Bool {
guard matches(word) else { return false }
- if let after = peek(word.count), after.isXMLNameContinuation { return false }
+ if let after = peek(word.count), after.isXMLNameContinuation {
+ return false
+ }
return true
}
diff --git a/Sources/XPath/CompilerPaths.swift b/Sources/XPath/CompilerPaths.swift
index 4d46c57..bac3057 100644
--- a/Sources/XPath/CompilerPaths.swift
+++ b/Sources/XPath/CompilerPaths.swift
@@ -36,8 +36,12 @@ extension PureXML.XPath.Compiler {
if character == "$" || character == "(" || character == "\"" || character == "'" {
return true
}
- if character.isNumber { return true }
- if character == ".", peek(1)?.isNumber == true { return true }
+ if character.isNumber {
+ return true
+ }
+ if character == ".", peek(1)?.isNumber == true {
+ return true
+ }
return functionCallAhead()
}
@@ -302,7 +306,9 @@ extension PureXML.XPath.Compiler {
mutating func parseName() -> String {
var name = ""
while let character = peek(), character.isXMLNameContinuation {
- if character == ":", peek(1) == ":" { break }
+ if character == ":", peek(1) == ":" {
+ break
+ }
name.append(character)
advance()
}
diff --git a/Sources/XPath/CoreFunctions.swift b/Sources/XPath/CoreFunctions.swift
index 1df583e..a40fb16 100644
--- a/Sources/XPath/CoreFunctions.swift
+++ b/Sources/XPath/CoreFunctions.swift
@@ -21,11 +21,15 @@ extension PureXML.XPath {
"false": { _, _ in .boolean(false) },
"boolean": { arguments, _ in .boolean(arguments.first?.boolean ?? false) },
"number": { arguments, context in
- if let first = arguments.first { return .number(first.number) }
+ if let first = arguments.first {
+ return .number(first.number)
+ }
return .number(PureXML.XPath.Value.parseNumber(context.node.stringValue))
},
"string": { arguments, context in
- if let first = arguments.first { return .string(first.string) }
+ if let first = arguments.first {
+ return .string(first.string)
+ }
return .string(context.node.stringValue)
},
])
diff --git a/Sources/XPath/DocumentOrder.swift b/Sources/XPath/DocumentOrder.swift
index 2284bbd..2228211 100644
--- a/Sources/XPath/DocumentOrder.swift
+++ b/Sources/XPath/DocumentOrder.swift
@@ -12,9 +12,15 @@ extension PureXML.XPath.AxisNavigation {
var sub = 0
static func < (lhs: OrderKey, rhs: OrderKey) -> Bool {
- if lhs.rootRank != rhs.rootRank { return lhs.rootRank < rhs.rootRank }
- if lhs.index != rhs.index { return lhs.index < rhs.index }
- if lhs.band != rhs.band { return lhs.band < rhs.band }
+ if lhs.rootRank != rhs.rootRank {
+ return lhs.rootRank < rhs.rootRank
+ }
+ if lhs.index != rhs.index {
+ return lhs.index < rhs.index
+ }
+ if lhs.band != rhs.band {
+ return lhs.band < rhs.band
+ }
return lhs.sub < rhs.sub
}
}
diff --git a/Sources/XPath/EXSLTFunctions.swift b/Sources/XPath/EXSLTFunctions.swift
index 14c9ad3..0743ab0 100644
--- a/Sources/XPath/EXSLTFunctions.swift
+++ b/Sources/XPath/EXSLTFunctions.swift
@@ -51,7 +51,9 @@ extension PureXML.XPath {
private static func extremum(_ value: Value?, isMin: Bool) -> Double {
guard let nodes = value?.nodes, !nodes.isEmpty else { return .nan }
let numbers = nodes.map { PureXML.XPath.Value.parseNumber($0.stringValue) }
- if numbers.contains(where: \.isNaN) { return .nan }
+ if numbers.contains(where: \.isNaN) {
+ return .nan
+ }
return isMin ? numbers.min() ?? .nan : numbers.max() ?? .nan
}
diff --git a/Sources/XPath/Evaluator.swift b/Sources/XPath/Evaluator.swift
index a0749fe..016efc8 100644
--- a/Sources/XPath/Evaluator.swift
+++ b/Sources/XPath/Evaluator.swift
@@ -154,8 +154,12 @@ extension PureXML.XPath {
/// QName): the raw key is tried first, so a caller's raw binding is
/// unaffected, then the namespace-resolved name.
private static func resolveVariable(_ name: String, _ context: EvaluationContext) throws -> Value {
- if let value = context.variables[name] { return value }
- if let expanded = expandedKey(name, context.namespaces), let value = context.variables[expanded] { return value }
+ if let value = context.variables[name] {
+ return value
+ }
+ if let expanded = expandedKey(name, context.namespaces), let value = context.variables[expanded] {
+ return value
+ }
throw QueryError.undefinedVariable(name)
}
diff --git a/Sources/XPath/EvaluatorOperators.swift b/Sources/XPath/EvaluatorOperators.swift
index b115a6e..a0199d5 100644
--- a/Sources/XPath/EvaluatorOperators.swift
+++ b/Sources/XPath/EvaluatorOperators.swift
@@ -71,9 +71,13 @@ extension PureXML.XPath.Evaluator {
}
private static func scalarEquality(_ lhs: Value, _ rhs: Value, negate: Bool) -> Bool {
- let equal: Bool = if case .boolean = lhs { lhs.boolean == rhs.boolean } else if case .boolean = rhs {
+ let equal: Bool = if case .boolean = lhs {
lhs.boolean == rhs.boolean
- } else if case .number = lhs { lhs.number == rhs.number } else if case .number = rhs {
+ } else if case .boolean = rhs {
+ lhs.boolean == rhs.boolean
+ } else if case .number = lhs {
+ lhs.number == rhs.number
+ } else if case .number = rhs {
lhs.number == rhs.number
} else {
lhs.string == rhs.string
diff --git a/Sources/XPath/EvaluatorSteps.swift b/Sources/XPath/EvaluatorSteps.swift
index 0004187..1b71323 100644
--- a/Sources/XPath/EvaluatorSteps.swift
+++ b/Sources/XPath/EvaluatorSteps.swift
@@ -232,20 +232,26 @@ extension PureXML.XPath.Evaluator {
case let .name(name):
let kind = axis.principalKind
return { node in
- if case let .tree(tree) = node { return treeTest(tree) }
+ if case let .tree(tree) = node {
+ return treeTest(tree)
+ }
return nameMatches(node, name, kind, namespaces)
}
case .wildcard:
let kind = axis.principalKind
return { node in
- if case let .tree(tree) = node { return treeTest(tree) }
+ if case let .tree(tree) = node {
+ return treeTest(tree)
+ }
return wildcardMatches(node, kind)
}
case .node:
return { _ in true }
case .text, .comment, .processingInstruction:
return { node in
- if case let .tree(tree) = node { return treeTest(tree) }
+ if case let .tree(tree) = node {
+ return treeTest(tree)
+ }
return false
}
}
@@ -303,7 +309,9 @@ extension PureXML.XPath.Evaluator {
if let uri = namespaces[prefix] {
let local = String(name[name.index(after: colon)...])
// The NCName:* form matches every name in the namespace.
- if local == "*" { return (qualified.namespaceURI ?? "") == uri }
+ if local == "*" {
+ return (qualified.namespaceURI ?? "") == uri
+ }
return qualified.localName == local && (qualified.namespaceURI ?? "") == uri
}
} else {
@@ -323,9 +331,13 @@ extension PureXML.XPath.Evaluator {
private static func wildcardMatches(_ node: Node, _ kind: PrincipalKind) -> Bool {
switch kind {
case .element: return isTreeKind(node, in: [.element])
- case .attribute: if case .attribute = node { return true }
+ case .attribute: if case .attribute = node {
+ return true
+ }
return false
- case .namespace: if case .namespace = node { return true }
+ case .namespace: if case .namespace = node {
+ return true
+ }
return false
}
}
diff --git a/Sources/XPath/NodeFunctions.swift b/Sources/XPath/NodeFunctions.swift
index 6f783a9..7842047 100644
--- a/Sources/XPath/NodeFunctions.swift
+++ b/Sources/XPath/NodeFunctions.swift
@@ -32,7 +32,9 @@ extension PureXML.XPath {
}
private static func target(_ arguments: [Value], _ context: EvaluationContext) -> Node? {
- if arguments.isEmpty { return context.node }
+ if arguments.isEmpty {
+ return context.node
+ }
return arguments.first?.nodes?.firstInDocumentOrder()
}
diff --git a/Sources/XPath/StringFunctions.swift b/Sources/XPath/StringFunctions.swift
index f078610..0544328 100644
--- a/Sources/XPath/StringFunctions.swift
+++ b/Sources/XPath/StringFunctions.swift
@@ -67,7 +67,9 @@ extension PureXML.XPath {
while prefix > 0, needle[index] != needle[prefix] {
prefix = failure[prefix - 1]
}
- if needle[index] == needle[prefix] { prefix += 1 }
+ if needle[index] == needle[prefix] {
+ prefix += 1
+ }
failure[index] = prefix
}
var matched = 0
@@ -75,8 +77,12 @@ extension PureXML.XPath {
while matched > 0, haystack[index] != needle[matched] {
matched = failure[matched - 1]
}
- if haystack[index] == needle[matched] { matched += 1 }
- if matched == needle.count { return index - needle.count + 1 }
+ if haystack[index] == needle[matched] {
+ matched += 1
+ }
+ if matched == needle.count {
+ return index - needle.count + 1
+ }
}
return nil
}
@@ -112,7 +118,9 @@ extension PureXML.XPath {
var replace: [Character: Character] = [:]
var delete: Set = []
for (index, character) in from.enumerated() {
- if replace[character] != nil || delete.contains(character) { continue }
+ if replace[character] != nil || delete.contains(character) {
+ continue
+ }
if index < replacements.count {
replace[character] = replacements[index]
} else {
@@ -134,7 +142,9 @@ extension PureXML.XPath {
/// XPath rounding: half rounds toward positive infinity, with NaN and
/// infinities passed through.
static func rounded(_ value: Double) -> Double {
- if value.isNaN || value.isInfinite { return value }
+ if value.isNaN || value.isInfinite {
+ return value
+ }
return (value + 0.5).rounded(.down)
}
}
diff --git a/Sources/XPath/Value.swift b/Sources/XPath/Value.swift
index 431a4c4..1e374fa 100644
--- a/Sources/XPath/Value.swift
+++ b/Sources/XPath/Value.swift
@@ -56,8 +56,12 @@ public extension PureXML.XPath {
/// XPath 1.0 forbids exponential notation, so any exponent in the shortest
/// representation is expanded to a plain decimal.
static func format(_ value: Double) -> String {
- if value.isNaN { return "NaN" }
- if value.isInfinite { return value < 0 ? "-Infinity" : "Infinity" }
+ if value.isNaN {
+ return "NaN"
+ }
+ if value.isInfinite {
+ return value < 0 ? "-Infinity" : "Infinity"
+ }
// An integer-valued double prints with no decimal point. The bound is
// 2^63 so the Int64 conversion cannot trap; exact integers run to 2^53
// and the representable ones above it are still whole, and a magnitude
@@ -114,9 +118,13 @@ public extension PureXML.XPath {
var seenDot = false
var digits = 0
for (offset, character) in trimmed.enumerated() {
- if character == "-", offset == 0 { continue }
+ if character == "-", offset == 0 {
+ continue
+ }
if character == "." {
- if seenDot { return .nan }
+ if seenDot {
+ return .nan
+ }
seenDot = true
continue
}
diff --git a/Sources/XPointer/Pointer.swift b/Sources/XPointer/Pointer.swift
index 94dca86..d772ca3 100644
--- a/Sources/XPointer/Pointer.swift
+++ b/Sources/XPointer/Pointer.swift
@@ -52,7 +52,9 @@ private struct PointerScanner {
while let character = peek() {
advance()
if let active = quote {
- if character == active { quote = nil }
+ if character == active {
+ quote = nil
+ }
data.append(character)
continue
}
@@ -63,7 +65,9 @@ private struct PointerScanner {
depth += 1
case ")":
depth -= 1
- if depth == 0 { return data }
+ if depth == 0 {
+ return data
+ }
default:
break
}
@@ -75,7 +79,9 @@ private struct PointerScanner {
private func translate(scheme: String, data: String) throws -> PointerPart {
switch scheme {
case "xpointer", "xpath1":
- if let form = PureXML.XPointer.RangeForm.parse(data) { return .range(form) }
+ if let form = PureXML.XPointer.RangeForm.parse(data) {
+ return .range(form)
+ }
return .expression(data)
case "element": return .expression(Self.elementToXPath(data))
case "xmlns": return try Self.namespaceBinding(data)
@@ -133,7 +139,9 @@ private struct PointerScanner {
}
private mutating func advance() {
- if index < chars.count { index += 1 }
+ if index < chars.count {
+ index += 1
+ }
}
private mutating func skipSpace() {
@@ -174,7 +182,9 @@ public extension PureXML.XPointer {
case let .expression(xpath):
guard let query = try? PureXML.XPath.Query(xpath) else { continue }
let result = query.evaluate(over: node, namespaces: namespaces)
- if !result.isEmpty { return result }
+ if !result.isEmpty {
+ return result
+ }
case .range:
continue
}
@@ -197,7 +207,9 @@ public extension PureXML.XPointer {
namespaces[prefix] = uri
case let .range(form):
let ranges = form.ranges(over: root, namespaces: namespaces)
- if !ranges.isEmpty { return ranges }
+ if !ranges.isEmpty {
+ return ranges
+ }
case .expression:
continue
}
diff --git a/Sources/XPointer/PointerRange.swift b/Sources/XPointer/PointerRange.swift
index c22fa47..7394cb7 100644
--- a/Sources/XPointer/PointerRange.swift
+++ b/Sources/XPointer/PointerRange.swift
@@ -68,7 +68,9 @@ public extension PureXML.XPointer {
while index < characters.count {
let character = characters[index]
if let active = quote {
- if character == active { quote = nil }
+ if character == active {
+ quote = nil
+ }
} else if character == "'" || character == "\"" {
quote = character
} else if character == "(" {
@@ -104,7 +106,9 @@ public extension PureXML.XPointer {
var quote: Character?
for character in value {
if let active = quote {
- if character == active { quote = nil }
+ if character == active {
+ quote = nil
+ }
current.append(character)
} else if character == "'" || character == "\"" {
quote = character
@@ -180,7 +184,9 @@ extension PureXML.XPointer.RangeForm {
while matched > 0, needle[index] != needle[matched] {
matched = failure[matched - 1]
}
- if needle[index] == needle[matched] { matched += 1 }
+ if needle[index] == needle[matched] {
+ matched += 1
+ }
failure[index] = matched
}
return failure
diff --git a/Sources/XSLT/XSLTAVT.swift b/Sources/XSLT/XSLTAVT.swift
index 8233691..7dd64e1 100644
--- a/Sources/XSLT/XSLTAVT.swift
+++ b/Sources/XSLT/XSLTAVT.swift
@@ -19,7 +19,8 @@ extension PureXML.XSLT.XSLTParser {
continue
}
if character == "{" {
- if !literal.isEmpty { parts.append(.literal(literal))
+ if !literal.isEmpty {
+ parts.append(.literal(literal))
literal = ""
}
index += 1
@@ -35,7 +36,9 @@ extension PureXML.XSLT.XSLTParser {
literal.append(character)
index += 1
}
- if !literal.isEmpty { parts.append(.literal(literal)) }
+ if !literal.isEmpty {
+ parts.append(.literal(literal))
+ }
return parts
}
}
diff --git a/Sources/XSLT/XSLTCollation.swift b/Sources/XSLT/XSLTCollation.swift
index 0247c8a..0d14097 100644
--- a/Sources/XSLT/XSLTCollation.swift
+++ b/Sources/XSLT/XSLTCollation.swift
@@ -39,9 +39,13 @@ extension PureXML.XSLT {
for (first, second) in zip(left, right) where first != second {
let leftRank = ranks[first] ?? beyond + Int(first.unicodeScalars.first?.value ?? 0)
let rightRank = ranks[second] ?? beyond + Int(second.unicodeScalars.first?.value ?? 0)
- if leftRank != rightRank { return leftRank < rightRank ? -1 : 1 }
+ if leftRank != rightRank {
+ return leftRank < rightRank ? -1 : 1
+ }
+ }
+ if left.count != right.count {
+ return left.count < right.count ? -1 : 1
}
- if left.count != right.count { return left.count < right.count ? -1 : 1 }
return left == right ? 0 : (left < right ? -1 : 1)
}
}
diff --git a/Sources/XSLT/XSLTDocumentCache.swift b/Sources/XSLT/XSLTDocumentCache.swift
index eb19b94..d11c7d0 100644
--- a/Sources/XSLT/XSLTDocumentCache.swift
+++ b/Sources/XSLT/XSLTDocumentCache.swift
@@ -62,7 +62,9 @@ extension PureXML.XSLT {
let names = PureXML.Validation.AttributeListParser.parse(body)
.filter { $0.type == .id }
.map(\.name)
- if !names.isEmpty { declared[element, default: []].formUnion(names) }
+ if !names.isEmpty {
+ declared[element, default: []].formUnion(names)
+ }
}
return declared
}
diff --git a/Sources/XSLT/XSLTElements.swift b/Sources/XSLT/XSLTElements.swift
index 1cf229d..953cd5c 100644
--- a/Sources/XSLT/XSLTElements.swift
+++ b/Sources/XSLT/XSLTElements.swift
@@ -4,8 +4,12 @@ extension PureXML.XSLT.Transformer {
static func caseInsensitiveCompare(_ left: String, _ right: String, _ caseOrder: PureXML.XSLT.CaseOrder) -> Int {
let leftLower = left.lowercased()
let rightLower = right.lowercased()
- if leftLower != rightLower { return leftLower < rightLower ? -1 : 1 }
- if left == right { return 0 }
+ if leftLower != rightLower {
+ return leftLower < rightLower ? -1 : 1
+ }
+ if left == right {
+ return 0
+ }
// Equal apart from case: codepoint order puts uppercase first.
let upperFirst = left < right
let leftFirst = caseOrder == .upperFirst ? upperFirst : !upperFirst
@@ -116,7 +120,9 @@ extension PureXML.XSLT.Transformer {
current = candidate.parent
}
return bindings.sorted(by: { $0.key < $1.key }).compactMap { prefix, uri in
- if prefix.isEmpty, uri.isEmpty { return nil }
+ if prefix.isEmpty, uri.isEmpty {
+ return nil
+ }
let name = prefix.isEmpty ? "xmlns" : "xmlns:" + prefix
return PureXML.XSLT.LiteralAttribute(name: PureXML.Model.QualifiedName(name), value: [.literal(uri)])
}
diff --git a/Sources/XSLT/XSLTFunctions.swift b/Sources/XSLT/XSLTFunctions.swift
index a15cba2..60ccfa8 100644
--- a/Sources/XSLT/XSLTFunctions.swift
+++ b/Sources/XSLT/XSLTFunctions.swift
@@ -37,7 +37,9 @@ extension PureXML.XSLT {
layout.prefix = String(symbols.minusSign) + layout.prefix
}
}
- if value.isInfinite { return layout.prefix + symbols.infinity + layout.suffix }
+ if value.isInfinite {
+ return layout.prefix + symbols.infinity + layout.suffix
+ }
let magnitude = Swift.abs(value) * positive.multiplier
return layout.prefix + render(magnitude, layout, symbols) + layout.suffix
}
@@ -57,7 +59,9 @@ extension PureXML.XSLT {
for character in subpattern {
if character == symbols.percent || character == symbols.perMille {
layout.multiplier = character == symbols.percent ? 100 : 1000
- if phase == 1 { phase = 2 }
+ if phase == 1 {
+ phase = 2
+ }
appendAffix(character, phase, &layout)
continue
}
@@ -70,7 +74,9 @@ extension PureXML.XSLT {
}
continue
}
- if phase == 1 { phase = 2 }
+ if phase == 1 {
+ phase = 2
+ }
appendAffix(character, phase, &layout)
}
layout.groupingSize = sinceGrouping ?? 0
@@ -87,12 +93,18 @@ extension PureXML.XSLT {
_ sinceGrouping: inout Int?,
) {
if character == symbols.groupingSeparator {
- if !inFraction { sinceGrouping = 0 }
+ if !inFraction {
+ sinceGrouping = 0
+ }
} else if inFraction {
layout.maxFraction += 1
- if character == symbols.zeroDigit { layout.minFraction += 1 }
+ if character == symbols.zeroDigit {
+ layout.minFraction += 1
+ }
} else {
- if character == symbols.zeroDigit { layout.minInteger += 1 }
+ if character == symbols.zeroDigit {
+ layout.minInteger += 1
+ }
sinceGrouping = sinceGrouping.map { $0 + 1 }
}
}
@@ -126,7 +138,9 @@ extension PureXML.XSLT {
paddedInteger = "0" + paddedInteger
}
var integerText = digitsToSymbols(paddedInteger, symbols)
- if layout.groupingSize > 0 { integerText = group(integerText, symbols.groupingSeparator, layout.groupingSize) }
+ if layout.groupingSize > 0 {
+ integerText = group(integerText, symbols.groupingSeparator, layout.groupingSize)
+ }
var fractionDigits = allFractionDigits
while fractionDigits.count > layout.minFraction, fractionDigits.hasSuffix("0") {
@@ -174,7 +188,9 @@ extension PureXML.XSLT {
private static func group(_ digits: String, _ separator: Character, _ size: Int) -> String {
var result = ""
for (offset, character) in digits.reversed().enumerated() {
- if offset > 0, offset.isMultiple(of: size) { result.append(separator) }
+ if offset > 0, offset.isMultiple(of: size) {
+ result.append(separator)
+ }
result.append(character)
}
return String(result.reversed())
@@ -285,7 +301,9 @@ extension PureXML.XSLT {
// The loaded document's base URI is the (already base-resolved)
// reference it came from, so a relative reference taken from one
// of its nodes resolves against it (XSLT 1.0 12.1).
- if let tree = cache.trees[path] { recordBaseURI(tree, path, cache) }
+ if let tree = cache.trees[path] {
+ recordBaseURI(tree, path, cache)
+ }
}
guard let tree = cache.trees[path], let parsed = cache.sources[path] else { return [] }
guard let fragment, !fragment.isEmpty else { return [.tree(tree)] }
@@ -384,10 +402,18 @@ extension PureXML.Emitting.Options {
// leading spaces (indent-amount 0), the conformance golds' form.
copy.indent = ""
}
- if let omit = output.omitXMLDeclaration { copy.includeXMLDeclaration = !omit }
- if let encoding = output.encoding { copy.encodingName = encoding }
- if let version = output.version { copy.xmlVersion = version }
- if let standalone = output.standalone { copy.standalone = standalone }
+ if let omit = output.omitXMLDeclaration {
+ copy.includeXMLDeclaration = !omit
+ }
+ if let encoding = output.encoding {
+ copy.encodingName = encoding
+ }
+ if let version = output.version {
+ copy.xmlVersion = version
+ }
+ if let standalone = output.standalone {
+ copy.standalone = standalone
+ }
return copy
}
}
diff --git a/Sources/XSLT/XSLTGlobals.swift b/Sources/XSLT/XSLTGlobals.swift
index a4e2638..a0f04eb 100644
--- a/Sources/XSLT/XSLTGlobals.swift
+++ b/Sources/XSLT/XSLTGlobals.swift
@@ -23,7 +23,9 @@ extension PureXML.XSLT.Transformer {
}
// A caller-supplied value overrides an xsl:param default and is fixed.
for global in globals where stylesheet.parameterNames.contains(global.name) {
- if let supplied = parameters[global.name] { variables[global.name] = .string(supplied) }
+ if let supplied = parameters[global.name] {
+ variables[global.name] = .string(supplied)
+ }
}
for _ in 0 ..< max(1, globals.count) {
var changed = false
@@ -35,7 +37,9 @@ extension PureXML.XSLT.Transformer {
changed = true
}
}
- if !changed { break }
+ if !changed {
+ break
+ }
}
return variables
}
diff --git a/Sources/XSLT/XSLTInstantiation.swift b/Sources/XSLT/XSLTInstantiation.swift
index 25af4ee..6aa3ee0 100644
--- a/Sources/XSLT/XSLTInstantiation.swift
+++ b/Sources/XSLT/XSLTInstantiation.swift
@@ -114,7 +114,9 @@ final class XSLTDriver {
private func drive() {
while let work = stack.popLast() {
- if transformer.termination.message != nil || transformer.recursionGuard.exceeded { continue }
+ if transformer.termination.message != nil || transformer.recursionGuard.exceeded {
+ continue
+ }
switch work {
case let .run(body, cursor, context, sink):
stepRun(body, cursor, context, sink)
diff --git a/Sources/XSLT/XSLTInstantiationElements.swift b/Sources/XSLT/XSLTInstantiationElements.swift
index 082d801..7ef0819 100644
--- a/Sources/XSLT/XSLTInstantiationElements.swift
+++ b/Sources/XSLT/XSLTInstantiationElements.swift
@@ -202,14 +202,22 @@ extension XSLTDriver {
case let .attribute(attribute):
// Ignored once content has been added (the recovery for an
// attribute created after children).
- if children.isEmpty { attributes.append(attribute) }
+ if children.isEmpty {
+ attributes.append(attribute)
+ }
case let .node(node):
children.append(node)
}
}
into.items.append(.node(.element(.init(name: name, attributes: Host.deduplicated(attributes), children: children))))
case .filteredElement:
- into.items.append(contentsOf: child.items.filter { if case .attribute = $0 { false } else { true } })
+ into.items.append(contentsOf: child.items.filter {
+ if case .attribute = $0 {
+ false
+ } else {
+ true
+ }
+ })
case .comment:
into.items.append(.node(.comment(transformer.escapedTextValue(of: child.items))))
case let .processingInstruction(target):
diff --git a/Sources/XSLT/XSLTInstantiationSteps.swift b/Sources/XSLT/XSLTInstantiationSteps.swift
index daa60ba..a6dc975 100644
--- a/Sources/XSLT/XSLTInstantiationSteps.swift
+++ b/Sources/XSLT/XSLTInstantiationSteps.swift
@@ -23,9 +23,15 @@ extension XSLTDriver {
}
// The remainder of the body runs after this instruction's items.
stack.append(.run(body, cursor + 1, context, sink))
- if runSimple(instruction, context, sink) { return }
- if runControl(instruction, context, sink) { return }
- if runTemplate(instruction, context, sink) { return }
+ if runSimple(instruction, context, sink) {
+ return
+ }
+ if runControl(instruction, context, sink) {
+ return
+ }
+ if runTemplate(instruction, context, sink) {
+ return
+ }
runConstruct(instruction, context, sink)
}
@@ -53,7 +59,9 @@ extension XSLTDriver {
private func runControl(_ instruction: Instruction, _ context: XSLTContext, _ sink: Sink) -> Bool {
switch instruction {
case let .ifInstruction(test, thenBody):
- if transformer.boolean(test, context) { stack.append(.run(thenBody, 0, context, sink)) }
+ if transformer.boolean(test, context) {
+ stack.append(.run(thenBody, 0, context, sink))
+ }
case let .choose(whens, otherwise):
let branch = whens.first { transformer.boolean($0.test, context) }?.body ?? otherwise
stack.append(.run(branch, 0, context, sink))
diff --git a/Sources/XSLT/XSLTLookups.swift b/Sources/XSLT/XSLTLookups.swift
index 394fcd7..538a7e2 100644
--- a/Sources/XSLT/XSLTLookups.swift
+++ b/Sources/XSLT/XSLTLookups.swift
@@ -121,7 +121,9 @@ extension PureXML.XSLT.Library {
) {
if node.kind == .element, let name = node.name?.description, let idNames = declared[name] {
for attribute in node.attributes where idNames.contains(attribute.name.description) {
- if index[attribute.value] == nil { index[attribute.value] = node }
+ if index[attribute.value] == nil {
+ index[attribute.value] = node
+ }
}
}
for child in node.children {
diff --git a/Sources/XSLT/XSLTMatchCache.swift b/Sources/XSLT/XSLTMatchCache.swift
index b223cfa..d0a5dde 100644
--- a/Sources/XSLT/XSLTMatchCache.swift
+++ b/Sources/XSLT/XSLTMatchCache.swift
@@ -50,7 +50,9 @@ extension PureXML.XSLT {
var key = pattern + "\u{1}" + namespaces.sorted(by: { $0.key < $1.key }).map { $0.key + "=" + $0.value }.joined(separator: ";")
key += "\u{1}\(UInt(bitPattern: ObjectIdentifier(root).hashValue))"
- if let cached = matched[key] { return cached }
+ if let cached = matched[key] {
+ return cached
+ }
var result = PatternMatches()
for branch in pattern.split(separator: "|") {
let trimmed = branch.trimmingXMLWhitespace()
diff --git a/Sources/XSLT/XSLTNumbering.swift b/Sources/XSLT/XSLTNumbering.swift
index e822e0f..bb4b91f 100644
--- a/Sources/XSLT/XSLTNumbering.swift
+++ b/Sources/XSLT/XSLTNumbering.swift
@@ -16,7 +16,9 @@ enum XSLTNumbering {
matches: (Tree, String) -> Bool,
) -> [Int] {
func matchesCount(_ candidate: Tree) -> Bool {
- if let count { return matches(candidate, count) }
+ if let count {
+ return matches(candidate, count)
+ }
return candidate.kind == node.kind && candidate.name?.description == node.name?.description
}
func matchesFrom(_ candidate: Tree) -> Bool {
@@ -44,8 +46,12 @@ enum XSLTNumbering {
case "any":
var total = 0
walkPreceding(node) { candidate in
- if matchesFrom(candidate) { return false }
- if matchesCount(candidate) { total += 1 }
+ if matchesFrom(candidate) {
+ return false
+ }
+ if matchesCount(candidate) {
+ total += 1
+ }
return true
}
return [total]
@@ -57,7 +63,9 @@ enum XSLTNumbering {
if matchesCount(candidate) {
return [position(of: candidate)]
}
- if matchesFrom(candidate) { break }
+ if matchesFrom(candidate) {
+ break
+ }
current = candidate.parent
}
return []
@@ -80,8 +88,12 @@ enum XSLTNumbering {
}
var position = 1
for sibling in parent.children {
- if sibling === node { break }
- if matchesCount(sibling) { position += 1 }
+ if sibling === node {
+ break
+ }
+ if matchesCount(sibling) {
+ position += 1
+ }
}
return position
}
@@ -114,7 +126,9 @@ enum XSLTNumbering {
private static func walkPreceding(_ node: Tree, _ visit: (Tree) -> Bool) {
var current: Tree? = node
while let candidate = current {
- if !visit(candidate) { return }
+ if !visit(candidate) {
+ return
+ }
if let parent = candidate.parent {
// The preceding siblings, nearest first. Find the candidate's
// index in the child list once, then walk the children before it
@@ -154,8 +168,12 @@ enum XSLTNumbering {
}
let token = offset < pieces.tokens.count ? pieces.tokens[offset] : lastToken
result += render(number, token, grouping)
- if offset < pieces.tokens.count { lastToken = pieces.tokens[offset] }
- if offset > 0, offset - 1 < pieces.separators.count { lastSeparator = pieces.separators[offset - 1] }
+ if offset < pieces.tokens.count {
+ lastToken = pieces.tokens[offset]
+ }
+ if offset > 0, offset - 1 < pieces.separators.count {
+ lastSeparator = pieces.separators[offset - 1]
+ }
}
return result + pieces.suffix
}
@@ -213,7 +231,9 @@ enum XSLTNumbering {
default:
var digits = String(number)
let width = token.count
- if digits.count < width { digits = String(repeating: "0", count: width - digits.count) + digits }
+ if digits.count < width {
+ digits = String(repeating: "0", count: width - digits.count) + digits
+ }
return grouped(digits, grouping)
}
}
diff --git a/Sources/XSLT/XSLTParser.swift b/Sources/XSLT/XSLTParser.swift
index 3599599..8038c41 100644
--- a/Sources/XSLT/XSLTParser.swift
+++ b/Sources/XSLT/XSLTParser.swift
@@ -233,12 +233,18 @@ extension PureXML.XSLT {
/// before the node test is classified (`attribute::node()` is -0.5, like
/// `node()`, not 0).
static func defaultPriority(_ pattern: String) -> Double {
- if pattern.contains("/") || pattern.contains("[") { return 0.5 }
+ if pattern.contains("/") || pattern.contains("[") {
+ return 0.5
+ }
let test = pattern.hasPrefix("attribute::") ? String(pattern.dropFirst(11))
: pattern.hasPrefix("child::") ? String(pattern.dropFirst(7))
: pattern
- if ["*", "@*", "node()", "text()", "comment()", "processing-instruction()"].contains(test) { return -0.5 }
- if test.hasSuffix(":*") { return -0.25 }
+ if ["*", "@*", "node()", "text()", "comment()", "processing-instruction()"].contains(test) {
+ return -0.5
+ }
+ if test.hasSuffix(":*") {
+ return -0.25
+ }
return 0
}
@@ -254,7 +260,9 @@ extension PureXML.XSLT {
var result: [Instruction] = []
var run = ""
func flush() {
- if !run.isEmpty, preserve || run.unicodeScalars.contains(where: { !PureXML.Parsing.XMLCharacter.isWhitespace($0) }) { result.append(.literalText(run)) }
+ if !run.isEmpty, preserve || run.unicodeScalars.contains(where: { !PureXML.Parsing.XMLCharacter.isWhitespace($0) }) {
+ result.append(.literalText(run))
+ }
run = ""
}
for child in node.children {
@@ -262,7 +270,9 @@ extension PureXML.XSLT {
run += child.value
} else {
flush()
- if let instruction = instruction(child) { result.append(instruction) }
+ if let instruction = instruction(child) {
+ result.append(instruction)
+ }
}
}
flush()
@@ -282,7 +292,9 @@ extension PureXML.XSLT {
}
private static func xslInstruction(_ node: XSLTTree) -> Instruction? {
- if let known = simpleInstruction(node) ?? structuralInstruction(node) { return known }
+ if let known = simpleInstruction(node) ?? structuralInstruction(node) {
+ return known
+ }
// An unrecognized XSLT element instantiates its xsl:fallback children
// (forwards-compatible processing); with none, it is dropped.
let fallback = XSLTNode.children(node, named: "fallback").flatMap(body)
diff --git a/Sources/XSLT/XSLTParserDeclarations.swift b/Sources/XSLT/XSLTParserDeclarations.swift
index bac4632..f4b4ef6 100644
--- a/Sources/XSLT/XSLTParserDeclarations.swift
+++ b/Sources/XSLT/XSLTParserDeclarations.swift
@@ -19,7 +19,9 @@ extension PureXML.XSLT.XSLTParser {
for attribute in element.attributes {
let isDefault = prefix == "#default" && attribute.name.prefix == nil && attribute.name.localName == "xmlns"
let isPrefixed = attribute.name.prefix == "xmlns" && attribute.name.localName == prefix
- if isDefault || isPrefixed { return attribute.value }
+ if isDefault || isPrefixed {
+ return attribute.value
+ }
}
current = element.parent
}
@@ -95,7 +97,9 @@ extension PureXML.XSLT.XSLTParser {
static func decimalFormat(_ node: XSLTTree) -> PureXML.XSLT.DecimalFormat {
var format = PureXML.XSLT.DecimalFormat()
func char(_ name: String, _ keyPath: WritableKeyPath) {
- if let value = XSLTNode.attribute(node, name)?.first { format[keyPath: keyPath] = value }
+ if let value = XSLTNode.attribute(node, name)?.first {
+ format[keyPath: keyPath] = value
+ }
}
char("decimal-separator", \.decimalSeparator)
char("grouping-separator", \.groupingSeparator)
@@ -105,8 +109,12 @@ extension PureXML.XSLT.XSLTParser {
char("digit", \.digit)
char("pattern-separator", \.patternSeparator)
char("minus-sign", \.minusSign)
- if let infinity = XSLTNode.attribute(node, "infinity") { format.infinity = infinity }
- if let notANumber = XSLTNode.attribute(node, "NaN") { format.notANumber = notANumber }
+ if let infinity = XSLTNode.attribute(node, "infinity") {
+ format.infinity = infinity
+ }
+ if let notANumber = XSLTNode.attribute(node, "NaN") {
+ format.notANumber = notANumber
+ }
return format
}
}
diff --git a/Sources/XSLT/XSLTParserEntry.swift b/Sources/XSLT/XSLTParserEntry.swift
index 1e45911..bf5eb1d 100644
--- a/Sources/XSLT/XSLTParserEntry.swift
+++ b/Sources/XSLT/XSLTParserEntry.swift
@@ -99,7 +99,9 @@ extension PureXML.XSLT.XSLTParser {
guard let head = needle.first else { return true }
var search = text[...]
while let start = search.firstIndex(of: head) {
- if search[start...].hasPrefix(needle) { return true }
+ if search[start...].hasPrefix(needle) {
+ return true
+ }
search = search[search.index(after: start)...]
}
return false
diff --git a/Sources/XSLT/XSLTParserStructural.swift b/Sources/XSLT/XSLTParserStructural.swift
index 23b4511..8358086 100644
--- a/Sources/XSLT/XSLTParserStructural.swift
+++ b/Sources/XSLT/XSLTParserStructural.swift
@@ -69,7 +69,9 @@ extension PureXML.XSLT.XSLTParser {
var excludedURIs: Set = [XSLTNode.namespace]
for prefix in excludedPrefixTokens(node) {
let key = prefix == "#default" ? "" : prefix
- if let uri = bindings[key] { excludedURIs.insert(uri) }
+ if let uri = bindings[key] {
+ excludedURIs.insert(uri)
+ }
}
return bindings.filter { !excludedURIs.contains($0.value) && !($0.key.isEmpty && $0.value.isEmpty) }
}
diff --git a/Sources/XSLT/XSLTSorting.swift b/Sources/XSLT/XSLTSorting.swift
index 2a15f93..108bb95 100644
--- a/Sources/XSLT/XSLTSorting.swift
+++ b/Sources/XSLT/XSLTSorting.swift
@@ -38,7 +38,9 @@ extension PureXML.XSLT.Transformer {
} else {
order = Self.compareKeys(lhs.keys[index], rhs.keys[index], sort)
}
- if order != 0 { return order < 0 }
+ if order != 0 {
+ return order < 0
+ }
}
return lhs.offset < rhs.offset
}.map(\.node)
@@ -76,7 +78,9 @@ extension PureXML.XSLT.Transformer {
}
let leftSpaces = spacePositions(left)
let rightSpaces = spacePositions(right)
- if leftSpaces == rightSpaces { return 0 }
+ if leftSpaces == rightSpaces {
+ return 0
+ }
for (leftIndex, rightIndex) in zip(leftSpaces, rightSpaces) where leftIndex != rightIndex {
return leftIndex > rightIndex ? -1 : 1
}
diff --git a/Sources/XSLT/XSLTTransform.swift b/Sources/XSLT/XSLTTransform.swift
index 73633b8..4d88747 100644
--- a/Sources/XSLT/XSLTTransform.swift
+++ b/Sources/XSLT/XSLTTransform.swift
@@ -38,8 +38,12 @@ public extension PureXML.XSLT {
maxTemplateDepth: maxTemplateDepth,
)
let result = transformer.run()
- if transformer.recursionLimitExceeded { throw XSLTError.recursionLimitExceeded(maxTemplateDepth) }
- if let message = transformer.terminationMessage { throw XSLTError.terminated(message) }
+ if transformer.recursionLimitExceeded {
+ throw XSLTError.recursionLimitExceeded(maxTemplateDepth)
+ }
+ if let message = transformer.terminationMessage {
+ throw XSLTError.terminated(message)
+ }
let method = sheet.output.method ?? defaultMethod(for: result)
if method == "text" {
// Text output is never escaped, so raw markers are only removed,
@@ -133,7 +137,9 @@ public extension PureXML.XSLT {
let wrap = names.contains(element.name.localName) || names.contains(element.name.description)
let children = wrap
? rebuiltChildren.flatMap { child -> [PureXML.Model.Node] in
- if case let .text(value) = child { return cdataNodes(value) }
+ if case let .text(value) = child {
+ return cdataNodes(value)
+ }
return [child]
}
: rebuiltChildren
@@ -174,7 +180,9 @@ public extension PureXML.XSLT {
return first ..< end
}
start = haystack.index(after: first)
- if start >= haystack.endIndex { break }
+ if start >= haystack.endIndex {
+ break
+ }
}
return nil
}
diff --git a/Sources/XSLT/XSLTTransformer.swift b/Sources/XSLT/XSLTTransformer.swift
index 99c92d0..96591fe 100644
--- a/Sources/XSLT/XSLTTransformer.swift
+++ b/Sources/XSLT/XSLTTransformer.swift
@@ -74,7 +74,9 @@ extension PureXML.XSLT {
current: .tree(root),
keys: { documentRoot in
let identity = ObjectIdentifier(documentRoot)
- if let cached = caches.indexes[identity] { return cached }
+ if let cached = caches.indexes[identity] {
+ return cached
+ }
let built = PureXML.XSLT.Library.buildKeyIndex(stylesheet: stylesheet, root: documentRoot)
caches.indexes[identity] = built
return built
@@ -91,7 +93,9 @@ extension PureXML.XSLT {
/// per document: the source and each document() load have their own).
private func keyIndex(for documentRoot: PureXML.Model.TreeNode) -> PureXML.XSLT.KeyIndex {
let identity = ObjectIdentifier(documentRoot)
- if let cached = keyIndexes.indexes[identity] { return cached }
+ if let cached = keyIndexes.indexes[identity] {
+ return cached
+ }
let built = PureXML.XSLT.Library.buildKeyIndex(stylesheet: stylesheet, root: documentRoot)
keyIndexes.indexes[identity] = built
return built
@@ -145,10 +149,14 @@ extension PureXML.XSLT {
}
func variableValue(_ select: String?, _ body: [Instruction], _ context: XSLTContext) -> PureXML.XPath.Value {
- if let select { return value(select, context) ?? .string("") }
+ if let select {
+ return value(select, context) ?? .string("")
+ }
// XSLT 1.0 11.2: a variable with no select and empty content is the
// empty string, not an RTF (so boolean() is false); only content is.
- if body.isEmpty { return .string("") }
+ if body.isEmpty {
+ return .string("")
+ }
// A body variable is a result-tree fragment: a queryable document node.
let children = instantiate(body, context).compactMap(Self.nodeOf).map(PureXML.Model.TreeNode.init)
return .nodeSet([.tree(PureXML.Model.TreeNode.document(children: children))])
@@ -203,13 +211,17 @@ extension PureXML.XSLT {
}
fileprivate static func nodeOf(_ item: ResultItem) -> PureXML.Model.Node? {
- if case let .node(node) = item { return node }
+ if case let .node(node) = item {
+ return node
+ }
return nil
}
static func text(of items: [ResultItem]) -> String {
items.reduce(into: "") { result, item in
- if case let .node(node) = item { result += Self.stringValue(node) }
+ if case let .node(node) = item {
+ result += Self.stringValue(node)
+ }
}
}
@@ -244,7 +256,9 @@ extension PureXML.XSLT.Transformer {
for definition in stylesheet.attributeSets[name] ?? [] {
result += attributeSetAttributes(definition.use, context, visiting: visiting.union([name]))
for item in instantiate(definition.attributes, context) {
- if case let .attribute(attribute) = item { result.append(attribute) }
+ if case let .attribute(attribute) = item {
+ result.append(attribute)
+ }
}
}
}
diff --git a/Sources/XSLT/XSLTWhitespace.swift b/Sources/XSLT/XSLTWhitespace.swift
index 1d67649..d765556 100644
--- a/Sources/XSLT/XSLTWhitespace.swift
+++ b/Sources/XSLT/XSLTWhitespace.swift
@@ -38,8 +38,12 @@ extension PureXML.XSLT {
let expanded = uri.isEmpty ? local : "{\(uri)}\(local)"
let namespaceWildcard = uri.isEmpty ? nil : "{\(uri)}*"
for test in [expanded, namespaceWildcard, "*"].compactMap(\.self) {
- if preserveSet.contains(test) { return false }
- if stripSet.contains(test) { return true }
+ if preserveSet.contains(test) {
+ return false
+ }
+ if stripSet.contains(test) {
+ return true
+ }
}
return false
}
diff --git a/Tests/AttributeDocumentOrderTests.swift b/Tests/AttributeDocumentOrderTests.swift
index 6be7061..09231f5 100644
--- a/Tests/AttributeDocumentOrderTests.swift
+++ b/Tests/AttributeDocumentOrderTests.swift
@@ -11,7 +11,9 @@ import Testing
struct AttributeDocumentOrderTests {
private func values(_ path: String, _ xml: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(xml)).compactMap {
- if case let .attribute(attribute) = $0 { return attribute.value }
+ if case let .attribute(attribute) = $0 {
+ return attribute.value
+ }
return nil
}
}
diff --git a/Tests/ByteFastPathTests.swift b/Tests/ByteFastPathTests.swift
index cf09d77..ad2a8b0 100644
--- a/Tests/ByteFastPathTests.swift
+++ b/Tests/ByteFastPathTests.swift
@@ -150,7 +150,9 @@ private extension PureXML.Model.Node {
var firstElement: PureXML.Model.Element? {
guard case let .document(children) = self else { return element }
for child in children {
- if case let .element(element) = child { return element }
+ if case let .element(element) = child {
+ return element
+ }
}
return nil
}
diff --git a/Tests/ContentModelOracle.swift b/Tests/ContentModelOracle.swift
index 4f01ec7..d480fd3 100644
--- a/Tests/ContentModelOracle.swift
+++ b/Tests/ContentModelOracle.swift
@@ -45,7 +45,9 @@ enum ContentModelOracle {
next.formUnion(ends(part, tokens, position))
}
positions = next
- if positions.isEmpty { break }
+ if positions.isEmpty {
+ break
+ }
}
return positions
case let .choice(parts):
@@ -75,10 +77,14 @@ enum ContentModelOracle {
byReps.append(next)
reps += 1
let priorPositions = byReps[0 ..< reps].reduce(into: Set()) { $0.formUnion($1) }
- if next.isSubset(of: priorPositions), reps >= low { break }
+ if next.isSubset(of: priorPositions), reps >= low {
+ break
+ }
}
var out: Set = []
- if low == 0 { out.insert(start) }
+ if low == 0 {
+ out.insert(start)
+ }
for count in low ... cap where count < byReps.count {
out.formUnion(byReps[count])
}
@@ -89,7 +95,9 @@ enum ContentModelOracle {
/// `xs:all` group in any order; a member that is nullable may be skipped.
private static func allEnds(_ members: [ContentModel], _ done: [Bool], _ tokens: [String], _ start: Int) -> Set {
var out: Set = []
- if members.indices.allSatisfy({ done[$0] || nullable(members[$0]) }) { out.insert(start) }
+ if members.indices.allSatisfy({ done[$0] || nullable(members[$0]) }) {
+ out.insert(start)
+ }
for index in members.indices where !done[index] {
for end in ends(members[index], tokens, start) where end > start {
var next = done
diff --git a/Tests/ContentNFAStateBudgetTests.swift b/Tests/ContentNFAStateBudgetTests.swift
index ca5c52f..7024ee3 100644
--- a/Tests/ContentNFAStateBudgetTests.swift
+++ b/Tests/ContentNFAStateBudgetTests.swift
@@ -84,7 +84,9 @@ struct ContentNFAStateBudgetTests {
let follow = nfa.follow(after: names("a", "a"))
let allowedNames = follow.allowed.compactMap { label -> String? in
- if case let .name(name) = label { return name.localName }
+ if case let .name(name) = label {
+ return name.localName
+ }
return nil
}
diff --git a/Tests/DTDSubsetStrictnessTests.swift b/Tests/DTDSubsetStrictnessTests.swift
index 0dd7fd4..179d738 100644
--- a/Tests/DTDSubsetStrictnessTests.swift
+++ b/Tests/DTDSubsetStrictnessTests.swift
@@ -147,7 +147,11 @@ struct DTDSubsetStrictnessTests {
case let .element(root)? = children.first(where: { $0.element != nil })
else { return }
#expect(root.children.allSatisfy { child in
- if case let .text(value) = child { value == "ok" } else { false }
+ if case let .text(value) = child {
+ value == "ok"
+ } else {
+ false
+ }
})
}
}
diff --git a/Tests/DTDTests.swift b/Tests/DTDTests.swift
index 362e4d1..69b2c71 100644
--- a/Tests/DTDTests.swift
+++ b/Tests/DTDTests.swift
@@ -43,7 +43,9 @@ struct DTDTests {
func test_recursiveEntity() {
let xml = "]>&a;"
expectError(xml, limits: dtdAllowed) { error in
- if case .recursiveEntity = error { return true }
+ if case .recursiveEntity = error {
+ return true
+ }
return false
}
}
@@ -57,7 +59,9 @@ struct DTDTests {
+ "]>&l2;"
let limits = PureXML.Parsing.Limits(allowDoctype: true, maxEntityExpansion: 100)
expectError(xml, limits: limits) { error in
- if case .amplificationLimitExceeded = error { return true }
+ if case .amplificationLimitExceeded = error {
+ return true
+ }
return false
}
}
@@ -68,7 +72,9 @@ struct DTDTests {
// rather than reading the file: external resolution never happens.
let xml = "]>&ext;"
expectError(xml, limits: dtdAllowed) { error in
- if case .undefinedEntity = error { return true }
+ if case .undefinedEntity = error {
+ return true
+ }
return false
}
}
@@ -91,7 +97,9 @@ struct DTDTests {
private func rootElement(_ node: PureXML.Model.Node) -> PureXML.Model.Element? {
guard case let .document(children) = node else { return nil }
for child in children {
- if case let .element(element) = child { return element }
+ if case let .element(element) = child {
+ return element
+ }
}
return nil
}
diff --git a/Tests/DeterminismDifferentialTests.swift b/Tests/DeterminismDifferentialTests.swift
index 3d51c1b..4d76067 100644
--- a/Tests/DeterminismDifferentialTests.swift
+++ b/Tests/DeterminismDifferentialTests.swift
@@ -79,7 +79,9 @@ struct DeterminismDifferentialTests {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let output = String(decoding: data, as: UTF8.self)
- if process.terminationStatus == 0 { return .accepts }
+ if process.terminationStatus == 0 {
+ return .accepts
+ }
return output.contains("not determinist") ? .notDeterminist : .otherReject
#endif
}
@@ -98,12 +100,18 @@ struct DeterminismDifferentialTests {
guard let reference = libxml2Verdict(source, xmllint: xmllint) else { continue }
// Only the determinism dimension: skip when either side rejects for an
// unrelated reason (a structural error the other may or may not share).
- if pureXML == .otherReject || reference == .otherReject { continue }
+ if pureXML == .otherReject || reference == .otherReject {
+ continue
+ }
compared += 1
if pureXML == .notDeterminist, reference == .accepts {
- if falsePositives.count < 20 { falsePositives.append(source) }
+ if falsePositives.count < 20 {
+ falsePositives.append(source)
+ }
} else if pureXML == .accepts, reference == .notDeterminist {
- if falseNegatives.count < 20 { falseNegatives.append(source) }
+ if falseNegatives.count < 20 {
+ falseNegatives.append(source)
+ }
}
}
let report = "compared \(compared) schemas; false positives \(falsePositives.count), false negatives \(falseNegatives.count)"
@@ -152,7 +160,9 @@ private struct DeterminismSchemaGenerator {
private mutating func particle(depth: Int) -> String {
// Leaf: an element of a small alphabet, or a wildcard.
if depth == 0 || chance(2) {
- if chance(4) { return "" }
+ if chance(4) {
+ return ""
+ }
return ""
}
// A reference to one of the defined groups (the multiply-referenced overlap).
diff --git a/Tests/EncodingTests.swift b/Tests/EncodingTests.swift
index abb6a53..57a018e 100644
--- a/Tests/EncodingTests.swift
+++ b/Tests/EncodingTests.swift
@@ -296,7 +296,9 @@ struct EncodingTests {
private func rootText(_ node: PureXML.Model.Node) -> String? {
guard case let .document(children) = node else { return nil }
for child in children {
- if case let .element(element) = child { return element.text }
+ if case let .element(element) = child {
+ return element.text
+ }
}
return nil
}
diff --git a/Tests/EntityDecodeBulkTests.swift b/Tests/EntityDecodeBulkTests.swift
index ce66a11..aa73725 100644
--- a/Tests/EntityDecodeBulkTests.swift
+++ b/Tests/EntityDecodeBulkTests.swift
@@ -82,7 +82,13 @@ struct EntityDecodeBulkTests {
Issue.record("no root")
return
}
- let text = root.children.compactMap { if case let .text(value) = $0 { value } else { nil } }.joined()
+ let text = root.children.compactMap {
+ if case let .text(value) = $0 {
+ value
+ } else {
+ nil
+ }
+ }.joined()
#expect(text == "x & y < z > w")
}
}
diff --git a/Tests/FacetValidationDifferentialTests.swift b/Tests/FacetValidationDifferentialTests.swift
index a52cef7..45553a5 100644
--- a/Tests/FacetValidationDifferentialTests.swift
+++ b/Tests/FacetValidationDifferentialTests.swift
@@ -68,7 +68,9 @@ struct FacetValidationDifferentialTests {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let output = String(decoding: data, as: UTF8.self)
- if output.contains("failed to compile") || output.contains("Schemas parser error") { return .schemaInvalid }
+ if output.contains("failed to compile") || output.contains("Schemas parser error") {
+ return .schemaInvalid
+ }
return process.terminationStatus == 0 ? .valid : .invalid
#endif
}
@@ -99,12 +101,18 @@ struct FacetValidationDifferentialTests {
let instance = "\(value)"
let pureValid = ((try? schema.document.validate(instance)) ?? [PureXML.Validation.ValidationError]()).isEmpty
guard let reference = libxml2(schema.file, instance, xmllint: xmllint) else { continue }
- if reference == .schemaInvalid { return } // libxml2 rejects the schema; skip its values
+ if reference == .schemaInvalid {
+ return
+ } // libxml2 rejects the schema; skip its values
tally.compared += 1
let detail = "\(schema.source)\n value: '\(value)' (PureXML \(pureValid ? "valid" : "invalid"), libxml2 \(reference))"
switch compare(pureValid: pureValid, reference: reference) {
- case .falsePositive: if tally.falsePositives.count < 25 { tally.falsePositives.append(detail) }
- case .falseNegative: if tally.falseNegatives.count < 25 { tally.falseNegatives.append(detail) }
+ case .falsePositive: if tally.falsePositives.count < 25 {
+ tally.falsePositives.append(detail)
+ }
+ case .falseNegative: if tally.falseNegatives.count < 25 {
+ tally.falseNegatives.append(detail)
+ }
case .agreeValid: tally.agreeValid += 1
case .agreeInvalid: tally.agreeInvalid += 1
}
@@ -244,9 +252,13 @@ private struct FacetSchemaGenerator {
var chosen: [(name: String, value: String)] = []
var seenName: Set = []
for facet in current.facets where Int.random(in: 0 ..< 2, using: &rng) == 0 {
- if facet.name == "enumeration" || seenName.insert(facet.name).inserted { chosen.append(facet) }
+ if facet.name == "enumeration" || seenName.insert(facet.name).inserted {
+ chosen.append(facet)
+ }
+ }
+ if chosen.isEmpty {
+ chosen = [pick(current.facets)]
}
- if chosen.isEmpty { chosen = [pick(current.facets)] }
let restriction = chosen.map { "" }.joined()
return ""
+ ""
diff --git a/Tests/Fixtures/validation-field-registry.txt b/Tests/Fixtures/validation-field-registry.txt
index a487b68..a4cb5c0 100644
--- a/Tests/Fixtures/validation-field-registry.txt
+++ b/Tests/Fixtures/validation-field-registry.txt
@@ -18,20 +18,20 @@ FIELD parseAdvisories dtdParseAdvisories
IGNORE isEmpty derived summary; no independent constraint
MODEL PureXML.Validation.XSDContext
-FIELD types xsdContentValidity,xsdStreamingShallowValidity
-FIELD constraints xsdIdentityConstraints
-FIELD rootDeclaration xsdContentValidity
-FIELD nillableElements xsdContentValidity
-FIELD elementConstraints xsdContentValidity
-FIELD abstractTypes xsdContentValidity
-FIELD typeBlock xsdContentValidity
-FIELD elementBlock xsdContentValidity
-FIELD typeDerivation xsdContentValidity
+FIELD types xsdTypeReferenceResolves,xsdContentModelStructure,xsdXsiTypeSubstitutionValid,xsdXsiTypeOverrideResolves,xsdStreamingShallowValidity
+FIELD constraints xsdIdentityConstraintXPathsValid,xsdIdentityKeyFieldPresent,xsdIdentityValueUnique,xsdIdentityKeyrefResolves,xsdIdentityFieldCardinality,xsdIdentityFieldMustBeSimple
+FIELD rootDeclaration xsdContentModelStructure
+FIELD nillableElements xsdNilAttributeRequiresNillable
+FIELD elementConstraints xsdNilElementNoFixedConstraint,xsdFixedElementNoChildren,xsdFixedElementValueMatches
+FIELD abstractTypes xsdXsiTypeSubstitutionValid
+FIELD typeBlock xsdXsiTypeSubstitutionValid
+FIELD elementBlock xsdXsiTypeSubstitutionValid
+FIELD typeDerivation xsdXsiTypeSubstitutionValid
MODEL PureXML.Model.Element
-FIELD name dtdContentModel,dtdUndeclaredElement,dtdUndeclaredAttributes,htmlRequiredParent,htmlVoidElementsAreEmpty,xsdContentValidity
+FIELD name dtdContentModel,dtdUndeclaredElement,dtdUndeclaredAttributes,htmlRequiredParent,htmlVoidElementsAreEmpty,xsdContentModelStructure
FIELD attributes uniqueAttributes,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredAttributes,dtdStandaloneAttributes
-FIELD children dtdContentModel,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentValidity
+FIELD children dtdContentModel,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentModelStructure
IGNORE text derived from direct text and CDATA children
MODEL PureXML.Model.Attribute
@@ -39,8 +39,8 @@ FIELD name uniqueAttributes,dtdUndeclaredAttributes,dtdRequiredAttributes,dtdFix
FIELD value dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdIdentifierIntegrity
MODEL PureXML.Model.Node
-CASE document dtdDeclarationValidity,dtdRootElementType,dtdIdentifierIntegrity,htmlUniqueIdentifiers,xsdContentValidity,xsdIdentityConstraints
-CASE element uniqueAttributes,dtdContentModel,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredElement,dtdUndeclaredAttributes,dtdStandaloneAttributes,dtdStandaloneElementWhitespace,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentValidity
+CASE document dtdDeclarationValidity,dtdRootElementType,dtdIdentifierIntegrity,htmlUniqueIdentifiers,xsdContentModelStructure,xsdIdentityConstraintXPathsValid
+CASE element uniqueAttributes,dtdContentModel,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredElement,dtdUndeclaredAttributes,dtdStandaloneAttributes,dtdStandaloneElementWhitespace,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentModelStructure
CASE text dtdContentModel
CASE cdata dtdContentModel
IGNORE comment no DTD or XSD constraints on comments
diff --git a/Tests/FollowingPrecedingAxisTests.swift b/Tests/FollowingPrecedingAxisTests.swift
index abe2149..1733e0b 100644
--- a/Tests/FollowingPrecedingAxisTests.swift
+++ b/Tests/FollowingPrecedingAxisTests.swift
@@ -12,7 +12,9 @@ import Testing
struct FollowingPrecedingAxisTests {
private func names(_ path: String, _ xml: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(xml)).compactMap {
- if case let .node(node) = $0 { return node.element?.name.localName }
+ if case let .node(node) = $0 {
+ return node.element?.name.localName
+ }
return nil
}
}
diff --git a/Tests/HTMLForeignContentTests.swift b/Tests/HTMLForeignContentTests.swift
index 73e0e4d..002c5c0 100644
--- a/Tests/HTMLForeignContentTests.swift
+++ b/Tests/HTMLForeignContentTests.swift
@@ -16,7 +16,9 @@ struct HTMLForeignContentTests {
case let .document(children):
return children.lazy.compactMap { find(name, in: $0) }.first
case let .element(element):
- if element.name.localName == name { return element }
+ if element.name.localName == name {
+ return element
+ }
return element.children.lazy.compactMap { find(name, in: $0) }.first
default:
return nil
diff --git a/Tests/HTMLTokenizerTests.swift b/Tests/HTMLTokenizerTests.swift
index ba6509c..78af6af 100644
--- a/Tests/HTMLTokenizerTests.swift
+++ b/Tests/HTMLTokenizerTests.swift
@@ -6,7 +6,13 @@ struct HTMLTokenizerTests {
/// The concatenated text content of the first parsed element.
private func text(_ html: String) -> String {
guard case let .document(children) = PureXML.HTML.parse(html), case let .element(element) = children.first else { return "" }
- return element.children.compactMap { if case let .text(value) = $0 { value } else { nil } }.joined()
+ return element.children.compactMap {
+ if case let .text(value) = $0 {
+ value
+ } else {
+ nil
+ }
+ }.joined()
}
@Test("A numeric reference to zero, a surrogate, or an out-of-range value yields U+FFFD")
diff --git a/Tests/InstanceValidationDifferentialTests.swift b/Tests/InstanceValidationDifferentialTests.swift
index 2ab2ae5..5cafd40 100644
--- a/Tests/InstanceValidationDifferentialTests.swift
+++ b/Tests/InstanceValidationDifferentialTests.swift
@@ -46,7 +46,9 @@ struct InstanceValidationDifferentialTests {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let output = String(decoding: data, as: UTF8.self)
- if output.contains("failed to compile") || output.contains("Schemas parser error") { return .schemaInvalid }
+ if output.contains("failed to compile") || output.contains("Schemas parser error") {
+ return .schemaInvalid
+ }
return process.terminationStatus == 0 ? .valid : .invalid
#endif
}
@@ -87,14 +89,22 @@ struct InstanceValidationDifferentialTests {
let instance = generator.instance()
let pureValid = ((try? schema.document.validate(instance.xml)) ?? [PureXML.Validation.ValidationError]()).isEmpty
guard let reference = libxml2(schema.file, instance.xml, xmllint: xmllint) else { continue }
- if reference == .schemaInvalid { return } // libxml2 rejects the schema; skip its instances
+ if reference == .schemaInvalid {
+ return
+ } // libxml2 rejects the schema; skip its instances
let truth = ContentModelOracle.matches(schema.model, instance.tokens)
tally.compared += 1
- if (reference == .valid) != truth { tally.libxml2Disagreements += 1 }
+ if (reference == .valid) != truth {
+ tally.libxml2Disagreements += 1
+ }
let detail = "\(schema.source)\n instance: \(instance.xml)\n oracle: \(truth ? "valid" : "invalid"), libxml2: \(reference)"
switch (pureValid, truth) {
- case (false, true): if tally.falsePositives.count < 20 { tally.falsePositives.append(detail) }
- case (true, false): if tally.falseNegatives.count < 20 { tally.falseNegatives.append(detail) }
+ case (false, true): if tally.falsePositives.count < 20 {
+ tally.falsePositives.append(detail)
+ }
+ case (true, false): if tally.falseNegatives.count < 20 {
+ tally.falseNegatives.append(detail)
+ }
case (true, true): tally.agreeValid += 1
case (false, false): tally.agreeInvalid += 1
}
diff --git a/Tests/LineEndingTests.swift b/Tests/LineEndingTests.swift
index 1cd1503..a87b26f 100644
--- a/Tests/LineEndingTests.swift
+++ b/Tests/LineEndingTests.swift
@@ -9,7 +9,9 @@ struct LineEndingTests {
return ""
}
return root.children.reduce(into: "") { result, child in
- if case let .text(text) = child { result += text }
+ if case let .text(text) = child {
+ result += text
+ }
}
}
diff --git a/Tests/NamespaceTests.swift b/Tests/NamespaceTests.swift
index 14a7cd4..f66d1c3 100644
--- a/Tests/NamespaceTests.swift
+++ b/Tests/NamespaceTests.swift
@@ -52,7 +52,9 @@ struct NamespaceTests {
let root = try rootElement("")
#expect(root.name.namespaceURI == "http://1")
let children = root.children.compactMap { node -> PureXML.Model.Element? in
- if case let .element(element) = node { return element }
+ if case let .element(element) = node {
+ return element
+ }
return nil
}
#expect(children.first?.name.namespaceURI == "http://2")
@@ -65,14 +67,18 @@ struct NamespaceTests {
throw Failure.notDocument
}
for child in children {
- if case let .element(element) = child { return element }
+ if case let .element(element) = child {
+ return element
+ }
}
throw Failure.noElement
}
private func firstChildElement(_ element: PureXML.Model.Element) -> PureXML.Model.Element? {
for child in element.children {
- if case let .element(found) = child { return found }
+ if case let .element(found) = child {
+ return found
+ }
}
return nil
}
diff --git a/Tests/OutputEncodingTests.swift b/Tests/OutputEncodingTests.swift
index d4de99d..6e4bd65 100644
--- a/Tests/OutputEncodingTests.swift
+++ b/Tests/OutputEncodingTests.swift
@@ -6,7 +6,9 @@ struct OutputEncodingTests {
private func rootText(_ node: PureXML.Model.Node) -> String? {
guard case let .document(children) = node else { return nil }
for child in children {
- if case let .element(element) = child { return element.text }
+ if case let .element(element) = child {
+ return element.text
+ }
}
return nil
}
diff --git a/Tests/ParsingTests.swift b/Tests/ParsingTests.swift
index 4475922..80cbdfd 100644
--- a/Tests/ParsingTests.swift
+++ b/Tests/ParsingTests.swift
@@ -178,7 +178,9 @@ private extension PureXML.Model.Node {
var firstElement: PureXML.Model.Element? {
guard case let .document(children) = self else { return element }
for child in children {
- if case let .element(element) = child { return element }
+ if case let .element(element) = child {
+ return element
+ }
}
return nil
}
diff --git a/Tests/PushParserTests.swift b/Tests/PushParserTests.swift
index c311765..427cd95 100644
--- a/Tests/PushParserTests.swift
+++ b/Tests/PushParserTests.swift
@@ -91,7 +91,9 @@ struct PushParserTests {
}
var names: [String] = []
for try await event in PureXML.events(feeding: stream) {
- if case let .startElement(name, _) = event { names.append(name.description) }
+ if case let .startElement(name, _) = event {
+ names.append(name.description)
+ }
}
#expect(names == ["doc", "item", "item"])
}
diff --git a/Tests/RelaxNGSpecSuiteTests.swift b/Tests/RelaxNGSpecSuiteTests.swift
index 4ec739b..cb7fcae 100644
--- a/Tests/RelaxNGSpecSuiteTests.swift
+++ b/Tests/RelaxNGSpecSuiteTests.swift
@@ -116,7 +116,13 @@ struct RelaxNGSpecSuiteTests {
for child in testCase.children.compactMap(\.element) {
switch child.name.localName {
case "section":
- section = child.children.compactMap { if case let .text(value) = $0 { value } else { nil } }.joined()
+ section = child.children.compactMap {
+ if case let .text(value) = $0 {
+ value
+ } else {
+ nil
+ }
+ }.joined()
case "resource", "dir":
merge(child, prefix: "", into: &scope)
case "correct", "incorrect":
diff --git a/Tests/SAXTests.swift b/Tests/SAXTests.swift
index 3a6ebc2..5e07831 100644
--- a/Tests/SAXTests.swift
+++ b/Tests/SAXTests.swift
@@ -33,7 +33,9 @@ struct SAXTests {
func test_namespace() throws {
var uri: String?
let handler = PureXML.Parsing.SAXHandler(startElement: { name, _ in
- if name.localName == "a" { uri = name.namespaceURI }
+ if name.localName == "a" {
+ uri = name.namespaceURI
+ }
})
try PureXML.parse("", sax: handler)
#expect(uri == "http://x")
diff --git a/Tests/SchemaDifferentialHarness.swift b/Tests/SchemaDifferentialHarness.swift
index d180998..3484591 100644
--- a/Tests/SchemaDifferentialHarness.swift
+++ b/Tests/SchemaDifferentialHarness.swift
@@ -69,10 +69,18 @@ enum SchemaDifferential {
}
private static func libxml2CompiledSchema(output: String, exitCode: Int32, schemaFile: String) -> Bool {
- if output.contains("WXS schema \(schemaFile) failed to compile") { return false }
- if output.contains("Schemas parser error"), output.contains(schemaFile) { return false }
- if output.contains("Failed to parse the XML resource '\(schemaFile)'") { return false }
- if exitCode == 5 { return false }
+ if output.contains("WXS schema \(schemaFile) failed to compile") {
+ return false
+ }
+ if output.contains("Schemas parser error"), output.contains(schemaFile) {
+ return false
+ }
+ if output.contains("Failed to parse the XML resource '\(schemaFile)'") {
+ return false
+ }
+ if exitCode == 5 {
+ return false
+ }
if exitCode == 1, output.localizedCaseInsensitiveContains("parser error"), output.contains(schemaFile) {
return false
}
@@ -101,7 +109,9 @@ enum SchemaDifferentialXSTS {
found += manifestElements(named: name, under: child)
}
case let .element(element):
- if element.name.localName == name { found.append(element) }
+ if element.name.localName == name {
+ found.append(element)
+ }
for child in element.children {
found += manifestElements(named: name, under: child)
}
@@ -135,7 +145,9 @@ enum SchemaDifferentialXSTS {
var parts = base.split(separator: "/", omittingEmptySubsequences: false).map(String.init)
for segment in href.split(separator: "/") {
if segment == ".." {
- if !parts.isEmpty { parts.removeLast() }
+ if !parts.isEmpty {
+ parts.removeLast()
+ }
} else if segment != "." {
parts.append(String(segment))
}
diff --git a/Tests/SchemaXSTSDiagnosticTests.swift b/Tests/SchemaXSTSDiagnosticTests.swift
index a474551..2e8d36f 100644
--- a/Tests/SchemaXSTSDiagnosticTests.swift
+++ b/Tests/SchemaXSTSDiagnosticTests.swift
@@ -83,7 +83,9 @@ struct SchemaXSTSDiagnosticTests {
found += elements(named: name, under: child)
}
case let .element(element):
- if element.name.localName == name { found.append(element) }
+ if element.name.localName == name {
+ found.append(element)
+ }
for child in element.children {
found += elements(named: name, under: child)
}
@@ -110,7 +112,13 @@ struct SchemaXSTSDiagnosticTests {
private func resolve(_ href: String, against base: String) -> String {
var parts = base.split(separator: "/", omittingEmptySubsequences: false).map(String.init)
for segment in href.split(separator: "/") {
- if segment == ".." { if !parts.isEmpty { parts.removeLast() } } else if segment != "." { parts.append(String(segment)) }
+ if segment == ".." {
+ if !parts.isEmpty {
+ parts.removeLast()
+ }
+ } else if segment != "." {
+ parts.append(String(segment))
+ }
}
return parts.joined(separator: "/")
}
diff --git a/Tests/SchemaXSTSRegressionTests.swift b/Tests/SchemaXSTSRegressionTests.swift
index b322edc..aab525b 100644
--- a/Tests/SchemaXSTSRegressionTests.swift
+++ b/Tests/SchemaXSTSRegressionTests.swift
@@ -97,8 +97,12 @@ struct SchemaXSTSRegressionTests {
let addXSD = try String(contentsOfFile: "\(base)/address.xsd", encoding: .utf8)
let itemXSD = try String(contentsOfFile: "\(base)/itematt.xsd", encoding: .utf8)
let loader: (String) -> String? = { loc in
- if loc.hasSuffix("address.xsd") { return addXSD }
- if loc.hasSuffix("itematt.xsd") { return itemXSD }
+ if loc.hasSuffix("address.xsd") {
+ return addXSD
+ }
+ if loc.hasSuffix("itematt.xsd") {
+ return itemXSD
+ }
return nil
}
let doc = try PureXML.Schema.Document(mainXSD, schemaLoader: loader)
@@ -114,8 +118,12 @@ struct SchemaXSTSRegressionTests {
let addXSD = try String(contentsOfFile: "\(base)/address.xsd", encoding: .utf8)
let itemXSD = try String(contentsOfFile: "\(base)/itematt.xsd", encoding: .utf8)
let loader: (String) -> String? = { loc in
- if loc.hasSuffix("address.xsd") { return addXSD }
- if loc.hasSuffix("itematt.xsd") { return itemXSD }
+ if loc.hasSuffix("address.xsd") {
+ return addXSD
+ }
+ if loc.hasSuffix("itematt.xsd") {
+ return itemXSD
+ }
return nil
}
let doc = try PureXML.Schema.Document(mainXSD, schemaLoader: loader)
diff --git a/Tests/SiblingAxisTests.swift b/Tests/SiblingAxisTests.swift
index ebb71fd..aeafe35 100644
--- a/Tests/SiblingAxisTests.swift
+++ b/Tests/SiblingAxisTests.swift
@@ -13,7 +13,9 @@ struct SiblingAxisTests {
private func names(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.doc)).compactMap {
- if case let .node(node) = $0 { return node.element?.name.localName }
+ if case let .node(node) = $0 {
+ return node.element?.name.localName
+ }
return nil
}
}
diff --git a/Tests/ValidationConfigurationTests.swift b/Tests/ValidationConfigurationTests.swift
index 712ec3d..e4cc65e 100644
--- a/Tests/ValidationConfigurationTests.swift
+++ b/Tests/ValidationConfigurationTests.swift
@@ -64,10 +64,40 @@ struct ValidationConfigurationTests {
func test_xsdInstance() {
let validator = PureXML.Validation.XSD.validator()
#expect(validator.nonReferenceValidationDescriptions == [
- "The document element is valid against its XSD type",
+ "An element carries xsi:nil only when it is declared nillable",
+ "A nilled element has no content",
+ "A nilled element carries no fixed value constraint",
+ "A fixed element has no element children",
+ "A fixed element's content equals its fixed value",
+ "An empty-content element has no element children",
+ "A simple-content element has no element children",
+ "A simple-content element's text is valid against its type",
+ "An element-only or empty-content element carries no text",
+ "A strict wildcard-matched element has a global declaration",
+ "A simple-typed element carries only schema-instance and namespace attributes",
+ "A simple-typed element has no element children",
+ "A simple-typed element's value is valid against its type",
+ "A type reference resolves to a known, non-circular type",
+ "An element's children conform to its content model",
+ "A declared attribute's value is valid against its type",
+ "A declared attribute's value equals its fixed value",
+ "A required attribute is present",
+ "An element carries at most one attribute of type ID",
+ "An attribute is declared or permitted by a wildcard",
+ "A strict wildcard-matched attribute has a global declaration",
+ "A wildcard-matched attribute's value is valid against its type",
+ "A wildcard-matched attribute's value equals its fixed value",
+ "An xsi:type substitution is validly derived and not blocked",
+ "An xsi:type override names a known, validly derived type",
+ "Every xs:ID is unique and every xs:IDREF resolves to one",
])
#expect(validator.referenceValidationDescriptions == [
- "XSD identity constraints hold",
+ "An identity constraint's selector and field XPaths compile",
+ "A key constraint's fields are all present",
+ "A key or unique constraint's values are distinct",
+ "A keyref constraint's value matches a key or unique in scope",
+ "An identity constraint field selects at most one node",
+ "An identity constraint field identifies a node with simple content",
])
}
@@ -125,6 +155,6 @@ struct ValidationConfigurationTests {
func test_builtinRegistry() {
let descriptions = PureXML.Validation.BuiltinValidation.allDescriptions
#expect(descriptions.count == Set(descriptions).count)
- #expect(descriptions.count == 24)
+ #expect(descriptions.count == 55)
}
}
diff --git a/Tests/ValidationFieldCoverageTests.swift b/Tests/ValidationFieldCoverageTests.swift
index 595e778..a09ef0c 100644
--- a/Tests/ValidationFieldCoverageTests.swift
+++ b/Tests/ValidationFieldCoverageTests.swift
@@ -42,9 +42,9 @@ struct ValidationFieldCoverageTests {
"dtdUndeclaredElement", "dtdUndeclaredAttributes", "dtdDeclarationValidity",
"dtdRootElementType", "dtdIdentifierIntegrity", "dtdStandaloneAttributes",
"dtdStandaloneElementWhitespace", "dtdParseAdvisories", "htmlVoidElementsAreEmpty", "htmlRequiredParent",
- "htmlUniqueIdentifiers", "xsdContentValidity", "xsdIdentityConstraints",
+ "htmlUniqueIdentifiers",
"xsdFinalRespected", "xsdRestrictionsAreSubsets", "conformanceMatchesExpected",
- "xsdStreamingShallowValidity",
+ "xsdStreamingShallowValidity", "xsdStreamingIdConsistency",
])
return tokens
}()
@@ -75,7 +75,9 @@ struct ValidationFieldCoverageTests {
var currentModel: String?
for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
let trimmed = line.trimmingCharacters(in: .whitespaces)
- if trimmed.isEmpty || trimmed.hasPrefix("#") { continue }
+ if trimmed.isEmpty || trimmed.hasPrefix("#") {
+ continue
+ }
let parts = trimmed.split(separator: " ", omittingEmptySubsequences: true).map(String.init)
guard let keyword = parts.first else { continue }
applyRegistryLine(keyword, parts: parts, model: ¤tModel, registry: ®istry)
diff --git a/Tests/ValidationFrameworkTests.swift b/Tests/ValidationFrameworkTests.swift
index 50e8d0c..0ef1fb6 100644
--- a/Tests/ValidationFrameworkTests.swift
+++ b/Tests/ValidationFrameworkTests.swift
@@ -175,6 +175,28 @@ struct ValidationFrameworkTests {
#expect(rule(missing).count == 1)
}
+ @Test("unwrapAndLookup skips a nil reference, errors on an unresolved one, else validates the resolved value")
+ func test_unwrapAndLookup() {
+ struct Item: PureXML.Validation.Validatable { var ref: String? }
+ struct Target: PureXML.Validation.Validatable { var key: String }
+ struct Store { var targets: [String: Target] = [:] }
+ typealias Context = PureXML.Validation.ValidationContext-
+ let rule: (Context) -> [PureXML.Validation.ValidationError] = unwrapAndLookup(
+ \Store.targets,
+ name: \Item.ref,
+ missing: { "missing \($0)" },
+ into: PureXML.Validation.Validation(description: "present", check: { _ in true }),
+ )
+ let store = Store(targets: ["a": Target(key: "a")])
+ let onNil = Context(document: store, subject: Item(ref: nil), codingPath: [])
+ #expect(rule(onNil).isEmpty)
+ let onMissing = Context(document: store, subject: Item(ref: "z"), codingPath: [])
+ #expect(rule(onMissing).count == 1)
+ #expect(rule(onMissing).first?.reason == "missing z")
+ let onResolved = Context(document: store, subject: Item(ref: "a"), codingPath: [])
+ #expect(rule(onResolved).isEmpty)
+ }
+
@Test("Parameter-pack KeyPath validating mixes void-document builtins")
func test_keyPathParameterPack() {
let validator = PureXML.Validation.Validator.blank
diff --git a/Tests/ValidationRuleRegistryTests.swift b/Tests/ValidationRuleRegistryTests.swift
index 05acaa9..4f9079f 100644
--- a/Tests/ValidationRuleRegistryTests.swift
+++ b/Tests/ValidationRuleRegistryTests.swift
@@ -28,19 +28,50 @@ struct ValidationRuleRegistryTests {
"htmlVoidElementsAreEmpty": ("test_htmlVoidElementsAreEmpty", "test_htmlVoidElementsAreEmpty_succeeds"),
"htmlRequiredParent": ("test_htmlRequiredParent", "test_htmlRequiredParent_succeeds"),
"htmlUniqueIdentifiers": ("test_htmlUniqueIdentifiers", "test_htmlUniqueIdentifiers_succeeds"),
- "xsdContentValidity": ("test_xsdContentValidity", "test_xsdContentValidity_succeeds"),
- "xsdIdentityConstraints": ("test_xsdIdentityConstraints", "test_xsdIdentityConstraints_succeeds"),
+ "xsdNilAttributeRequiresNillable": ("test_nilAttributeRequiresNillable", "test_nilAttributeRequiresNillable_succeeds"),
+ "xsdNilElementMustBeEmpty": ("test_nilElementMustBeEmpty", "test_nilElementMustBeEmpty_succeeds"),
+ "xsdNilElementNoFixedConstraint": ("test_nilElementNoFixedConstraint", "test_nilElementNoFixedConstraint_succeeds"),
+ "xsdFixedElementNoChildren": ("test_fixedElementNoChildren", "test_fixedElementNoChildren_succeeds"),
+ "xsdFixedElementValueMatches": ("test_fixedElementValueMatches", "test_fixedElementValueMatches_succeeds"),
+ "xsdEmptyContentForbidsChildren": ("test_emptyContentForbidsChildren", "test_emptyContentForbidsChildren_succeeds"),
+ "xsdSimpleContentForbidsChildren": ("test_simpleContentForbidsChildren", "test_simpleContentForbidsChildren_succeeds"),
+ "xsdSimpleContentTypeValid": ("test_simpleContentTypeValid", "test_simpleContentTypeValid_succeeds"),
+ "xsdTextForbidden": ("test_textForbidden", "test_textForbidden_succeeds"),
+ "xsdWildcardElementUndeclared": ("test_wildcardElementUndeclared", "test_wildcardElementUndeclared_succeeds"),
+ "xsdSimpleTypeDisallowedAttribute": ("test_simpleTypeDisallowedAttribute", "test_simpleTypeDisallowedAttribute_succeeds"),
+ "xsdSimpleTypeForbidsChildren": ("test_simpleTypeForbidsChildren", "test_simpleTypeForbidsChildren_succeeds"),
+ "xsdSimpleTypeValueValid": ("test_simpleTypeValueValid", "test_simpleTypeValueValid_succeeds"),
+ "xsdTypeReferenceResolves": ("test_typeReferenceResolves", "test_typeReferenceResolves_succeeds"),
+ "xsdContentModelStructure": ("test_contentModelStructure", "test_contentModelStructure_succeeds"),
+ "xsdAttributeTypeValid": ("test_attributeTypeValid", "test_attributeTypeValid_succeeds"),
+ "xsdAttributeFixedMatches": ("test_attributeFixedMatches", "test_attributeFixedMatches_succeeds"),
+ "xsdAttributeRequiredPresent": ("test_attributeRequiredPresent", "test_attributeRequiredPresent_succeeds"),
+ "xsdAttributeSingleID": ("test_attributeSingleID", "test_attributeSingleID_succeeds"),
+ "xsdAttributeDeclaredOrPermitted": ("test_attributeDeclaredOrPermitted", "test_attributeDeclaredOrPermitted_succeeds"),
+ "xsdWildcardAttributeUndeclared": ("test_wildcardAttributeUndeclared", "test_wildcardAttributeUndeclared_succeeds"),
+ "xsdWildcardAttributeTypeValid": ("test_wildcardAttributeTypeValid", "test_wildcardAttributeTypeValid_succeeds"),
+ "xsdWildcardAttributeFixedMatches": ("test_wildcardAttributeFixedMatches", "test_wildcardAttributeFixedMatches_succeeds"),
+ "xsdXsiTypeSubstitutionValid": ("test_xsiTypeSubstitutionValid", "test_xsiTypeSubstitutionValid_succeeds"),
+ "xsdXsiTypeOverrideResolves": ("test_xsiTypeOverrideResolves", "test_xsiTypeOverrideResolves_succeeds"),
+ "xsdIdIdrefConsistent": ("test_idIdrefConsistent", "test_idIdrefConsistent_succeeds"),
+ "xsdIdentityConstraintXPathsValid": ("test_identityConstraintXPathsValid", "test_identityConstraintXPathsValid_succeeds"),
+ "xsdIdentityKeyFieldPresent": ("test_identityKeyFieldPresent", "test_identityKeyFieldPresent_succeeds"),
+ "xsdIdentityValueUnique": ("test_identityValueUnique", "test_identityValueUnique_succeeds"),
+ "xsdIdentityKeyrefResolves": ("test_identityKeyrefResolves", "test_identityKeyrefResolves_succeeds"),
+ "xsdIdentityFieldCardinality": ("test_identityFieldCardinality", "test_identityFieldCardinality_succeeds"),
+ "xsdIdentityFieldMustBeSimple": ("test_identityFieldMustBeSimple", "test_identityFieldMustBeSimple_succeeds"),
"xsdFinalRespected": ("test_xsdFinalRespected", "test_xsdFinalRespected_succeeds"),
"xsdRestrictionsAreSubsets": ("test_xsdRestrictionsAreSubsets", "test_xsdRestrictionsAreSubsets_succeeds"),
"conformanceMatchesExpected": ("test_conformanceMatchesExpected", "test_conformanceMatchesExpected_succeeds"),
"xsdStreamingShallowValidity": ("test_xsdStreamingShallowValidity", "test_xsdStreamingShallowValidity_succeeds"),
+ "xsdStreamingIdConsistency": ("test_xsdStreamingIdConsistency", "test_xsdStreamingIdConsistency_succeeds"),
]
@Test("Every builtin rule id has a fail and succeed test registered")
func test_allRulesPaired() {
let ids = PureXML.Validation.BuiltinValidation.allRuleIDs
#expect(Set(Self.pairs.keys) == Set(ids), "pairs keys must match allRuleIDs")
- #expect(ids.count == 24)
+ #expect(ids.count == 55)
}
@Test("Builtin descriptions stay unique")
diff --git a/Tests/ValidationRuleTestSupport.swift b/Tests/ValidationRuleTestSupport.swift
index 8fb5cdc..ee3f7af 100644
--- a/Tests/ValidationRuleTestSupport.swift
+++ b/Tests/ValidationRuleTestSupport.swift
@@ -1,9 +1,50 @@
+import Testing
@testable import PureXML
enum ValidationRuleTestSupport {
typealias DTDSchema = PureXML.Validation.DTDSchema
typealias XSDContext = PureXML.Validation.XSDContext
+ /// `xmlns:xsi` namespace declaration, spliced into instance fixtures that need
+ /// `xsi:nil`/`xsi:type`.
+ static let xsiNS = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
+
+ /// Compiles `schema`, parses `instance`, and runs `rule` alone (isolated via
+ /// `.blank.validating(rule)`) against the root element declared `rootName`.
+ /// Shared by every `ValidationRuleXSD*Tests` suite.
+ static func xsdErrors(
+ _ rule: PureXML.Validation.Validation,
+ schema: String,
+ instance: String,
+ rootName: String,
+ ) throws -> [PureXML.Validation.ValidationError] {
+ let compiled = try PureXML.Schema.XSDParser.parse(schema)
+ let node = try PureXML.parse(instance)
+ guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
+ Issue.record("no root element")
+ return []
+ }
+ let context = XSDContext(
+ types: compiled.types,
+ constraints: compiled.constraints,
+ rootDeclaration: compiled.elements[rootName],
+ nillableElements: compiled.nillableElements,
+ elementConstraints: compiled.elementConstraints,
+ abstractTypes: compiled.nsAbstractTypes,
+ typeBlock: compiled.nsTypeBlock,
+ elementBlock: compiled.nsElementBlock,
+ typeDerivation: compiled.nsTypeDerivation,
+ unionMembers: compiled.nsUnionMembers,
+ globalAttributes: compiled.globalAttributes,
+ identityFieldTypes: compiled.identityFieldTypes,
+ identityFieldConstraints: compiled.identityFieldConstraints,
+ identityNonSimpleFields: compiled.identityNonSimpleFields,
+ )
+ return PureXML.Validation.Validator.blank
+ .validating(rule)
+ .errors(for: .element(root), in: context)
+ }
+
static func dtd(_ xml: String, resolver: PureXML.Parsing.EntityResolver = .refusing) throws -> (PureXML.Model.Node, DTDSchema) {
let parsed = try PureXML.Parsing.Parser().parseWithDocumentType(xml, limits: .init(allowDoctype: true), resolver: resolver)
let standalone = parsed.declaration?.standalone == true
diff --git a/Tests/ValidationRuleXSDAttributeTests.swift b/Tests/ValidationRuleXSDAttributeTests.swift
new file mode 100644
index 0000000..b984aed
--- /dev/null
+++ b/Tests/ValidationRuleXSDAttributeTests.swift
@@ -0,0 +1,205 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: attributes.
+@Suite("Validation rules XSD: attributes")
+struct ValidationRuleXSDAttributeTests {
+ // MARK: attributeTypeValid / attributeFixedMatches / attributeRequiredPresent / attributeSingleID
+
+ private let requiredIntAttributeSchema = """
+
+
+
+ """
+
+ @Test("attributeTypeValid rejects an attribute value invalid against its type")
+ func test_attributeTypeValid() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeTypeValid, schema: requiredIntAttributeSchema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("attribute 'a'") == true)
+ }
+
+ @Test("attributeTypeValid accepts a valid attribute value")
+ func test_attributeTypeValid_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeTypeValid, schema: requiredIntAttributeSchema, instance: "", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ @Test("attributeRequiredPresent rejects a missing required attribute")
+ func test_attributeRequiredPresent() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeRequiredPresent, schema: requiredIntAttributeSchema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("missing required attribute") == true)
+ }
+
+ @Test("attributeRequiredPresent accepts a present required attribute")
+ func test_attributeRequiredPresent_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.attributeRequiredPresent,
+ schema: requiredIntAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ private let fixedAttributeSchema = """
+
+
+
+ """
+
+ @Test("attributeFixedMatches rejects a value that differs from the fixed value")
+ func test_attributeFixedMatches() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeFixedMatches, schema: fixedAttributeSchema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("is fixed and must be") == true)
+ }
+
+ @Test("attributeFixedMatches accepts a value equal to the fixed value")
+ func test_attributeFixedMatches_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeFixedMatches, schema: fixedAttributeSchema, instance: "", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ private let twoIDAttributesSchema = """
+
+
+
+ """
+
+ @Test("attributeSingleID rejects more than one attribute of type ID")
+ func test_attributeSingleID() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.attributeSingleID,
+ schema: twoIDAttributesSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("at most one attribute of type ID") == true)
+ }
+
+ @Test("attributeSingleID accepts a single attribute of type ID")
+ func test_attributeSingleID_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeSingleID, schema: twoIDAttributesSchema, instance: "", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: attributeDeclaredOrPermitted
+
+ private let emptyContentSchema = """
+
+
+
+ """
+
+ @Test("attributeDeclaredOrPermitted rejects an undeclared attribute with no wildcard")
+ func test_attributeDeclaredOrPermitted() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.attributeDeclaredOrPermitted,
+ schema: emptyContentSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("undeclared attribute") == true)
+ }
+
+ @Test("attributeDeclaredOrPermitted accepts no undeclared attributes")
+ func test_attributeDeclaredOrPermitted_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeDeclaredOrPermitted, schema: emptyContentSchema, instance: "", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: wildcardAttributeUndeclared / wildcardAttributeTypeValid / wildcardAttributeFixedMatches
+
+ private let strictWildcardAttributeSchema = """
+
+
+
+ """
+
+ @Test("wildcardAttributeUndeclared rejects a strict wildcard match with no global declaration")
+ func test_wildcardAttributeUndeclared() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeUndeclared,
+ schema: strictWildcardAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("no declaration for wildcard-matched attribute") == true)
+ }
+
+ @Test("wildcardAttributeUndeclared accepts no wildcard-matched attributes")
+ func test_wildcardAttributeUndeclared_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeUndeclared,
+ schema: strictWildcardAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ private let laxWildcardIntAttributeSchema = """
+
+
+
+
+ """
+
+ @Test("wildcardAttributeTypeValid rejects a wildcard-matched attribute invalid against its type")
+ func test_wildcardAttributeTypeValid() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeTypeValid,
+ schema: laxWildcardIntAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("attribute 'foo'") == true)
+ }
+
+ @Test("wildcardAttributeTypeValid accepts a valid wildcard-matched attribute")
+ func test_wildcardAttributeTypeValid_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeTypeValid,
+ schema: laxWildcardIntAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ private let laxWildcardFixedAttributeSchema = """
+
+
+
+
+ """
+
+ @Test("wildcardAttributeFixedMatches rejects a wildcard-matched attribute that differs from its fixed value")
+ func test_wildcardAttributeFixedMatches() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeFixedMatches,
+ schema: laxWildcardFixedAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("is fixed and must be") == true)
+ }
+
+ @Test("wildcardAttributeFixedMatches accepts a wildcard-matched attribute equal to its fixed value")
+ func test_wildcardAttributeFixedMatches_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardAttributeFixedMatches,
+ schema: laxWildcardFixedAttributeSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDContentTests.swift b/Tests/ValidationRuleXSDContentTests.swift
new file mode 100644
index 0000000..9659677
--- /dev/null
+++ b/Tests/ValidationRuleXSDContentTests.swift
@@ -0,0 +1,159 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): content and wildcard rules.
+@Suite("Validation rules XSD: content and wildcard rules")
+struct ValidationRuleXSDContentTests {
+ // MARK: emptyContentForbidsChildren
+
+ private let emptyContentSchema = """
+
+
+
+ """
+
+ @Test("emptyContentForbidsChildren rejects children on an empty-content element")
+ func test_emptyContentForbidsChildren() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.emptyContentForbidsChildren,
+ schema: emptyContentSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason == "element must be empty")
+ }
+
+ @Test("emptyContentForbidsChildren accepts a truly empty element")
+ func test_emptyContentForbidsChildren_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.emptyContentForbidsChildren,
+ schema: emptyContentSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: simpleContentForbidsChildren / simpleContentTypeValid
+
+ private let simpleContentSchema = """
+
+
+
+
+
+ """
+
+ @Test("simpleContentForbidsChildren rejects element children under simpleContent")
+ func test_simpleContentForbidsChildren() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleContentForbidsChildren,
+ schema: simpleContentSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason == "element must not have children")
+ }
+
+ @Test("simpleContentForbidsChildren accepts text-only simpleContent")
+ func test_simpleContentForbidsChildren_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleContentForbidsChildren,
+ schema: simpleContentSchema,
+ instance: "5",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ @Test("simpleContentTypeValid rejects text invalid against the simpleContent type")
+ func test_simpleContentTypeValid() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleContentTypeValid,
+ schema: simpleContentSchema,
+ instance: "x",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.hasPrefix("content:") == true)
+ }
+
+ @Test("simpleContentTypeValid accepts text valid against the simpleContent type")
+ func test_simpleContentTypeValid_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleContentTypeValid,
+ schema: simpleContentSchema,
+ instance: "5",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: textForbidden
+
+ private let elementOnlyOptionalSchema = """
+
+
+
+
+
+ """
+
+ @Test("textForbidden rejects text under element-only content")
+ func test_textForbidden() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.textForbidden,
+ schema: elementOnlyOptionalSchema,
+ instance: "hello",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason == "element must not contain text")
+ }
+
+ @Test("textForbidden accepts element-only content with no text")
+ func test_textForbidden_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.textForbidden,
+ schema: elementOnlyOptionalSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: wildcardElementUndeclared
+
+ private let strictWildcardElementSchema = """
+
+
+
+
+
+ """
+
+ @Test("wildcardElementUndeclared rejects a strict wildcard match with no global declaration")
+ func test_wildcardElementUndeclared() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardElementUndeclared,
+ schema: strictWildcardElementSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("no declaration for wildcard-matched element") == true)
+ }
+
+ @Test("wildcardElementUndeclared accepts an optional wildcard with no match")
+ func test_wildcardElementUndeclared_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.wildcardElementUndeclared,
+ schema: strictWildcardElementSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDDerivationTests.swift b/Tests/ValidationRuleXSDDerivationTests.swift
new file mode 100644
index 0000000..99f4c50
--- /dev/null
+++ b/Tests/ValidationRuleXSDDerivationTests.swift
@@ -0,0 +1,121 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: derivation and identifiers.
+@Suite("Validation rules XSD: derivation and identifiers")
+struct ValidationRuleXSDDerivationTests {
+ // MARK: xsiTypeSubstitutionValid / xsiTypeOverrideResolves
+
+ private let abstractDerivedSchema = """
+
+
+
+
+
+ """
+
+ @Test("xsiTypeSubstitutionValid rejects an abstract declared type with no xsi:type override")
+ func test_xsiTypeSubstitutionValid() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.xsiTypeSubstitutionValid, schema: abstractDerivedSchema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("abstract type") == true)
+ }
+
+ @Test("xsiTypeSubstitutionValid accepts an xsi:type naming a concrete derived type")
+ func test_xsiTypeSubstitutionValid_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.xsiTypeSubstitutionValid,
+ schema: abstractDerivedSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ private let simpleStringSchema = """
+
+
+
+ """
+
+ @Test("xsiTypeOverrideResolves rejects an xsi:type naming an unknown type")
+ func test_xsiTypeOverrideResolves() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.xsiTypeOverrideResolves,
+ schema: simpleStringSchema,
+ instance: "text",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("unknown xsi:type") == true)
+ }
+
+ @Test("xsiTypeOverrideResolves accepts an xsi:type naming a resolvable type")
+ func test_xsiTypeOverrideResolves_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.xsiTypeOverrideResolves,
+ schema: simpleStringSchema,
+ instance: "text",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: idIdrefConsistent
+
+ private let idElementSchema = """
+
+
+
+
+
+
+
+ """
+
+ @Test("idIdrefConsistent rejects a duplicate xs:ID value")
+ func test_idIdrefConsistent() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.idIdrefConsistent,
+ schema: idElementSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(!errors.isEmpty)
+ }
+
+ @Test("idIdrefConsistent accepts distinct xs:ID values")
+ func test_idIdrefConsistent_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.idIdrefConsistent,
+ schema: idElementSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: xsdStreamingIdConsistency
+
+ @Test("xsdStreamingIdConsistency rejects a duplicate xs:ID recorded during streaming")
+ func test_xsdStreamingIdConsistency() {
+ let validator = PureXML.Schema.ComplexValidator()
+ validator.recordIDs(PureXML.Schema.SimpleType(base: .id), value: "x1", at: [])
+ validator.recordIDs(PureXML.Schema.SimpleType(base: .id), value: "x1", at: [])
+ let errors = PureXML.Validation.Validator.blank
+ .validating(PureXML.Schema.ComplexValidator.idConsistency)
+ .errors(for: validator, in: ())
+ #expect(!errors.isEmpty)
+ }
+
+ @Test("xsdStreamingIdConsistency accepts distinct xs:ID values recorded during streaming")
+ func test_xsdStreamingIdConsistency_succeeds() {
+ let validator = PureXML.Schema.ComplexValidator()
+ validator.recordIDs(PureXML.Schema.SimpleType(base: .id), value: "x1", at: [])
+ validator.recordIDs(PureXML.Schema.SimpleType(base: .id), value: "x2", at: [])
+ let errors = PureXML.Validation.Validator.blank
+ .validating(PureXML.Schema.ComplexValidator.idConsistency)
+ .errors(for: validator, in: ())
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDIdentityTests.swift b/Tests/ValidationRuleXSDIdentityTests.swift
new file mode 100644
index 0000000..3eeee2b
--- /dev/null
+++ b/Tests/ValidationRuleXSDIdentityTests.swift
@@ -0,0 +1,260 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: identity constraints.
+@Suite("Validation rules XSD: identity constraints")
+struct ValidationRuleXSDIdentityTests {
+ // MARK: identityConstraintXPathsValid
+
+ private let uniqueListSchemaTemplate = """
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityConstraintXPathsValid rejects a malformed selector XPath")
+ func test_identityConstraintXPathsValid() throws {
+ let schema = String(format: uniqueListSchemaTemplate, "[[[")
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityConstraintXPathsValid,
+ schema: schema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("invalid selector XPath") == true)
+ }
+
+ @Test("identityConstraintXPathsValid accepts a well-formed selector XPath")
+ func test_identityConstraintXPathsValid_succeeds() throws {
+ let schema = String(format: uniqueListSchemaTemplate, "item")
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityConstraintXPathsValid,
+ schema: schema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: identityKeyFieldPresent
+
+ private let keyListSchema = """
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityKeyFieldPresent rejects a key target missing its field")
+ func test_identityKeyFieldPresent() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityKeyFieldPresent,
+ schema: keyListSchema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("a field is missing") == true)
+ }
+
+ @Test("identityKeyFieldPresent accepts a key target with its field present")
+ func test_identityKeyFieldPresent_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityKeyFieldPresent,
+ schema: keyListSchema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: identityValueUnique
+
+ private let uniqueListSchema = """
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityValueUnique reports a duplicate key field")
+ func test_identityValueUnique() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityValueUnique,
+ schema: uniqueListSchema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(!errors.isEmpty)
+ }
+
+ @Test("identityValueUnique accepts distinct field values")
+ func test_identityValueUnique_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityValueUnique,
+ schema: uniqueListSchema,
+ instance: "
",
+ rootName: "list",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: identityKeyrefResolves
+
+ private let keyrefSchema = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityKeyrefResolves rejects a keyref value with no matching key")
+ func test_identityKeyrefResolves() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityKeyrefResolves,
+ schema: keyrefSchema,
+ instance: "",
+ rootName: "root",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("no matching key") == true)
+ }
+
+ @Test("identityKeyrefResolves accepts a keyref value that matches a key")
+ func test_identityKeyrefResolves_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityKeyrefResolves,
+ schema: keyrefSchema,
+ instance: "",
+ rootName: "root",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: identityFieldCardinality
+
+ private let multiChildFieldSchema = """
+
+
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityFieldCardinality rejects a field that selects more than one node")
+ func test_identityFieldCardinality() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityFieldCardinality,
+ schema: multiChildFieldSchema,
+ instance: "- ab
",
+ rootName: "list",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("must select at most one node") == true)
+ }
+
+ @Test("identityFieldCardinality accepts a field that selects exactly one node")
+ func test_identityFieldCardinality_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityFieldCardinality,
+ schema: multiChildFieldSchema,
+ instance: "- a
",
+ rootName: "list",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: identityFieldMustBeSimple
+
+ private let complexChildFieldSchema = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ private let simpleChildFieldSchema = """
+
+
+
+
+
+
+
+
+
+
+ """
+
+ @Test("identityFieldMustBeSimple rejects a field identifying a node with element children")
+ func test_identityFieldMustBeSimple() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityFieldMustBeSimple,
+ schema: complexChildFieldSchema,
+ instance: "- x
",
+ rootName: "list",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("must identify a node with simple content") == true)
+ }
+
+ @Test("identityFieldMustBeSimple accepts a field identifying a simple-content node")
+ func test_identityFieldMustBeSimple_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.identityFieldMustBeSimple,
+ schema: simpleChildFieldSchema,
+ instance: "- a
",
+ rootName: "list",
+ )
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDNilFixedTests.swift b/Tests/ValidationRuleXSDNilFixedTests.swift
new file mode 100644
index 0000000..5f254b7
--- /dev/null
+++ b/Tests/ValidationRuleXSDNilFixedTests.swift
@@ -0,0 +1,165 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): nillable and fixed-value rules.
+@Suite("Validation rules XSD: nillable and fixed-value rules")
+struct ValidationRuleXSDNilFixedTests {
+ // MARK: nilAttributeRequiresNillable
+
+ @Test("nilAttributeRequiresNillable rejects xsi:nil on a non-nillable element")
+ func test_nilAttributeRequiresNillable() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilAttributeRequiresNillable,
+ schema: schema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("not nillable") == true)
+ }
+
+ @Test("nilAttributeRequiresNillable accepts xsi:nil on a nillable element")
+ func test_nilAttributeRequiresNillable_succeeds() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilAttributeRequiresNillable,
+ schema: schema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: nilElementMustBeEmpty
+
+ @Test("nilElementMustBeEmpty rejects content on a nilled element")
+ func test_nilElementMustBeEmpty() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilElementMustBeEmpty,
+ schema: schema,
+ instance: "x",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("must be empty") == true)
+ }
+
+ @Test("nilElementMustBeEmpty accepts an empty nilled element")
+ func test_nilElementMustBeEmpty_succeeds() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilElementMustBeEmpty,
+ schema: schema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: nilElementNoFixedConstraint
+
+ @Test("nilElementNoFixedConstraint rejects a nilled element declared fixed")
+ func test_nilElementNoFixedConstraint() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilElementNoFixedConstraint,
+ schema: schema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("fixed value constraint") == true)
+ }
+
+ @Test("nilElementNoFixedConstraint accepts a nilled element with no fixed constraint")
+ func test_nilElementNoFixedConstraint_succeeds() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.nilElementNoFixedConstraint,
+ schema: schema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: fixedElementNoChildren / fixedElementValueMatches
+
+ private let fixedStringSchema = """
+
+
+
+ """
+
+ @Test("fixedElementNoChildren rejects element children on a fixed element")
+ func test_fixedElementNoChildren() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.fixedElementNoChildren,
+ schema: fixedStringSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("must not have element children") == true)
+ }
+
+ @Test("fixedElementNoChildren accepts a fixed element with only text")
+ func test_fixedElementNoChildren_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.fixedElementNoChildren,
+ schema: fixedStringSchema,
+ instance: "5",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ @Test("fixedElementValueMatches rejects text that differs from the fixed value")
+ func test_fixedElementValueMatches() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.fixedElementValueMatches,
+ schema: fixedStringSchema,
+ instance: "6",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("is fixed and must be") == true)
+ }
+
+ @Test("fixedElementValueMatches accepts text equal to the fixed value")
+ func test_fixedElementValueMatches_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.fixedElementValueMatches,
+ schema: fixedStringSchema,
+ instance: "5",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDStructureTests.swift b/Tests/ValidationRuleXSDStructureTests.swift
new file mode 100644
index 0000000..b67ff96
--- /dev/null
+++ b/Tests/ValidationRuleXSDStructureTests.swift
@@ -0,0 +1,133 @@
+import Testing
+@testable import PureXML
+
+/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: simple type and structure.
+@Suite("Validation rules XSD: simple type and structure")
+struct ValidationRuleXSDStructureTests {
+ // MARK: simpleTypeDisallowedAttribute / simpleTypeForbidsChildren / simpleTypeValueValid
+
+ private let simpleStringSchema = """
+
+
+
+ """
+
+ @Test("simpleTypeDisallowedAttribute rejects an attribute on a simple-typed element")
+ func test_simpleTypeDisallowedAttribute() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleTypeDisallowedAttribute,
+ schema: simpleStringSchema,
+ instance: "text",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("has a simple type") == true)
+ }
+
+ @Test("simpleTypeDisallowedAttribute accepts a simple-typed element with no attributes")
+ func test_simpleTypeDisallowedAttribute_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleTypeDisallowedAttribute,
+ schema: simpleStringSchema,
+ instance: "text",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ @Test("simpleTypeForbidsChildren rejects element children on a simple-typed element")
+ func test_simpleTypeForbidsChildren() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleTypeForbidsChildren,
+ schema: simpleStringSchema,
+ instance: "",
+ rootName: "n",
+ )
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("must not have children") == true)
+ }
+
+ @Test("simpleTypeForbidsChildren accepts a simple-typed element with only text")
+ func test_simpleTypeForbidsChildren_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(
+ PureXML.Validation.XSD.simpleTypeForbidsChildren,
+ schema: simpleStringSchema,
+ instance: "text",
+ rootName: "n",
+ )
+ #expect(errors.isEmpty)
+ }
+
+ @Test("simpleTypeValueValid locates a simple-type violation at the root element")
+ func test_simpleTypeValueValid() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.simpleTypeValueValid, schema: schema, instance: "x", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("n") == true)
+ #expect(errors.first.map(ValidationRuleTestSupport.path) == ["n"])
+ }
+
+ @Test("simpleTypeValueValid accepts a valid integer at the boundary")
+ func test_simpleTypeValueValid_succeeds() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.simpleTypeValueValid, schema: schema, instance: "0", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: typeReferenceResolves
+
+ @Test("typeReferenceResolves rejects a reference to an undeclared type")
+ func test_typeReferenceResolves() throws {
+ let schema = """
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.typeReferenceResolves, schema: schema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("unknown type") == true)
+ }
+
+ @Test("typeReferenceResolves accepts a reference that resolves to a declared type")
+ func test_typeReferenceResolves_succeeds() throws {
+ let schema = """
+
+
+
+
+ """
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.typeReferenceResolves, schema: schema, instance: "", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+
+ // MARK: contentModelStructure
+
+ private let sequenceSchema = """
+
+
+
+
+
+ """
+
+ @Test("contentModelStructure rejects a child the content model does not allow")
+ func test_contentModelStructure() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.contentModelStructure, schema: sequenceSchema, instance: "", rootName: "n")
+ #expect(errors.count == 1)
+ #expect(errors.first?.reason.contains("is not allowed here") == true)
+ }
+
+ @Test("contentModelStructure accepts children matching the content model")
+ func test_contentModelStructure_succeeds() throws {
+ let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.contentModelStructure, schema: sequenceSchema, instance: "x", rootName: "n")
+ #expect(errors.isEmpty)
+ }
+}
diff --git a/Tests/ValidationRuleXSDTests.swift b/Tests/ValidationRuleXSDTests.swift
index 23a97bc..1125580 100644
--- a/Tests/ValidationRuleXSDTests.swift
+++ b/Tests/ValidationRuleXSDTests.swift
@@ -1,102 +1,11 @@
import Testing
@testable import PureXML
-/// Per-rule isolation tests (Validation rules XSD).
+/// Per-rule isolation tests (Validation rules XSD): the schema-consistency layer
+/// (`PureXML.Validation.XSDSchema`), unrelated to instance validation.
@Suite("Validation rules XSD")
struct ValidationRuleXSDTests {
- @Test("XSD.contentValidity locates a simple-type violation at the root element")
- func test_xsdContentValidity() throws {
- let xsd = """
-
-
-
- """
- let compiled = try PureXML.Schema.XSDParser.parse(xsd)
- let node = try PureXML.parse("x")
- guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
- Issue.record("no root element")
- return
- }
- let context = ValidationRuleTestSupport.XSDContext(
- types: compiled.types,
- constraints: compiled.constraints,
- rootDeclaration: compiled.elements["n"],
- )
- // The validator is rooted at the document element, as Schema.Document does.
- let errors = PureXML.Validation.Validator.blank
- .validating(PureXML.Validation.XSD.contentValidity)
- .errors(for: .element(root), in: context)
- #expect(errors.count == 1)
- #expect(errors.first?.reason.contains("n") == true)
- #expect(errors.first.map(ValidationRuleTestSupport.path) == ["n"])
- }
-
- @Test("XSD.contentValidity accepts a valid integer at the boundary")
- func test_xsdContentValidity_succeeds() throws {
- let xsd = """
-
-
-
- """
- let compiled = try PureXML.Schema.XSDParser.parse(xsd)
- let node = try PureXML.parse("0")
- guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
- Issue.record("no root element")
- return
- }
- let context = ValidationRuleTestSupport.XSDContext(types: compiled.types, constraints: compiled.constraints, rootDeclaration: compiled.elements["n"])
- let errors = PureXML.Validation.Validator.blank
- .validating(PureXML.Validation.XSD.contentValidity)
- .errors(for: .element(root), in: context)
- #expect(errors.isEmpty)
- }
-
- @Test("XSD.identityConstraints reports a duplicate key field")
- func test_xsdIdentityConstraints() throws {
- let xsd = """
-
-
-
-
-
-
-
-
- """
- let compiled = try PureXML.Schema.XSDParser.parse(xsd)
- let node = try PureXML.parse("
")
- guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
- Issue.record("no root element")
- return
- }
- let context = ValidationRuleTestSupport.XSDContext(types: compiled.types, constraints: compiled.constraints, rootDeclaration: compiled.elements["list"])
- let errors = PureXML.Validation.Validator.blank
- .validating(PureXML.Validation.XSD.identityConstraints)
- .errors(for: .element(root), in: context)
- #expect(!errors.isEmpty)
- }
-
- @Test("XSD.identityConstraints accepts distinct field values")
- func test_xsdIdentityConstraints_succeeds() throws {
- let xsd = """
-
-
-
-
-
-
-
-
- """
- let compiled = try PureXML.Schema.XSDParser.parse(xsd)
- let node = try PureXML.parse("
")
- guard case let .document(children) = node, let root = children.compactMap(\.element).first else { return }
- let context = ValidationRuleTestSupport.XSDContext(types: compiled.types, constraints: compiled.constraints, rootDeclaration: compiled.elements["list"])
- #expect(PureXML.Validation.Validator.blank.validating(PureXML.Validation.XSD.identityConstraints).errors(
- for: .element(root),
- in: context,
- ).isEmpty)
- }
+ // MARK: XSDSchema (unchanged, schema-consistency layer)
@Test("XSDSchema.finalRespected rejects a derivation forbidden by final")
func test_xsdFinalRespected() throws {
diff --git a/Tests/XPathDescendantFusionTests.swift b/Tests/XPathDescendantFusionTests.swift
index 2a68571..7bd2ad6 100644
--- a/Tests/XPathDescendantFusionTests.swift
+++ b/Tests/XPathDescendantFusionTests.swift
@@ -18,7 +18,9 @@ struct XPathDescendantFusionTests {
private func ids(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.source)).compactMap {
- if case let .node(node) = $0 { return node.element?.attributes.first { $0.name.localName == "id" }?.value }
+ if case let .node(node) = $0 {
+ return node.element?.attributes.first { $0.name.localName == "id" }?.value
+ }
return nil
}
}
diff --git a/Tests/XPathFusedAttributeTests.swift b/Tests/XPathFusedAttributeTests.swift
index f6e2c15..28cd45b 100644
--- a/Tests/XPathFusedAttributeTests.swift
+++ b/Tests/XPathFusedAttributeTests.swift
@@ -13,7 +13,9 @@ struct XPathFusedAttributeTests {
private func values(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.source)).compactMap {
- if case let .attribute(attribute) = $0 { return attribute.value }
+ if case let .attribute(attribute) = $0 {
+ return attribute.value
+ }
return nil
}
}
diff --git a/Tests/XPathFusedDescendantTests.swift b/Tests/XPathFusedDescendantTests.swift
index e53b49f..b3dd195 100644
--- a/Tests/XPathFusedDescendantTests.swift
+++ b/Tests/XPathFusedDescendantTests.swift
@@ -19,7 +19,9 @@ struct XPathFusedDescendantTests {
private func ids(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.source)).compactMap {
- if case let .node(node) = $0 { return node.element?.attributes.first { $0.name.localName == "id" }?.value }
+ if case let .node(node) = $0 {
+ return node.element?.attributes.first { $0.name.localName == "id" }?.value
+ }
return nil
}
}
diff --git a/Tests/XPathSingleStepOrderTests.swift b/Tests/XPathSingleStepOrderTests.swift
index 8d06fe3..004001c 100644
--- a/Tests/XPathSingleStepOrderTests.swift
+++ b/Tests/XPathSingleStepOrderTests.swift
@@ -22,14 +22,18 @@ struct XPathSingleStepOrderTests {
private func ids(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.source)).compactMap {
- if case let .node(node) = $0 { return node.element?.attributes.first { $0.name.localName == "id" }?.value }
+ if case let .node(node) = $0 {
+ return node.element?.attributes.first { $0.name.localName == "id" }?.value
+ }
return nil
}
}
private func names(_ path: String) throws -> [String] {
try PureXML.XPath.Query(path).evaluate(over: PureXML.parse(Self.source)).compactMap {
- if case let .node(node) = $0 { return node.element?.name.localName }
+ if case let .node(node) = $0 {
+ return node.element?.name.localName
+ }
return nil
}
}
diff --git a/Tests/XSLTParityTests.swift b/Tests/XSLTParityTests.swift
index bab1928..af229fd 100644
--- a/Tests/XSLTParityTests.swift
+++ b/Tests/XSLTParityTests.swift
@@ -12,7 +12,9 @@ struct XSLTParityTests {
guard output.hasPrefix("") else { return output }
var body = String(output[end.upperBound...])
- if body.hasPrefix("\n") { body.removeFirst() }
+ if body.hasPrefix("\n") {
+ body.removeFirst()
+ }
return body
}
diff --git a/Tests/XSLTParserTests.swift b/Tests/XSLTParserTests.swift
index ae731e5..a767ce9 100644
--- a/Tests/XSLTParserTests.swift
+++ b/Tests/XSLTParserTests.swift
@@ -66,8 +66,20 @@ struct XSLTParserTests {
Issue.record("expected three parts, got \(parts.count)")
return
}
- if case let .literal(text) = parts[0] { #expect(text == "id-") } else { Issue.record("part 0") }
- if case let .expression(expr) = parts[1] { #expect(expr == "@n") } else { Issue.record("part 1") }
- if case let .literal(text) = parts[2] { #expect(text == "-{x}") } else { Issue.record("part 2") }
+ if case let .literal(text) = parts[0] {
+ #expect(text == "id-")
+ } else {
+ Issue.record("part 0")
+ }
+ if case let .expression(expr) = parts[1] {
+ #expect(expr == "@n")
+ } else {
+ Issue.record("part 1")
+ }
+ if case let .literal(text) = parts[2] {
+ #expect(text == "-{x}")
+ } else {
+ Issue.record("part 2")
+ }
}
}
diff --git a/Tests/XSLTTests.swift b/Tests/XSLTTests.swift
index 7490c5a..ce406dc 100644
--- a/Tests/XSLTTests.swift
+++ b/Tests/XSLTTests.swift
@@ -12,7 +12,9 @@ struct XSLTTests {
guard output.hasPrefix("") else { return output }
var body = String(output[end.upperBound...])
- if body.hasPrefix("\n") { body.removeFirst() }
+ if body.hasPrefix("\n") {
+ body.removeFirst()
+ }
return body
}
diff --git a/Tests/XSTSDifferentialTests.swift b/Tests/XSTSDifferentialTests.swift
index 0f73644..2419722 100644
--- a/Tests/XSTSDifferentialTests.swift
+++ b/Tests/XSTSDifferentialTests.swift
@@ -127,7 +127,9 @@ struct XSTSDifferentialTests {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let output = String(decoding: data, as: UTF8.self)
- if output.contains("Schemas parser error") || output.contains("failed to compile") { return false }
+ if output.contains("Schemas parser error") || output.contains("failed to compile") {
+ return false
+ }
return true
#endif
}
diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift
index 89bdfd5..b750aec 100644
--- a/Tests/XSTSSuiteTests.swift
+++ b/Tests/XSTSSuiteTests.swift
@@ -318,7 +318,9 @@ struct XSTSSuiteTests {
found += elements(named: name, under: child)
}
case let .element(element):
- if element.name.localName == name { found.append(element) }
+ if element.name.localName == name {
+ found.append(element)
+ }
for child in element.children {
found += elements(named: name, under: child)
}
@@ -362,7 +364,9 @@ struct XSTSSuiteTests {
var parts = base.split(separator: "/", omittingEmptySubsequences: false).map(String.init)
for segment in href.split(separator: "/") {
if segment == ".." {
- if !parts.isEmpty { parts.removeLast() }
+ if !parts.isEmpty {
+ parts.removeLast()
+ }
} else if segment != "." {
parts.append(String(segment))
}
diff --git a/docs/validation-field-registry.txt b/docs/validation-field-registry.txt
index a487b68..a4cb5c0 100644
--- a/docs/validation-field-registry.txt
+++ b/docs/validation-field-registry.txt
@@ -18,20 +18,20 @@ FIELD parseAdvisories dtdParseAdvisories
IGNORE isEmpty derived summary; no independent constraint
MODEL PureXML.Validation.XSDContext
-FIELD types xsdContentValidity,xsdStreamingShallowValidity
-FIELD constraints xsdIdentityConstraints
-FIELD rootDeclaration xsdContentValidity
-FIELD nillableElements xsdContentValidity
-FIELD elementConstraints xsdContentValidity
-FIELD abstractTypes xsdContentValidity
-FIELD typeBlock xsdContentValidity
-FIELD elementBlock xsdContentValidity
-FIELD typeDerivation xsdContentValidity
+FIELD types xsdTypeReferenceResolves,xsdContentModelStructure,xsdXsiTypeSubstitutionValid,xsdXsiTypeOverrideResolves,xsdStreamingShallowValidity
+FIELD constraints xsdIdentityConstraintXPathsValid,xsdIdentityKeyFieldPresent,xsdIdentityValueUnique,xsdIdentityKeyrefResolves,xsdIdentityFieldCardinality,xsdIdentityFieldMustBeSimple
+FIELD rootDeclaration xsdContentModelStructure
+FIELD nillableElements xsdNilAttributeRequiresNillable
+FIELD elementConstraints xsdNilElementNoFixedConstraint,xsdFixedElementNoChildren,xsdFixedElementValueMatches
+FIELD abstractTypes xsdXsiTypeSubstitutionValid
+FIELD typeBlock xsdXsiTypeSubstitutionValid
+FIELD elementBlock xsdXsiTypeSubstitutionValid
+FIELD typeDerivation xsdXsiTypeSubstitutionValid
MODEL PureXML.Model.Element
-FIELD name dtdContentModel,dtdUndeclaredElement,dtdUndeclaredAttributes,htmlRequiredParent,htmlVoidElementsAreEmpty,xsdContentValidity
+FIELD name dtdContentModel,dtdUndeclaredElement,dtdUndeclaredAttributes,htmlRequiredParent,htmlVoidElementsAreEmpty,xsdContentModelStructure
FIELD attributes uniqueAttributes,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredAttributes,dtdStandaloneAttributes
-FIELD children dtdContentModel,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentValidity
+FIELD children dtdContentModel,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentModelStructure
IGNORE text derived from direct text and CDATA children
MODEL PureXML.Model.Attribute
@@ -39,8 +39,8 @@ FIELD name uniqueAttributes,dtdUndeclaredAttributes,dtdRequiredAttributes,dtdFix
FIELD value dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdIdentifierIntegrity
MODEL PureXML.Model.Node
-CASE document dtdDeclarationValidity,dtdRootElementType,dtdIdentifierIntegrity,htmlUniqueIdentifiers,xsdContentValidity,xsdIdentityConstraints
-CASE element uniqueAttributes,dtdContentModel,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredElement,dtdUndeclaredAttributes,dtdStandaloneAttributes,dtdStandaloneElementWhitespace,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentValidity
+CASE document dtdDeclarationValidity,dtdRootElementType,dtdIdentifierIntegrity,htmlUniqueIdentifiers,xsdContentModelStructure,xsdIdentityConstraintXPathsValid
+CASE element uniqueAttributes,dtdContentModel,dtdRequiredAttributes,dtdFixedAttributeValues,dtdEnumeratedAttributeValues,dtdTokenizedAttributeTypes,dtdNotationAttributes,dtdUndeclaredElement,dtdUndeclaredAttributes,dtdStandaloneAttributes,dtdStandaloneElementWhitespace,htmlVoidElementsAreEmpty,htmlRequiredParent,xsdContentModelStructure
CASE text dtdContentModel
CASE cdata dtdContentModel
IGNORE comment no DTD or XSD constraints on comments