// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn xml_namespace_uri() -> String {
"http://www.w3.org/XML/1998/namespace"
}
///|
fn xmlns_namespace_uri() -> String {
"http://www.w3.org/2000/xmlns/"
}
///|
fn namespace_reader_from_reader(reader : Reader) -> NamespaceReader {
let base_scope : Map[String, String] = Map([])
base_scope.set("xml", xml_namespace_uri())
{ reader, scopes: [base_scope], }
}
///|
/// Create a namespace-aware reader from a string.
pub fn NamespaceReader::from_string(input : String) -> NamespaceReader {
namespace_reader_from_reader(Reader::from_string(input))
}
///|
/// Create a namespace-aware reader from a file path.
pub fn NamespaceReader::from_file(
path : String,
) -> NamespaceReader raise @fs.IOError {
namespace_reader_from_reader(Reader::from_file(path))
}
///|
/// Check whether the underlying reader has consumed its input.
pub fn NamespaceReader::is_eof(self : NamespaceReader) -> Bool {
self.reader.is_eof()
}
///|
/// Get the current one-indexed line number.
pub fn NamespaceReader::line(self : NamespaceReader) -> Int {
self.reader.line()
}
///|
/// Get the current one-indexed column number.
pub fn NamespaceReader::column(self : NamespaceReader) -> Int {
self.reader.column()
}
///|
fn split_qualified_name(name : String) -> (String?, String) raise XmlErrorKind {
if name == "" {
raise InvalidSyntax("invalid qualified name: " + name)
}
match name.find(":") {
None => (None, name)
Some(index) => {
if index == 0 || index + 1 >= name.length() {
raise InvalidSyntax("invalid qualified name: " + name)
}
let prefix = name[:index].to_owned()
let local_name = name[index + 1:].to_owned()
if local_name.find(":") is Some(_) {
raise InvalidSyntax("invalid qualified name: " + name)
}
(Some(prefix), local_name)
}
}
}
///|
fn namespace_declaration(
attribute : XmlAttribute,
) -> NamespaceDeclaration? raise XmlErrorKind {
let attribute_name = attribute.name
let namespace_uri = attribute.value
if attribute_name == "xmlns" {
Some({
prefix: None,
namespace_uri,
span: attribute.span,
name_span: attribute.name_span,
value_span: attribute.value_span,
})
} else if attribute_name.has_prefix("xmlns:") {
let declared_prefix = attribute_name[6:].to_owned()
if declared_prefix == "" || declared_prefix.find(":") is Some(_) {
raise InvalidSyntax("invalid qualified name: " + attribute_name)
}
Some({
prefix: Some(declared_prefix),
namespace_uri,
span: attribute.span,
name_span: attribute.name_span,
value_span: attribute.value_span,
})
} else {
None
}
}
///|
fn apply_namespace_declaration(
scope : Map[String, String],
declaration : NamespaceDeclaration,
) -> Unit raise XmlErrorKind {
let uri = declaration.namespace_uri
match declaration.prefix {
None => {
if uri == xml_namespace_uri() || uri == xmlns_namespace_uri() {
raise InvalidSyntax(
"reserved namespace URI cannot be the default namespace",
)
}
if uri == "" {
scope.remove("")
} else {
scope.set("", uri)
}
}
Some(prefix) => {
if prefix == "xmlns" {
raise InvalidSyntax("namespace prefix 'xmlns' is reserved")
}
if prefix == "xml" {
if uri != xml_namespace_uri() {
raise InvalidSyntax(
"prefix 'xml' must be bound to " + xml_namespace_uri(),
)
}
} else {
if uri == "" {
raise InvalidSyntax(
"namespace prefix cannot be bound to an empty URI",
)
}
if uri == xml_namespace_uri() {
raise InvalidSyntax(
"only prefix 'xml' may be bound to " + xml_namespace_uri(),
)
}
}
if uri == xmlns_namespace_uri() {
raise InvalidSyntax("namespace URI " + uri + " is reserved")
}
scope.set(prefix, uri)
}
}
}
///|
fn NamespaceReader::copy_current_scope(
self : NamespaceReader,
) -> Map[String, String] {
let result : Map[String, String] = Map([])
let current = self.scopes[self.scopes.length() - 1]
for prefix, uri in current {
result.set(prefix, uri)
}
result
}
///|
fn resolve_xml_name(
qualified_name : String,
scope : Map[String, String],
use_default_namespace : Bool,
) -> XmlName raise XmlErrorKind {
let (prefix, local_name) = split_qualified_name(qualified_name)
let namespace_uri = match prefix {
Some("xmlns") => raise InvalidSyntax("namespace prefix 'xmlns' is reserved")
Some(prefix) =>
match scope.get(prefix) {
Some(uri) => Some(uri)
None => raise InvalidSyntax("undeclared namespace prefix: " + prefix)
}
None => if use_default_namespace { scope.get("") } else { None }
}
{ qualified_name, prefix, local_name, namespace_uri, }
}
///|
fn NamespaceReader::resolve_element(
self : NamespaceReader,
element : XmlElement,
push_scope : Bool,
) -> NamespaceElement raise XmlErrorKind {
let scope = self.copy_current_scope()
let declarations : Array[NamespaceDeclaration] = []
for attribute in element.attributes {
match namespace_declaration(attribute) {
Some(declaration) => {
apply_namespace_declaration(scope, declaration)
declarations.push(declaration)
}
None => ()
}
}
let name = resolve_xml_name(element.name, scope, true)
let attributes : Array[NamespaceAttribute] = []
let expanded_names : Map[String, Bool] = Map([])
for attribute in element.attributes {
if namespace_declaration(attribute) is None {
let attribute_name = resolve_xml_name(attribute.name, scope, false)
let uri = attribute_name.namespace_uri.unwrap_or("")
let expanded_name = uri + "\u{0}" + attribute_name.local_name
if expanded_names.contains(expanded_name) {
let display_name = if uri == "" {
attribute_name.local_name
} else {
"{" + uri + "}" + attribute_name.local_name
}
raise InvalidSyntax(
"duplicate expanded attribute name: " + display_name,
)
}
expanded_names.set(expanded_name, true)
attributes.push({
name: attribute_name,
value: attribute.value,
span: attribute.span,
name_span: attribute.name_span,
value_span: attribute.value_span,
})
}
}
if push_scope {
self.scopes.push(scope)
}
{ name, attributes, namespace_declarations: declarations, }
}
///|
/// Read the next namespace-aware XML event.
pub fn NamespaceReader::read_event(
self : NamespaceReader,
) -> NamespaceEvent raise XmlError {
let event = self.reader.read_event()
let kind = try {
match event.kind {
Start(element) =>
NamespaceEventKind::Start(self.resolve_element(element, true))
Empty(element) => Empty(self.resolve_element(element, false))
End(qualified_name) => {
let scope = self.scopes[self.scopes.length() - 1]
let name = resolve_xml_name(qualified_name, scope, true)
if self.scopes.length() > 1 {
let _ = self.scopes.pop()
}
End(name)
}
Text(content) => Text(content)
CData(content) => CData(content)
Comment(content) => Comment(content)
PI(target~, data~) => PI(target~, data~)
Decl(version~, encoding~, standalone~) =>
Decl(version~, encoding~, standalone~)
DocType(name) => DocType(name)
Eof => Eof
}
} catch {
error => raise At(error~, span=event.span)
}
{ kind, span: event.span, }
}
///|
/// Read all namespace-aware events through the final `Eof` event.
pub fn NamespaceReader::read_events_until_eof(
self : NamespaceReader,
) -> Array[NamespaceEvent] raise XmlError {
let events : Array[NamespaceEvent] = []
for ;; {
let event = self.read_event()
events.push(event)
if event.kind is Eof {
return events
}
}
}