diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9f446..5e9b512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ Second pre-release. Continues the W3C XSTS schema-validity campaign: **invalid-s ### Fixed +- XSTS invalid-schemas-accepted (#145): a `pattern` facet whose `\p{...}` or `\P{...}` property name can be no XSD `charProp` (`IsCategory | IsBlock`) under any repertoire is now rejected as a schema-validity error: an empty name, the bare prefix `Is`, a non-block name carrying a non-letter (`\p{\L}`), or `Is` followed by a non-block character. A well-formed but unrecognised category or block name (such as an unknown `Is`-block) stays a lenient engine limitation rather than an error, so only the unambiguously malformed forms reject. Caught three invalid schemas: invalid-schemas-accepted 203 → 200, no other bucket moved. + - XSTS invalid-schemas-accepted (#145): two attribute-declaration validity rules. An attribute `ref` may not also carry `form` (the form comes from the referenced global declaration; `src-attribute.3.2`), and no attribute declaration may be named `xmlns` (that name is reserved for namespace declarations; no-xmlns). Caught four invalid schemas: invalid-schemas-accepted 207 → 203, no other bucket moved. - XSTS invalid-schemas-accepted (#145): a referencing `attributeGroup` (one carrying a `ref`) is held to its `(annotation?)` content model. It names a definition elsewhere, so it may not itself declare attributes, nested attributeGroup references, or an `anyAttribute`; only a defining `attributeGroup` (with `name`) carries those (`attgD`). Caught three invalid schemas: invalid-schemas-accepted 210 → 207, no other bucket moved. diff --git a/Sources/Regex/RegexParser.swift b/Sources/Regex/RegexParser.swift index be144f6..74a305e 100644 --- a/Sources/Regex/RegexParser.swift +++ b/Sources/Regex/RegexParser.swift @@ -133,11 +133,28 @@ extension PureXML.Regex { guard peek() == "}" else { throw RegexError.badClass } advance() guard let predicate = CategoryPredicate(name: name, negated: negated) else { - throw RegexError.unsupported("\\p{\(name)}") + // An unknown name is by default a limit of this engine, not an error + // (stay lenient). But a name that can be NO XSD charProp at all is + // unambiguously invalid and rejects, independent of which categories + // or blocks this engine happens to recognise. + throw Self.isMalformedProperty(name) ? RegexError.invalidProperty : RegexError.unsupported("\\p{\(name)}") } return predicate } + /// Whether `name` can be no XSD `charProp` (`IsCategory | IsBlock`) under any + /// repertoire: empty, the bare `Is` prefix, a non-block name (no `Is` prefix) + /// carrying a non-letter, or `Is` followed by a non-`[A-Za-z0-9-]` character. + /// A well-formed but unrecognised name (an unknown block such as `IsaA0-a9`) + /// is NOT malformed and stays a lenient `unsupported` limit. + private static func isMalformedProperty(_ name: String) -> Bool { + if name.isEmpty || name == "Is" { return true } + guard name.hasPrefix("Is") else { + return name.contains { !$0.isLetter } + } + return name.dropFirst(2).contains { !($0.isLetter || $0.isNumber || $0 == "-") } + } + private func classFrom(_ member: ClassMember) -> CharClass { switch member { case let .scalar(value): .single(value) diff --git a/Sources/Regex/RegexSyntax.swift b/Sources/Regex/RegexSyntax.swift index 3c32ab3..4725a78 100644 --- a/Sources/Regex/RegexSyntax.swift +++ b/Sources/Regex/RegexSyntax.swift @@ -20,6 +20,12 @@ extension PureXML.Regex { /// A character class opened with `[` that the expression ends before /// closing with `]` (`a[`, `[a[:xyz:`). Unambiguously invalid. case unterminatedClass + /// The `{...}` of a `\p`/`\P` escape whose content can be no XSD charProp: + /// empty, the bare prefix `Is`, a non-block name carrying a character that + /// is not a letter, or `Is` followed by a non-block-name character + /// (`\p{\L}`, `\p{Is}`). Unambiguously invalid, distinct from an unknown but + /// well-formed category or block name (which stays an `unsupported` limit). + case invalidProperty var description: String { switch self { @@ -34,6 +40,7 @@ extension PureXML.Regex { case .reversedQuantifier: "quantifier minimum exceeds maximum" case .incompleteEscape: "an escape '\\' has no following character" case .unterminatedClass: "a character class is not closed with ']'" + case .invalidProperty: "a \\p{...} property name is malformed" } } } diff --git a/Sources/Schema/XSDSimpleParser.swift b/Sources/Schema/XSDSimpleParser.swift index 55c5a1d..3eb3f99 100644 --- a/Sources/Schema/XSDSimpleParser.swift +++ b/Sources/Schema/XSDSimpleParser.swift @@ -80,7 +80,7 @@ extension PureXML.Schema { private static func rejectsPattern(_ error: PureXML.Regex.RegexError) -> Bool { switch error { case .unbalanced, .danglingQuantifier, .reversedRange, .emptyClass, .reversedQuantifier, - .incompleteEscape, .unterminatedClass: + .incompleteEscape, .unterminatedClass, .invalidProperty: true case .unsupported, .badQuantifier, .badEscape, .badClass: false diff --git a/Tests/XSTSSuiteTests.swift b/Tests/XSTSSuiteTests.swift index 2197c34..7d7b0f8 100644 --- a/Tests/XSTSSuiteTests.swift +++ b/Tests/XSTSSuiteTests.swift @@ -120,8 +120,13 @@ struct XSTSSuiteTests { /// Rejecting an attribute `ref` that also carries `form` (src-attribute.3.2) and /// an attribute declaration named `xmlns` (no-xmlns) then caught four more /// invalid schemas: invalid-schemas accepted 207 → 203, no other bucket moved. + /// Rejecting a `\p{...}` property whose name can be no XSD charProp under any + /// repertoire (empty, the bare `Is`, a non-block name with a non-letter, or `Is` + /// followed by a non-block character: `\p{\L}`, `\p{Is}`) while staying lenient + /// on a well-formed but unrecognised block name then caught three more invalid + /// schemas: invalid-schemas accepted 203 → 200, no other bucket moved. private let knownSchemaValidRejected = 1 - private let knownSchemaInvalidAccepted = 203 + private let knownSchemaInvalidAccepted = 200 private let knownInstanceValidRejected = 3 private let knownInstanceInvalidAccepted = 132