Add namespace resolution following the XML Namespaces rules, reimplemented in Swift from libxml2's SAX2 model; no upstream code copied. - QualifiedName gains an optional resolved namespaceURI (nil for names built directly or in no namespace), with a resolved(namespaceURI:) helper. - NamespaceContext maintains a scope stack of xmlns bindings as elements open and close, resolving element and attribute names against it. - A default namespace applies to unprefixed element names but never to attributes; the xml prefix is built in; xmlns declarations are preserved as attributes so documents round-trip; an unbound prefix raises ParseError.undefinedNamespacePrefix. End-tag matching stays lexical. - Namespace-free documents are unchanged (URIs remain nil). - 7 namespace tests; 39 total.
52 lines
2.0 KiB
Swift
52 lines
2.0 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.
|
|
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)"
|
|
}
|
|
}
|
|
}
|