fix(schema): an identity-constraint xpath may not use an undeclared prefix; invalid-schemas-accepted 77 to 75 (#145)
A selector/field xpath in an identity constraint may name elements/attributes with namespace-qualified name tests; every prefix used must be a declared xmlns: prefix in scope (the implicit xml prefix excepted). identityXPathErrors, after the existing syntax check, now resolves each name-test prefix against the in-scope bindings (namespaceBindingsInScope, which captures the schema root and every ancestor) and rejects an undeclared one. An adversarial review confirmed no false positive: a prefix declared on the schema root, any intermediate ancestor, or the selector/field element resolves correctly; xml, the child::/attribute:: axis keywords (colon-free), prefix:*, and inter-token whitespace are all handled. XSTS: invalid-schemas-accepted 77 to 75 (idI010, idJ011), valid-schemas-rejected held at 0, no other bucket moved. (Two existing xpath-syntax fixtures used an undeclared 'ns' prefix to test name-test syntax; the test schema now declares xmlns:ns so they exercise syntax with a bound prefix.)
This commit is contained in:
parent
880137a955
commit
6379e5b25a
@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- XSTS invalid-schemas-accepted (#145): an identity-constraint `selector`/`field` xpath may not use an undeclared namespace prefix. Every prefix in a name test must be a declared `xmlns:` prefix in scope (the implicit `xml` prefix excepted); a `selector`/`field` whose xpath names an unbound prefix is rejected. invalid-schemas-accepted 77 → 75, no false positive (valid-schemas-rejected held at 0; an adversarial review confirmed declared prefixes on the schema root or any ancestor resolve correctly), no other bucket moved.
|
||||
|
||||
- XSTS invalid-schemas-accepted (#161): a `group` inside `xs:redefine` may reference itself at most once, and that self-reference must occur exactly once, `minOccurs` = `maxOccurs` = 1 (`src-redefine.6.1.2`). A self-reference with `minOccurs="0"`, `maxOccurs="unbounded"`, or any count above 1 is rejected. The self-reference is matched by namespace, not local name alone, so a reference to an imported group that merely shares the local name is unconstrained (no false positive). invalid-schemas-accepted 79 → 77, no false positive (valid-schemas-rejected held at 0), no other bucket moved.
|
||||
|
||||
- XSTS invalid-schemas-accepted (#145): a local `element` declaration must identify itself with a `name` or a `ref`; an element with neither is rejected. invalid-schemas-accepted 80 → 79, no false positive (valid-schemas-rejected held at 0), no other bucket moved.
|
||||
|
||||
@ -21,9 +21,30 @@ extension PureXML.Schema.XSDParser {
|
||||
guard local == "selector" || local == "field",
|
||||
let xpath = PureXML.Schema.XSDNode.attribute(node, "xpath")
|
||||
else { return [] }
|
||||
return validXPath(xpath, isField: local == "field")
|
||||
? []
|
||||
: ["the \(local) xpath '\(xpath)' is not a valid identity-constraint path"]
|
||||
if !validXPath(xpath, isField: local == "field") {
|
||||
return ["the \(local) xpath '\(xpath)' is not a valid identity-constraint path"]
|
||||
}
|
||||
// Every namespace prefix a name test uses must be declared in scope; the
|
||||
// implicit `xml` prefix is always bound. An unbound prefix is invalid.
|
||||
let bindings = namespaceBindingsInScope(of: node, defaultBindings: [:])
|
||||
for prefix in xpathNameTestPrefixes(xpath) where prefix != "xml" && bindings[prefix] == nil {
|
||||
return ["the \(local) xpath '\(xpath)' uses the undeclared namespace prefix '\(prefix)'"]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// The set of namespace prefixes used in the name tests of a (syntactically
|
||||
/// valid) identity-constraint xpath. A name test is `prefix:local` or
|
||||
/// `prefix:*`; an unprefixed name, `*`, `.`, or an axis keyword has no prefix.
|
||||
private static func xpathNameTestPrefixes(_ xpath: String) -> [String] {
|
||||
guard let tokens = tokenize(xpath) else { return [] }
|
||||
var prefixes: [String] = []
|
||||
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) }
|
||||
}
|
||||
return prefixes
|
||||
}
|
||||
|
||||
private enum XPathToken: Equatable {
|
||||
|
||||
@ -13,7 +13,7 @@ struct SchemaIdentityXPathTests {
|
||||
private func compile(_ selector: String, fields: [String] = ["@v"]) throws {
|
||||
let fieldElements = fields.map { #"<xs:field xpath="\#($0)"/>"# }.joined()
|
||||
_ = try PureXML.Schema.Document(#"""
|
||||
<xs:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns="urn:ns">
|
||||
<xs:element name="root" type="xs:string"/>
|
||||
<xs:element name="r">
|
||||
<xs:complexType><xs:sequence><xs:element ref="root"/></xs:sequence></xs:complexType>
|
||||
@ -85,6 +85,17 @@ struct SchemaIdentityXPathTests {
|
||||
return (try? PureXML.Schema.Document(document)) != nil
|
||||
}
|
||||
|
||||
/// A name-test prefix in a selector/field xpath must be a declared namespace
|
||||
/// prefix; an undeclared prefix is invalid. The implicitly-bound `xml` prefix and
|
||||
/// a prefix bound by the schema's own `xmlns:` declarations are valid.
|
||||
@Test("an identity-constraint xpath may not use an undeclared namespace prefix")
|
||||
func test_xpathUndeclaredPrefix() throws {
|
||||
try compile("xs:a")
|
||||
try compile("root", fields: ["xs:v"])
|
||||
#expect(rejectsSelector("imp:a"))
|
||||
#expect(rejectsField("imp:v"))
|
||||
}
|
||||
|
||||
@Test("an identity constraint requires one selector then one or more fields")
|
||||
func test_identityConstraintContentModel() {
|
||||
let selector = #"<xs:selector xpath="c"/>"#
|
||||
|
||||
@ -149,10 +149,11 @@ struct XSTSSuiteTests {
|
||||
/// schemaLocation must be a schema (src-resolve), import namespace matches the
|
||||
/// imported target (src-import.3), redefined type derives from itself
|
||||
/// (src-redefine.5), local complexType is anonymous, a local element has a name
|
||||
/// or ref, and a redefined group's self-reference occurs exactly once
|
||||
/// (src-redefine.6.1.2).
|
||||
/// or ref, a redefined group's self-reference occurs exactly once
|
||||
/// (src-redefine.6.1.2), and an identity-constraint xpath uses no undeclared
|
||||
/// namespace prefix.
|
||||
private let knownSchemaValidRejected = 0
|
||||
private let knownSchemaInvalidAccepted = 77
|
||||
private let knownSchemaInvalidAccepted = 75
|
||||
private let knownInstanceValidRejected = 0
|
||||
private let knownInstanceInvalidAccepted = 133
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user