From f6159191dbadbc4a9d504c37b46d2636f13a189b Mon Sep 17 00:00:00 2001 From: Mihaela Mihaljevic Date: Sat, 20 Jun 2026 17:47:08 +0200 Subject: [PATCH] fix(schema): compare simpleContent fixed value in value space A fixed value on an element whose type is a complex type with simpleContent was compared as a raw string, so value-space-equal content was wrongly rejected (e.g. "05" against an xs:int fixed "5"). The complex-element fixed check on both the tree path (validate(_:against:)) and the streaming path passed no value type to elementFixedErrors, falling through to a string compare; both now pass the simpleContent simple type as the value type, in lockstep, so the comparison is in value space (cvc-elt.5.2.2.1) as the simple-typed-element path already does. XSTS gate held at 0/37/0/26 across all 14383 groups. --- CHANGELOG.md | 6 +++ Sources/Schema/ComplexValidator.swift | 5 ++- .../Schema/ComplexValidatorStreaming.swift | 5 ++- Tests/SchemaSimpleContentFixedTests.swift | 38 +++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 Tests/SchemaSimpleContentFixedTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d3316..433db59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- A `fixed` value on an element whose type is a complex type with `simpleContent` is + now compared in the simple type's value space (cvc-elt.5.2.2.1), not as a raw string + (#202), so `05` satisfies an `xs:int` `fixed="5"`. The complex-element fixed check on + both the tree and streaming paths now passes the `simpleContent` type as the value + type, in lockstep, as the simple-typed-element path already does. + - The streaming XSD validator now applies an element's `default`/`fixed` value constraint to its simple content (#200): an empty element takes its constraint value, and that value (not the empty string) is validated against the type diff --git a/Sources/Schema/ComplexValidator.swift b/Sources/Schema/ComplexValidator.swift index 13750aa..70b228a 100644 --- a/Sources/Schema/ComplexValidator.swift +++ b/Sources/Schema/ComplexValidator.swift @@ -82,7 +82,10 @@ public extension PureXML.Schema { return errors + nilErrors } validateContent(element, type.content, at: path, namespaceBindings: namespaceBindings, into: &errors) - errors += elementFixedErrors(element, at: path) + // 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 } + errors += elementFixedErrors(element, valueType: simpleContentType, at: path) return errors } diff --git a/Sources/Schema/ComplexValidatorStreaming.swift b/Sources/Schema/ComplexValidatorStreaming.swift index be95c0a..6ca278c 100644 --- a/Sources/Schema/ComplexValidatorStreaming.swift +++ b/Sources/Schema/ComplexValidatorStreaming.swift @@ -168,7 +168,10 @@ public extension PureXML.Schema.ComplexValidator { validateAttributes(element, complex, at: path, into: &errors) if let nilErrors = nilErrors(element, at: path) { return errors + nilErrors } shallowContent(element, complex.content, at: path, into: &errors) - errors += elementFixedErrors(element, at: path) + // 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 } + errors += elementFixedErrors(element, valueType: simpleContentType, at: path) case .typeReference: // The shared resolver also guards a circular chain, which would // otherwise recurse here without terminating. diff --git a/Tests/SchemaSimpleContentFixedTests.swift b/Tests/SchemaSimpleContentFixedTests.swift new file mode 100644 index 0000000..b0e61a4 --- /dev/null +++ b/Tests/SchemaSimpleContentFixedTests.swift @@ -0,0 +1,38 @@ +import Testing +@testable import PureXML + +/// A `fixed` value on an element whose type is a complex type with `simpleContent` +/// must be compared in the simple type's value space (cvc-elt.5.2.2.1), not as a raw +/// string (#202). Both the tree and streaming paths must agree, so "05" satisfies an +/// xs:int `fixed="5"` and is not falsely rejected. +@Suite("XSD simpleContent fixed value-space (#202)") +struct SchemaSimpleContentFixedTests { + private let schema = """ + + + + + + + + + """ + + private func agreement(_ instance: String) throws -> [String] { + let document = try PureXML.Schema.Document(schema) + let tree = try document.validate(instance).map(\.reason).sorted() + let streamed = try document.validate(streaming: instance).map(\.reason).sorted() + #expect(tree == streamed, "streaming disagreed with tree:\n tree: \(tree)\n stream: \(streamed)") + return streamed + } + + @Test("a value-space-equal fixed simpleContent value is accepted on both paths") + func test_valueSpaceEqualAccepted() throws { + #expect(try agreement("05").isEmpty) + } + + @Test("a value-space-different fixed simpleContent value is rejected on both paths") + func test_valueSpaceDifferentRejected() throws { + #expect(try !agreement("6").isEmpty) + } +}