feat(relaxng): fold RNC ~ string concatenation
A string literal split with ~ ("a" ~ "b") is the single literal "ab",
the way long values and namespace URIs are wrapped across lines. The fold
applies wherever a string literal is read: value literals and the URIs of
namespace/datatypes/include/external declarations.
This commit is contained in:
parent
79f424321c
commit
9845e1cd9d
@ -98,11 +98,24 @@ extension RNCParser {
|
||||
|
||||
func expectString() -> String {
|
||||
if case let .string(value) = peek() { advance()
|
||||
return value
|
||||
return concatenated(value)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/// Folds RNC string concatenation: `"a" ~ "b"` is the single literal `"ab"`,
|
||||
/// the way long strings are split across lines. The leading string has been
|
||||
/// consumed; this appends each following `~ "…"`.
|
||||
func concatenated(_ value: String) -> String {
|
||||
var result = value
|
||||
while peek() == .symbol("~"), case let .string(next)? = peek(1) {
|
||||
advance()
|
||||
advance()
|
||||
result += next
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func strip(_ qualified: String) -> String {
|
||||
qualified.split(separator: ":").last.map(String.init) ?? qualified
|
||||
}
|
||||
|
||||
@ -250,7 +250,7 @@ extension RNCParser {
|
||||
switch peek() {
|
||||
case let .string(value): advance()
|
||||
// A bare string literal is shorthand for a `token`-typed value.
|
||||
return .value(PureXML.Schema.SimpleType(base: .token), value)
|
||||
return .value(PureXML.Schema.SimpleType(base: .token), concatenated(value))
|
||||
case .symbol("("): advance()
|
||||
let pattern = parsePattern()
|
||||
expectSymbol(")")
|
||||
@ -269,7 +269,7 @@ extension RNCParser {
|
||||
var facets = PureXML.Schema.Facets()
|
||||
if peek() == .symbol("{") { parseParams(into: &facets) }
|
||||
if case let .string(value) = peek() { advance()
|
||||
return .value(PureXML.Schema.SimpleType(base: type), value)
|
||||
return .value(PureXML.Schema.SimpleType(base: type), concatenated(value))
|
||||
}
|
||||
return .data(PureXML.Schema.SimpleType(base: type, facets: facets))
|
||||
}
|
||||
|
||||
33
Tests/RelaxNGCompactConcatTests.swift
Normal file
33
Tests/RelaxNGCompactConcatTests.swift
Normal file
@ -0,0 +1,33 @@
|
||||
@testable import PureXML
|
||||
import Testing
|
||||
|
||||
@Suite("RELAX NG compact string concatenation")
|
||||
struct RelaxNGCompactConcatTests {
|
||||
private func validRNC(_ rnc: String, _ xml: String) throws -> Bool {
|
||||
try PureXML.Schema.RelaxNG(compact: rnc).validate(xml)
|
||||
}
|
||||
|
||||
@Test("A value literal split with ~ is concatenated")
|
||||
func test_valueConcatenation() throws {
|
||||
let rnc = "element greeting { \"hello \" ~ \"world\" }"
|
||||
#expect(try validRNC(rnc, "<greeting>hello world</greeting>"))
|
||||
#expect(try !validRNC(rnc, "<greeting>hello</greeting>"))
|
||||
}
|
||||
|
||||
@Test("More than two segments concatenate left to right")
|
||||
func test_multiSegment() throws {
|
||||
let rnc = "element n { \"a\" ~ \"b\" ~ \"c\" }"
|
||||
#expect(try validRNC(rnc, "<n>abc</n>"))
|
||||
#expect(try !validRNC(rnc, "<n>ab</n>"))
|
||||
}
|
||||
|
||||
@Test("A namespace URI may be assembled with ~")
|
||||
func test_namespaceConcatenation() throws {
|
||||
let rnc = """
|
||||
namespace p = "urn:" ~ "example"
|
||||
element root { element p:item { empty } }
|
||||
"""
|
||||
#expect(try validRNC(rnc, "<root xmlns:p=\"urn:example\"><p:item/></root>"))
|
||||
#expect(try !validRNC(rnc, "<root xmlns:p=\"urn:other\"><p:item/></root>"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user