Document the weak-parent ownership trap at the public entry points: parseTree
and readTree now state that a tree (or ownerDocument) must be held while using
any node inside it, that TreeNode has no internal synchronization (confine to
one thread or actor), and that the immutable Sendable Node projection is the
way across concurrency boundaries; the TreeNode class doc carries the same
contract. QualifiedName's malformed-colon fallback (':local', 'prefix:', '::'
stay whole and unprefixed), already implemented, is now documented on init
and locked by a test.
Closes #114
55 lines
2.2 KiB
Swift
55 lines
2.2 KiB
Swift
public extension PureXML.Model {
|
|
/// An optionally namespace-prefixed XML name such as `xs:element`.
|
|
struct QualifiedName: Equatable, Hashable, Sendable, CustomStringConvertible {
|
|
/// Namespace prefix preceding the colon, when present (`xs` in `xs:element`).
|
|
public var prefix: String?
|
|
|
|
/// The local part of the name (`element` in `xs:element`).
|
|
public var localName: String
|
|
|
|
/// The resolved namespace URI, or nil when the name is in no namespace or
|
|
/// has not been namespace-resolved. Populated by the parser from in-scope
|
|
/// `xmlns` declarations; nil for names built directly.
|
|
public var namespaceURI: String?
|
|
|
|
public init(prefix: String? = nil, localName: String, namespaceURI: String? = nil) {
|
|
self.prefix = prefix
|
|
self.localName = localName
|
|
self.namespaceURI = namespaceURI
|
|
}
|
|
|
|
/// Returns a copy of this name carrying the given resolved namespace URI.
|
|
public func resolved(namespaceURI: String?) -> QualifiedName {
|
|
QualifiedName(prefix: prefix, localName: localName, namespaceURI: namespaceURI)
|
|
}
|
|
|
|
/// Parses a raw `prefix:local` or bare `local` name. A malformed colon
|
|
/// placement (`:local`, `prefix:`, `::`) does not split: the whole string
|
|
/// is kept as an unprefixed local name rather than inventing an empty
|
|
/// prefix or local part.
|
|
public init(_ raw: String) {
|
|
namespaceURI = nil
|
|
if let colon = raw.firstIndex(of: ":") {
|
|
let head = String(raw[raw.startIndex ..< colon])
|
|
let tail = String(raw[raw.index(after: colon)...])
|
|
if head.isEmpty || tail.isEmpty {
|
|
prefix = nil
|
|
localName = raw
|
|
} else {
|
|
prefix = head
|
|
localName = tail
|
|
}
|
|
} else {
|
|
prefix = nil
|
|
localName = raw
|
|
}
|
|
}
|
|
|
|
/// The full `prefix:local` rendering used in serialized output.
|
|
public var description: String {
|
|
guard let prefix else { return localName }
|
|
return "\(prefix):\(localName)"
|
|
}
|
|
}
|
|
}
|