use ahash::HashMap;
use xot::xmlname::NameStrInfo;
use xot::Xot;
use xee_name::{Name, Namespaces};
use xee_xpath_ast::parse_name;
use xee_xpath_macros::xpath_fn;
use crate::atomic;
use crate::error;
use crate::function::StaticFunctionDescription;
use crate::interpreter::Interpreter;
use crate::wrap_xpath_fn;
#[xpath_fn("fn:resolve-QName($qname as xs:string?, $element as element()) as xs:QName?")]
fn resolve_qname(
interpreter: &Interpreter,
qname: Option<&str>,
node: xot::Node,
) -> error::Result<Option<atomic::Atomic>> {
if let Some(qname) = qname {
let namespaces = element_namespaces(node, interpreter.xot());
let name = parse_name(qname, &namespaces)?.value;
let name = name.with_default_namespace(namespaces.default_element_namespace());
Ok(Some(name.into()))
} else {
Ok(None)
}
}
fn element_namespaces(node: xot::Node, xot: &Xot) -> Namespaces {
let mut m = HashMap::default();
let mut default_element_namespace = "";
for (prefix_id, namespace_id) in xot.namespaces_in_scope(node) {
let prefix = xot.prefix_str(prefix_id).to_string();
if m.contains_key(&prefix) {
continue;
}
if xot.empty_prefix() == prefix_id {
default_element_namespace = xot.namespace_str(namespace_id);
}
let namespace = xot.namespace_str(namespace_id).to_string();
m.insert(prefix, namespace);
}
Namespaces::new(m, default_element_namespace.to_string(), "".to_string())
}
#[xpath_fn("fn:QName($paramURI as xs:string?, $paramQName as xs:string) as xs:QName")]
fn qname(param_uri: Option<&str>, param_qname: &str) -> error::Result<atomic::Atomic> {
let param_uri = param_uri.unwrap_or("");
let mut prefix_split = param_qname.split(':');
let pairs = if let Some(prefix) = prefix_split.next() {
if prefix_split.next().is_some() {
if param_uri.is_empty() {
return Err(error::Error::FOCA0002);
}
vec![(prefix.to_string(), param_uri.to_string())]
} else {
vec![("".to_string(), param_uri.to_string())]
}
} else {
vec![("".to_string(), param_uri.to_string())]
};
let pairs = HashMap::from_iter(pairs);
let namespaces = Namespaces::new(pairs, "".to_string(), "".to_string());
let name = parse_name(param_qname, &namespaces)
.map_err(|_| error::Error::FOCA0002)?
.value;
if name.namespace().is_empty() && !param_uri.is_empty() {
Ok(name.with_default_namespace(param_uri).into())
} else {
Ok(name.into())
}
}
#[xpath_fn("fn:prefix-from-QName($arg as xs:QName?) as xs:NCName?")]
fn prefix_from_qname(arg: Option<Name>) -> error::Result<Option<atomic::Atomic>> {
if let Some(arg) = arg {
let prefix = arg.prefix();
if !prefix.is_empty() {
Ok(Some(atomic::Atomic::String(
atomic::StringType::NCName,
prefix.to_string().into(),
)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
#[xpath_fn("fn:local-name-from-QName($arg as xs:QName?) as xs:NCName?")]
fn local_name_from_qname(arg: Option<Name>) -> error::Result<Option<atomic::Atomic>> {
if let Some(arg) = arg {
Ok(Some(atomic::Atomic::String(
atomic::StringType::NCName,
arg.local_name().to_string().into(),
)))
} else {
Ok(None)
}
}
#[xpath_fn("fn:namespace-uri-from-QName($arg as xs:QName?) as xs:anyURI?")]
fn namespace_uri_from_qname(arg: Option<Name>) -> error::Result<Option<atomic::Atomic>> {
if let Some(arg) = arg {
let namespace = arg.namespace();
if !namespace.is_empty() {
Ok(Some(atomic::Atomic::String(
atomic::StringType::AnyURI,
namespace.to_string().into(),
)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
#[xpath_fn(
"fn:namespace-uri-for-prefix($prefix as xs:string?, $element as element()) as xs:anyURI?"
)]
fn namespace_uri_for_prefix(
interpreter: &Interpreter,
prefix: Option<&str>,
node: xot::Node,
) -> error::Result<Option<atomic::Atomic>> {
if let Some(prefix) = prefix {
let namespaces = element_namespaces(node, interpreter.xot());
Ok(namespaces
.by_prefix(prefix)
.map(|s| atomic::Atomic::String(atomic::StringType::AnyURI, s.to_string().into())))
} else {
Ok(None)
}
}
#[xpath_fn("fn:in-scope-prefixes($element as element()) as xs:string*")]
fn in_scope_prefixes(interpreter: &Interpreter, node: xot::Node) -> Vec<atomic::Atomic> {
let xot = interpreter.xot();
xot.namespaces_in_scope(node)
.map(|(prefix, _)| xot.prefix_str(prefix).to_string().into())
.collect::<Vec<_>>()
}
pub(crate) fn static_function_descriptions() -> Vec<StaticFunctionDescription> {
vec![
wrap_xpath_fn!(resolve_qname),
wrap_xpath_fn!(qname),
wrap_xpath_fn!(prefix_from_qname),
wrap_xpath_fn!(local_name_from_qname),
wrap_xpath_fn!(namespace_uri_from_qname),
wrap_xpath_fn!(namespace_uri_for_prefix),
wrap_xpath_fn!(in_scope_prefixes),
]
}