PureXML/Tests/QualifiedNameColonHintTests.swift
Mihaela Mihaljevic 7a5761b64d
perf(parse): split the name prefix from a scanned colon offset (#292)
QualifiedName(_:) ran firstIndex(of: ":") over the whole name, grapheme by
grapheme, on every element and attribute. Most names have no prefix, so the
common case scanned the entire name to find no colon at all.

The byte scanner already visits every byte of an all-ASCII name, so have
takeASCIIName record the offset of the first colon (one comparison per byte),
and split at that known offset in QualifiedName(ascii:colonOffset:). The
unprefixed name (no colon) is then built with no scan; a prefixed name splits
at the recorded offset. The names are identical to the firstIndex path for
every colon placement: none, leading (:local), trailing (prefix:), middle,
doubled (::), and multiple (first colon wins). The hint is confined to the
all-ASCII fast path; non-ASCII names and every other construction site keep
QualifiedName(_:).

Measured parse ~12% faster on the 20k-item generated corpus via a same-state
A/B (8.5x to ~7.7x slower than libxml2). Verified by QualifiedNameColonHintTests
(colon-placement differential), the W3C XML conformance corpora, the Xalan XSLT
gold-output corpus, the full suite, the WASM build, and an adversarial
equivalence review.
2026-06-25 12:23:53 +02:00

52 lines
2.5 KiB
Swift

import Testing
@testable import PureXML
/// `QualifiedName(ascii:colonOffset:)` splits a scanned ASCII name using the
/// first-colon offset the byte scanner already found, instead of re-scanning
/// the name with `firstIndex(of:)`. It must produce exactly what
/// `QualifiedName(_:)` produces for the same name. This pins that equivalence
/// across every colon placement: none, leading, trailing, middle, doubled, and
/// a bare colon, so the hint path can never disagree with the scanning path.
@Suite("QualifiedName colon-hint equivalence")
struct QualifiedNameColonHintTests {
private static let names = [
"item", "name", "price", "id", "kind", "currency",
"m:rank", "xml:space", "a:b", "x:y:z",
":local", "prefix:", "::", ":", "a:", ":b",
"_underscore", "with-dash", "with.dot", "n123",
]
/// The offset of the first ':' (a byte offset, which for an ASCII name is a
/// character offset), or nil. This mirrors what `takeASCIIName` records.
private func firstColonOffset(_ name: String) -> Int? {
name.firstIndex(of: ":").map { name.distance(from: name.startIndex, to: $0) }
}
@Test("the hint init equals the scanning init for every colon placement")
func test_equivalence() {
for name in Self.names {
let scanned = PureXML.Model.QualifiedName(name)
let hinted = PureXML.Model.QualifiedName(ascii: name, colonOffset: firstColonOffset(name))
#expect(hinted.prefix == scanned.prefix, "prefix mismatch for \(name)")
#expect(hinted.localName == scanned.localName, "localName mismatch for \(name)")
#expect(hinted == scanned, "qualified name mismatch for \(name)")
}
}
@Test("prefixed and unprefixed ASCII names round-trip through the parser")
func test_throughParser() throws {
let node = try PureXML.parse(#"<r xmlns:m="urn:m"><m:child plain="1" m:flag="2"/></r>"#)
guard case let .document(children) = node, let root = children.compactMap(\.element).first,
case let .element(child)? = root.children.first
else {
Issue.record("no child element")
return
}
#expect(child.name.prefix == "m")
#expect(child.name.localName == "child")
let attributeNames = child.attributes.map(\.name)
#expect(attributeNames.contains { $0.prefix == nil && $0.localName == "plain" })
#expect(attributeNames.contains { $0.prefix == "m" && $0.localName == "flag" })
}
}