use crate::xpath::context::DynamicContext;
use crate::xpath::error::XPathError;
use crate::xpath::DomNavigator;
use crate::xpath::{DomNodeType, NamespaceAxisScope};
use super::{atomize_to_single_opt, atomize_to_string_opt, XPathValue};
use crate::namespace::qname::{is_ncname, QualifiedName};
use crate::types::value::{XmlAtomicValue, XmlValue, XmlValueKind};
use crate::types::XmlTypeCode;
use crate::xpath::iterator::XmlItem;
pub fn resolve_qname<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 2 {
return Err(XPathError::wrong_number_of_arguments(
"resolve-QName",
2,
args.len(),
));
}
let element = get_element_arg(&mut args)?;
let qname_arg = args.remove(0);
let qname_str = atomize_to_string_opt(qname_arg)?;
let qname_str = match qname_str {
None => return Ok(XPathValue::Empty),
Some(s) if s.is_empty() => {
return Err(XPathError::invalid_cast_value("", "xs:QName"));
}
Some(s) => s,
};
let (prefix, local_name) = parse_lexical_qname(&qname_str)?;
let namespace_uri = lookup_namespace_for_prefix(&element, prefix.as_deref())?;
let qn = create_qname_value(
context,
namespace_uri.as_deref(),
&local_name,
prefix.as_deref(),
);
Ok(qn)
}
pub fn qname_constructor<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 2 {
return Err(XPathError::wrong_number_of_arguments(
"QName",
2,
args.len(),
));
}
let local_arg = args.pop().unwrap();
let uri_arg = args.pop().unwrap();
let param_qname = super::atomize_to_string_required(local_arg)?;
let param_uri = atomize_to_string_opt(uri_arg)?;
let (prefix, local_name) = parse_lexical_qname(¶m_qname)?;
let namespace_uri = match param_uri {
None => None,
Some(s) if s.is_empty() => None,
Some(s) => Some(s),
};
if prefix.is_some() && namespace_uri.is_none() {
return Err(XPathError::FOCA0002 { qname: param_qname });
}
let qn = create_qname_value(
context,
namespace_uri.as_deref(),
&local_name,
prefix.as_deref(),
);
Ok(qn)
}
pub fn prefix_from_qname<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"prefix-from-QName",
1,
args.len(),
));
}
let arg = args.remove(0);
let qname = atomize_to_qname(context, arg)?;
match qname {
None => Ok(XPathValue::Empty),
Some(qn) => match qn.prefix {
None => Ok(XPathValue::Empty),
Some(prefix_id) => {
let prefix = context
.static_context
.names
.try_resolve(prefix_id)
.unwrap_or_default();
if prefix.is_empty() {
Ok(XPathValue::Empty)
} else {
Ok(XPathValue::string(prefix))
}
}
},
}
}
pub fn local_name_from_qname<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"local-name-from-QName",
1,
args.len(),
));
}
let arg = args.remove(0);
let qname = atomize_to_qname(context, arg)?;
match qname {
None => Ok(XPathValue::Empty),
Some(qn) => {
let local = context
.static_context
.names
.try_resolve(qn.local_name)
.unwrap_or_default();
Ok(XPathValue::string(local))
}
}
}
pub fn namespace_uri_from_qname<N: DomNavigator>(
context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"namespace-uri-from-QName",
1,
args.len(),
));
}
let arg = args.remove(0);
let qname = atomize_to_qname(context, arg)?;
match qname {
None => Ok(XPathValue::Empty),
Some(qn) => {
match qn.namespace_uri {
None => {
Ok(make_any_uri(""))
}
Some(ns_id) => {
let ns = context
.static_context
.names
.try_resolve(ns_id)
.unwrap_or_default();
Ok(make_any_uri(&ns))
}
}
}
}
}
pub fn namespace_uri_for_prefix<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 2 {
return Err(XPathError::wrong_number_of_arguments(
"namespace-uri-for-prefix",
2,
args.len(),
));
}
let element = get_element_arg(&mut args)?;
let prefix_arg = args.remove(0);
let prefix = atomize_to_string_opt(prefix_arg)?;
let is_default_ns_lookup = prefix.is_none() || prefix.as_deref() == Some("");
let namespace = lookup_namespace_for_prefix_opt(&element, prefix.as_deref());
match namespace {
Some(ns) => Ok(make_any_uri(&ns)),
None if is_default_ns_lookup => {
Ok(make_any_uri(""))
}
None => {
Ok(XPathValue::Empty)
}
}
}
pub fn in_scope_prefixes<N: DomNavigator>(
_context: &mut DynamicContext<'_, N>,
mut args: Vec<XPathValue<N>>,
) -> Result<XPathValue<N>, XPathError> {
if args.len() != 1 {
return Err(XPathError::wrong_number_of_arguments(
"in-scope-prefixes",
1,
args.len(),
));
}
let element = get_element_arg(&mut args)?;
let mut prefixes = Vec::new();
prefixes.push("xml".to_string());
let mut nav = element.clone();
if nav.move_to_first_namespace(NamespaceAxisScope::All) {
loop {
let prefix = nav.local_name();
if !prefixes.iter().any(|p| p == prefix) {
prefixes.push(prefix.to_string());
}
if !nav.move_to_next_namespace(NamespaceAxisScope::All) {
break;
}
}
}
let items: Vec<XmlItem<N>> = prefixes
.into_iter()
.map(|p| XmlItem::Atomic(XmlValue::string(p)))
.collect();
Ok(XPathValue::from_sequence(items))
}
pub fn parse_lexical_qname(qname: &str) -> Result<(Option<String>, String), XPathError> {
let qname = qname.trim();
if qname.is_empty() {
return Err(XPathError::invalid_cast_value(qname, "xs:QName"));
}
match qname.find(':') {
Some(pos) if pos > 0 && pos < qname.len() - 1 => {
let prefix = &qname[..pos];
let local = &qname[pos + 1..];
if !is_ncname(prefix) {
return Err(XPathError::invalid_cast_value(qname, "xs:QName"));
}
if !is_ncname(local) || local.contains(':') {
return Err(XPathError::invalid_cast_value(qname, "xs:QName"));
}
Ok((Some(prefix.to_string()), local.to_string()))
}
Some(_) => {
Err(XPathError::invalid_cast_value(qname, "xs:QName"))
}
None => {
if !is_ncname(qname) {
return Err(XPathError::invalid_cast_value(qname, "xs:QName"));
}
Ok((None, qname.to_string()))
}
}
}
fn get_element_arg<N: DomNavigator>(args: &mut Vec<XPathValue<N>>) -> Result<N, XPathError> {
let arg = args
.pop()
.ok_or_else(|| XPathError::type_mismatch("element()", "empty-sequence()"))?;
match arg {
XPathValue::Item(XmlItem::Node(nav)) => {
if nav.node_type() == DomNodeType::Element {
Ok(nav)
} else {
Err(XPathError::type_mismatch("element()", "node()"))
}
}
XPathValue::Sequence(items) if items.len() == 1 => {
if let Some(XmlItem::Node(nav)) = items.into_iter().next() {
if nav.node_type() == DomNodeType::Element {
Ok(nav)
} else {
Err(XPathError::type_mismatch("element()", "node()"))
}
} else {
Err(XPathError::type_mismatch("element()", "atomic value"))
}
}
_ => Err(XPathError::type_mismatch("element()", "sequence")),
}
}
fn atomize_to_qname<N: DomNavigator>(
_context: &DynamicContext<'_, N>,
value: XPathValue<N>,
) -> Result<Option<QualifiedName>, XPathError> {
let atomic = atomize_to_single_opt(value)?;
match atomic {
None => Ok(None),
Some(value) => {
if let Some(qn) = value.as_qname() {
Ok(Some(qn.clone()))
} else {
Err(XPathError::type_mismatch(
"xs:QName",
format!("{:?}", value.type_code),
))
}
}
}
}
fn lookup_namespace_for_prefix<N: DomNavigator>(
element: &N,
prefix: Option<&str>,
) -> Result<Option<String>, XPathError> {
let result = lookup_namespace_for_prefix_opt(element, prefix);
if prefix.is_some() && prefix != Some("") && result.is_none() {
return Err(XPathError::undefined_prefix(prefix.unwrap_or("")));
}
Ok(result)
}
fn lookup_namespace_for_prefix_opt<N: DomNavigator>(
element: &N,
prefix: Option<&str>,
) -> Option<String> {
let target_prefix = prefix.unwrap_or("");
let mut nav = element.clone();
if nav.move_to_first_namespace(NamespaceAxisScope::All) {
loop {
let ns_prefix = nav.local_name();
if ns_prefix == target_prefix {
let uri = nav.value();
if uri.is_empty() {
return None; }
return Some(uri);
}
if !nav.move_to_next_namespace(NamespaceAxisScope::All) {
break;
}
}
}
if target_prefix == "xml" {
return Some("http://www.w3.org/XML/1998/namespace".to_string());
}
None
}
fn make_any_uri<N: DomNavigator>(uri: &str) -> XPathValue<N> {
let value = XmlValue::new(
XmlTypeCode::AnyUri,
XmlValueKind::Atomic(XmlAtomicValue::AnyUri(uri.to_string())),
);
XPathValue::Item(XmlItem::Atomic(value))
}
fn create_qname_value<N: DomNavigator>(
context: &DynamicContext<'_, N>,
namespace_uri: Option<&str>,
local_name: &str,
prefix: Option<&str>,
) -> XPathValue<N> {
let names = context.static_context.names;
let local_id = names.add(local_name);
let ns_id = namespace_uri.map(|ns| names.add(ns));
let prefix_id = prefix.map(|p| names.add(p));
let qn = QualifiedName::new(ns_id, local_id, prefix_id);
let value = XmlValue::new(
XmlTypeCode::QName,
XmlValueKind::Atomic(XmlAtomicValue::QName(qn)),
);
XPathValue::Item(XmlItem::Atomic(value))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::namespace::table::NameTable;
use crate::xpath::context::XPathContext;
use crate::xpath::RoXmlNavigator;
fn create_context<'a>(names: &'a NameTable) -> DynamicContext<'a, RoXmlNavigator<'a>> {
let static_ctx = XPathContext::new(names);
let static_ctx = Box::leak(Box::new(static_ctx));
DynamicContext::new(static_ctx, 0)
}
#[test]
fn test_qname_constructor_and_extraction() {
let names = NameTable::new();
let mut ctx = create_context(&names);
let qname_result = qname_constructor(
&mut ctx,
vec![
XPathValue::string("http://example.com"),
XPathValue::string("p:local"),
],
)
.unwrap();
let prefix_result = prefix_from_qname(&mut ctx, vec![qname_result.clone()]).unwrap();
if let XPathValue::Item(XmlItem::Atomic(value)) = prefix_result {
assert_eq!(value.as_string(), Some("p"));
} else {
panic!("Expected string for prefix");
}
let local_result = local_name_from_qname(&mut ctx, vec![qname_result.clone()]).unwrap();
if let XPathValue::Item(XmlItem::Atomic(value)) = local_result {
assert_eq!(value.as_string(), Some("local"));
} else {
panic!("Expected string for local name");
}
let ns_result = namespace_uri_from_qname(&mut ctx, vec![qname_result]).unwrap();
if let XPathValue::Item(XmlItem::Atomic(value)) = ns_result {
assert_eq!(value.to_string_value(), "http://example.com");
} else {
panic!("Expected anyURI for namespace");
}
}
#[test]
fn test_qname_unprefixed() {
let names = NameTable::new();
let mut ctx = create_context(&names);
let qname_result = qname_constructor(
&mut ctx,
vec![
XPathValue::Empty, XPathValue::string("localonly"),
],
)
.unwrap();
let prefix_result = prefix_from_qname(&mut ctx, vec![qname_result.clone()]).unwrap();
assert!(matches!(prefix_result, XPathValue::Empty));
let local_result = local_name_from_qname(&mut ctx, vec![qname_result]).unwrap();
if let XPathValue::Item(XmlItem::Atomic(value)) = local_result {
assert_eq!(value.as_string(), Some("localonly"));
} else {
panic!("Expected string for local name");
}
}
#[test]
fn test_qname_prefix_with_no_namespace_error() {
let names = NameTable::new();
let mut ctx = create_context(&names);
let result = qname_constructor(
&mut ctx,
vec![
XPathValue::Empty, XPathValue::string("p:local"),
],
);
assert!(result.is_err());
if let Err(XPathError::FOCA0002 { .. }) = result {
} else {
panic!("Expected FOCA0002 error");
}
}
#[test]
fn test_parse_lexical_qname_prefixed() {
let (prefix, local) = parse_lexical_qname("xs:string").unwrap();
assert_eq!(prefix, Some("xs".to_string()));
assert_eq!(local, "string");
}
#[test]
fn test_parse_lexical_qname_unprefixed() {
let (prefix, local) = parse_lexical_qname("localName").unwrap();
assert_eq!(prefix, None);
assert_eq!(local, "localName");
}
#[test]
fn test_parse_lexical_qname_invalid() {
assert!(parse_lexical_qname("").is_err());
assert!(parse_lexical_qname(":local").is_err());
assert!(parse_lexical_qname("prefix:").is_err());
assert!(parse_lexical_qname("a:b:c").is_err());
assert!(parse_lexical_qname("123invalid").is_err());
}
#[test]
fn test_parse_lexical_qname_with_whitespace() {
let (prefix, local) = parse_lexical_qname(" xs:string ").unwrap();
assert_eq!(prefix, Some("xs".to_string()));
assert_eq!(local, "string");
}
}