use std::fmt;
use super::XmlTypeCode;
use crate::ids::{SimpleTypeKey, TypeKey};
use crate::namespace::qname::QualifiedName;
use crate::schema::model::DerivationSet;
use crate::types::value::XmlValue;
use crate::xpath::cast::type_matches;
use crate::xpath::context::XPathContext;
use crate::xpath::iterator::XmlItem;
use crate::xpath::{DomNavigator, DomNodeType};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum XmlTypeCardinality {
#[default]
One,
ZeroOrOne,
OneOrMore,
ZeroOrMore,
Empty,
}
impl XmlTypeCardinality {
pub fn allows_empty(&self) -> bool {
matches!(self, Self::ZeroOrOne | Self::ZeroOrMore | Self::Empty)
}
pub fn allows_many(&self) -> bool {
matches!(self, Self::OneOrMore | Self::ZeroOrMore)
}
pub fn matches_count(&self, count: usize) -> bool {
match self {
Self::One => count == 1,
Self::ZeroOrOne => count <= 1,
Self::OneOrMore => count >= 1,
Self::ZeroOrMore => true,
Self::Empty => count == 0,
}
}
pub fn symbol(&self) -> &'static str {
match self {
Self::One => "",
Self::ZeroOrOne => "?",
Self::OneOrMore => "+",
Self::ZeroOrMore => "*",
Self::Empty => "", }
}
}
impl fmt::Display for XmlTypeCardinality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.symbol())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ItemType {
AnyItem,
AnyNode,
Document(Option<Box<ItemType>>),
Element(Option<NameTest>, Option<SimpleTypeKey>),
Attribute(Option<NameTest>, Option<SimpleTypeKey>),
SchemaElement(QualifiedName),
SchemaAttribute(QualifiedName),
Text,
Comment,
ProcessingInstruction(Option<String>),
NamespaceNode,
AtomicType(XmlTypeCode),
SchemaAtomicType(SimpleTypeKey),
}
impl ItemType {
pub fn type_code(&self) -> XmlTypeCode {
match self {
Self::AnyItem => XmlTypeCode::Item,
Self::AnyNode => XmlTypeCode::Node,
Self::Document(_) => XmlTypeCode::Document,
Self::Element(_, _) | Self::SchemaElement(_) => XmlTypeCode::Element,
Self::Attribute(_, _) | Self::SchemaAttribute(_) => XmlTypeCode::Attribute,
Self::Text => XmlTypeCode::Text,
Self::Comment => XmlTypeCode::Comment,
Self::ProcessingInstruction(_) => XmlTypeCode::ProcessingInstruction,
Self::NamespaceNode => XmlTypeCode::Namespace,
Self::AtomicType(code) => *code,
Self::SchemaAtomicType(_) => XmlTypeCode::AnyAtomicType,
}
}
pub fn is_node(&self) -> bool {
matches!(
self,
Self::AnyNode
| Self::Document(_)
| Self::Element(_, _)
| Self::Attribute(_, _)
| Self::SchemaElement(_)
| Self::SchemaAttribute(_)
| Self::Text
| Self::Comment
| Self::ProcessingInstruction(_)
| Self::NamespaceNode
)
}
pub fn is_atomic(&self) -> bool {
matches!(self, Self::AtomicType(_) | Self::SchemaAtomicType(_))
}
pub fn matches_item<N: DomNavigator>(&self, item: &XmlItem<N>, ctx: &XPathContext<'_>) -> bool {
match item {
XmlItem::Node(nav) => self.matches_node(nav, ctx),
XmlItem::Atomic(value) => self.matches_atomic(value, ctx),
}
}
fn matches_node<N: DomNavigator>(&self, nav: &N, ctx: &XPathContext<'_>) -> bool {
match self {
Self::AnyItem => true,
Self::AnyNode => true,
Self::Document(None) => nav.node_type() == DomNodeType::Root,
Self::Document(Some(inner)) => {
if nav.node_type() != DomNodeType::Root {
return false;
}
let mut cursor = nav.clone();
if !cursor.move_to_first_child() {
return false;
}
let mut element_count = 0;
let mut matching_element: Option<N> = None;
loop {
let node_type = cursor.node_type();
match node_type {
DomNodeType::Element => {
element_count += 1;
if element_count > 1 {
return false;
}
matching_element = Some(cursor.clone());
}
DomNodeType::Comment | DomNodeType::ProcessingInstruction => {
}
DomNodeType::Text
| DomNodeType::Whitespace
| DomNodeType::SignificantWhitespace => {
}
_ => {
}
}
if !cursor.move_to_next_sibling() {
break;
}
}
if element_count != 1 {
return false;
}
match matching_element {
Some(elem) => inner.matches_node(&elem, ctx),
None => false,
}
}
Self::Element(name_test, schema_type) => {
if nav.node_type() != DomNodeType::Element {
return false;
}
if let Some(test) = name_test {
if !Self::matches_name_test(test, nav, ctx) {
return false;
}
}
if let Some(expected) = schema_type {
if !Self::matches_schema_type(nav, *expected, ctx) {
return false;
}
}
true
}
Self::Attribute(name_test, schema_type) => {
if nav.node_type() != DomNodeType::Attribute {
return false;
}
if let Some(test) = name_test {
if !Self::matches_name_test(test, nav, ctx) {
return false;
}
}
if let Some(expected) = schema_type {
if !Self::matches_schema_type(nav, *expected, ctx) {
return false;
}
}
true
}
Self::SchemaElement(name) => {
if nav.node_type() != DomNodeType::Element {
return false;
}
if !Self::matches_qname(name, nav, ctx) {
return false;
}
Self::matches_schema_element_decl(nav, name, ctx)
}
Self::SchemaAttribute(name) => {
if nav.node_type() != DomNodeType::Attribute {
return false;
}
if !Self::matches_qname(name, nav, ctx) {
return false;
}
Self::matches_schema_attribute_decl(nav, name, ctx)
}
Self::Text => nav.node_type().is_text_like(),
Self::Comment => nav.node_type() == DomNodeType::Comment,
Self::ProcessingInstruction(target) => {
nav.node_type() == DomNodeType::ProcessingInstruction
&& target.as_ref().is_none_or(|name| nav.local_name() == name)
}
Self::NamespaceNode => nav.node_type() == DomNodeType::Namespace,
Self::AtomicType(_) | Self::SchemaAtomicType(_) => false,
}
}
fn matches_atomic(&self, value: &XmlValue, ctx: &XPathContext<'_>) -> bool {
match self {
Self::AnyItem => true,
Self::AnyNode => false, Self::AtomicType(code) => type_matches(value.type_code, *code),
Self::SchemaAtomicType(key) => {
if let Some(value_key) = value.schema_type {
if let Some(schema_set) = ctx.schema_set {
return schema_set.is_type_derived_from(
TypeKey::Simple(value_key),
TypeKey::Simple(*key),
DerivationSet::empty(),
);
}
return value_key == *key;
}
false
}
Self::Document(_)
| Self::Element(_, _)
| Self::Attribute(_, _)
| Self::SchemaElement(_)
| Self::SchemaAttribute(_)
| Self::Text
| Self::Comment
| Self::ProcessingInstruction(_)
| Self::NamespaceNode => false,
}
}
fn matches_name_test<N: DomNavigator>(
test: &NameTest,
nav: &N,
ctx: &XPathContext<'_>,
) -> bool {
match test {
NameTest::Wildcard => true,
NameTest::NamespaceWildcard(local_id) => {
match ctx.resolve_name(*local_id) {
Some(local) => nav.local_name() == local,
None => false,
}
}
NameTest::LocalWildcard(ns_id) => {
match ctx.resolve_name(*ns_id) {
Some(ns) => nav.namespace_uri() == ns,
None => false,
}
}
NameTest::QName(qname) => Self::matches_qname(qname, nav, ctx),
}
}
fn matches_qname<N: DomNavigator>(
qname: &QualifiedName,
nav: &N,
ctx: &XPathContext<'_>,
) -> bool {
let local = match ctx.resolve_name(qname.local_name) {
Some(local) => local,
None => return false,
};
let ns = match qname.namespace_uri {
Some(id) => match ctx.resolve_name(id) {
Some(ns) => ns,
None => return false,
},
None => String::new(),
};
nav.local_name() == local && nav.namespace_uri() == ns
}
fn matches_schema_type<N: DomNavigator>(
nav: &N,
expected: SimpleTypeKey,
ctx: &XPathContext<'_>,
) -> bool {
if let Some(actual) = nav.schema_type() {
if let Some(schema_set) = ctx.schema_set {
return schema_set.is_type_derived_from(
TypeKey::Simple(actual),
TypeKey::Simple(expected),
DerivationSet::empty(),
);
}
return actual == expected;
}
false
}
fn matches_schema_element_decl<N: DomNavigator>(
nav: &N,
name: &QualifiedName,
ctx: &XPathContext<'_>,
) -> bool {
if let Some(schema_set) = ctx.schema_set {
let ns_id = name.namespace_uri;
let Some(elem_key) = schema_set.lookup_element(ns_id, name.local_name) else {
return false;
};
let Some(elem_data) = schema_set.arenas.elements.get(elem_key) else {
return false;
};
if let Some(expected_type) = elem_data.resolved_type {
let Some(actual_type) = nav.schema_type() else {
return false;
};
return schema_set.is_type_derived_from(
TypeKey::Simple(actual_type),
expected_type,
DerivationSet::empty(),
);
}
return true;
}
true
}
fn matches_schema_attribute_decl<N: DomNavigator>(
nav: &N,
name: &QualifiedName,
ctx: &XPathContext<'_>,
) -> bool {
if let Some(schema_set) = ctx.schema_set {
let ns_id = name.namespace_uri;
let Some(attr_key) = schema_set.lookup_attribute(ns_id, name.local_name) else {
return false;
};
let Some(attr_data) = schema_set.arenas.attributes.get(attr_key) else {
return false;
};
if let Some(expected_type) = attr_data.resolved_type {
let Some(actual_type) = nav.schema_type() else {
return false;
};
return schema_set.is_type_derived_from(
TypeKey::Simple(actual_type),
expected_type,
DerivationSet::empty(),
);
}
return true;
}
true
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NameTest {
Wildcard,
NamespaceWildcard(crate::ids::NameId),
LocalWildcard(crate::ids::NameId),
QName(QualifiedName),
}
impl NameTest {
pub fn is_wildcard(&self) -> bool {
matches!(self, Self::Wildcard)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SequenceType {
pub item_type: ItemType,
pub cardinality: XmlTypeCardinality,
}
impl SequenceType {
pub fn new(item_type: ItemType, cardinality: XmlTypeCardinality) -> Self {
Self {
item_type,
cardinality,
}
}
pub fn one(item_type: ItemType) -> Self {
Self::new(item_type, XmlTypeCardinality::One)
}
pub fn optional(item_type: ItemType) -> Self {
Self::new(item_type, XmlTypeCardinality::ZeroOrOne)
}
pub fn plus(item_type: ItemType) -> Self {
Self::new(item_type, XmlTypeCardinality::OneOrMore)
}
pub fn star(item_type: ItemType) -> Self {
Self::new(item_type, XmlTypeCardinality::ZeroOrMore)
}
pub fn empty() -> Self {
Self::new(ItemType::AnyItem, XmlTypeCardinality::Empty)
}
pub fn is_empty_sequence(&self) -> bool {
self.cardinality == XmlTypeCardinality::Empty
}
pub fn any() -> Self {
Self::star(ItemType::AnyItem)
}
pub fn item() -> Self {
Self::one(ItemType::AnyItem)
}
pub fn node() -> Self {
Self::one(ItemType::AnyNode)
}
pub fn nodes() -> Self {
Self::star(ItemType::AnyNode)
}
pub fn string() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::String))
}
pub fn string_optional() -> Self {
Self::optional(ItemType::AtomicType(XmlTypeCode::String))
}
pub fn boolean() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Boolean))
}
pub fn integer() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Integer))
}
pub fn integer_optional() -> Self {
Self::optional(ItemType::AtomicType(XmlTypeCode::Integer))
}
pub fn decimal() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Decimal))
}
pub fn double() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Double))
}
pub fn double_optional() -> Self {
Self::optional(ItemType::AtomicType(XmlTypeCode::Double))
}
pub fn any_atomic() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
}
pub fn any_atomic_optional() -> Self {
Self::optional(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
}
pub fn any_atomic_star() -> Self {
Self::star(ItemType::AtomicType(XmlTypeCode::AnyAtomicType))
}
pub fn datetime() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::DateTime))
}
pub fn date() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Date))
}
pub fn time() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Time))
}
pub fn duration() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::Duration))
}
pub fn qname() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::QName))
}
pub fn any_uri() -> Self {
Self::one(ItemType::AtomicType(XmlTypeCode::AnyUri))
}
pub fn type_code(&self) -> XmlTypeCode {
self.item_type.type_code()
}
pub fn is_node(&self) -> bool {
self.item_type.is_node()
}
pub fn is_atomic(&self) -> bool {
self.item_type.is_atomic()
}
pub fn is_numeric(&self) -> bool {
self.type_code().is_numeric()
}
pub fn allows_empty(&self) -> bool {
self.cardinality.allows_empty()
}
pub fn matches_sequence<N: DomNavigator>(
&self,
items: &[XmlItem<N>],
ctx: &XPathContext<'_>,
) -> bool {
if !self.cardinality.matches_count(items.len()) {
return false;
}
for item in items {
if !self.item_type.matches_item(item, ctx) {
return false;
}
}
true
}
}
impl Default for SequenceType {
fn default() -> Self {
Self::any()
}
}
impl fmt::Display for SequenceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.cardinality == XmlTypeCardinality::Empty {
return write!(f, "empty-sequence()");
}
match &self.item_type {
ItemType::AnyItem => write!(f, "item()")?,
ItemType::AnyNode => write!(f, "node()")?,
ItemType::Document(None) => write!(f, "document-node()")?,
ItemType::Document(Some(elem)) => write!(f, "document-node({:?})", elem)?,
ItemType::Element(None, None) => write!(f, "element()")?,
ItemType::Element(Some(name), None) => write!(f, "element({:?})", name)?,
ItemType::Element(name, Some(_type_key)) => {
write!(f, "element({:?}, ...)", name)?;
}
ItemType::Attribute(None, None) => write!(f, "attribute()")?,
ItemType::Attribute(Some(name), None) => write!(f, "attribute({:?})", name)?,
ItemType::Attribute(name, Some(_type_key)) => {
write!(f, "attribute({:?}, ...)", name)?;
}
ItemType::SchemaElement(qn) => write!(f, "schema-element({:?})", qn)?,
ItemType::SchemaAttribute(qn) => write!(f, "schema-attribute({:?})", qn)?,
ItemType::Text => write!(f, "text()")?,
ItemType::Comment => write!(f, "comment()")?,
ItemType::ProcessingInstruction(None) => write!(f, "processing-instruction()")?,
ItemType::ProcessingInstruction(Some(name)) => {
write!(f, "processing-instruction({})", name)?;
}
ItemType::NamespaceNode => write!(f, "namespace-node()")?,
ItemType::AtomicType(code) => {
if let Some(name) = code.local_name() {
write!(f, "xs:{}", name)?;
} else {
write!(f, "{:?}", code)?;
}
}
ItemType::SchemaAtomicType(_key) => write!(f, "schema-type(...)")?,
}
write!(f, "{}", self.cardinality)?;
Ok(())
}
}
#[cfg(feature = "xsd11")]
pub fn resolve_list_item_schema_type(
list_type_key: SimpleTypeKey,
schema_set: &crate::schema::SchemaSet,
) -> Option<SimpleTypeKey> {
let st_data = schema_set.arenas.simple_types.get(list_type_key)?;
st_data.resolved_item_type?.as_simple()
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use crate::namespace::table::NameTable;
use crate::navigator::RoXmlNavigator;
#[test]
fn test_cardinality_matches_count() {
assert!(XmlTypeCardinality::One.matches_count(1));
assert!(!XmlTypeCardinality::One.matches_count(0));
assert!(!XmlTypeCardinality::One.matches_count(2));
assert!(XmlTypeCardinality::ZeroOrOne.matches_count(0));
assert!(XmlTypeCardinality::ZeroOrOne.matches_count(1));
assert!(!XmlTypeCardinality::ZeroOrOne.matches_count(2));
assert!(!XmlTypeCardinality::OneOrMore.matches_count(0));
assert!(XmlTypeCardinality::OneOrMore.matches_count(1));
assert!(XmlTypeCardinality::OneOrMore.matches_count(100));
assert!(XmlTypeCardinality::ZeroOrMore.matches_count(0));
assert!(XmlTypeCardinality::ZeroOrMore.matches_count(1));
assert!(XmlTypeCardinality::ZeroOrMore.matches_count(100));
}
#[test]
fn test_cardinality_symbols() {
assert_eq!(XmlTypeCardinality::One.symbol(), "");
assert_eq!(XmlTypeCardinality::ZeroOrOne.symbol(), "?");
assert_eq!(XmlTypeCardinality::OneOrMore.symbol(), "+");
assert_eq!(XmlTypeCardinality::ZeroOrMore.symbol(), "*");
}
#[test]
fn test_sequence_type_display() {
assert_eq!(SequenceType::item().to_string(), "item()");
assert_eq!(SequenceType::string().to_string(), "xs:string");
assert_eq!(SequenceType::string_optional().to_string(), "xs:string?");
assert_eq!(SequenceType::nodes().to_string(), "node()*");
assert_eq!(SequenceType::integer().to_string(), "xs:integer");
}
#[test]
fn test_sequence_type_is_node() {
assert!(SequenceType::node().is_node());
assert!(SequenceType::nodes().is_node());
assert!(!SequenceType::string().is_node());
assert!(!SequenceType::any_atomic().is_node());
}
#[test]
fn test_sequence_type_is_atomic() {
assert!(SequenceType::string().is_atomic());
assert!(SequenceType::integer().is_atomic());
assert!(SequenceType::any_atomic().is_atomic());
assert!(!SequenceType::node().is_atomic());
assert!(!SequenceType::item().is_atomic());
}
#[test]
fn test_sequence_type_is_numeric() {
assert!(SequenceType::decimal().is_numeric());
assert!(SequenceType::integer().is_numeric());
assert!(SequenceType::double().is_numeric());
assert!(!SequenceType::string().is_numeric());
assert!(!SequenceType::boolean().is_numeric());
}
#[test]
fn test_item_type_type_code() {
assert_eq!(ItemType::AnyItem.type_code(), XmlTypeCode::Item);
assert_eq!(ItemType::AnyNode.type_code(), XmlTypeCode::Node);
assert_eq!(ItemType::Text.type_code(), XmlTypeCode::Text);
assert_eq!(
ItemType::AtomicType(XmlTypeCode::String).type_code(),
XmlTypeCode::String
);
}
#[test]
fn test_cardinality_one() {
let seq_type = SequenceType::integer();
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
];
assert!(seq_type.matches_sequence(&one_item, &ctx));
assert!(!seq_type.matches_sequence(&no_items, &ctx));
assert!(!seq_type.matches_sequence(&two_items, &ctx));
}
#[test]
fn test_cardinality_optional() {
let seq_type = SequenceType::integer_optional();
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
];
assert!(seq_type.matches_sequence(&one_item, &ctx));
assert!(seq_type.matches_sequence(&no_items, &ctx));
assert!(!seq_type.matches_sequence(&two_items, &ctx));
}
#[test]
fn test_cardinality_star() {
let seq_type = SequenceType::star(ItemType::AtomicType(XmlTypeCode::Integer));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
let three_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
];
assert!(seq_type.matches_sequence(&no_items, &ctx));
assert!(seq_type.matches_sequence(&one_item, &ctx));
assert!(seq_type.matches_sequence(&three_items, &ctx));
}
#[test]
fn test_cardinality_plus() {
let seq_type = SequenceType::plus(ItemType::AtomicType(XmlTypeCode::Integer));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let no_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
let three_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(3))),
];
assert!(!seq_type.matches_sequence(&no_items, &ctx));
assert!(seq_type.matches_sequence(&one_item, &ctx));
assert!(seq_type.matches_sequence(&three_items, &ctx));
}
#[test]
fn test_atomic_integer_match() {
let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let int_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
let str_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::string("hello"));
assert!(item_type.matches_item(&int_value, &ctx));
assert!(!item_type.matches_item(&str_value, &ctx));
}
#[test]
fn test_atomic_string_match() {
let item_type = ItemType::AtomicType(XmlTypeCode::String);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let str_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::string("hello"));
let int_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
assert!(item_type.matches_item(&str_value, &ctx));
assert!(!item_type.matches_item(&int_value, &ctx));
}
#[test]
fn test_atomic_type_hierarchy() {
let item_type = ItemType::AtomicType(XmlTypeCode::AnyAtomicType);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let str_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::string("hello"));
let int_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
let bool_value: XmlItem<RoXmlNavigator<'static>> = XmlItem::Atomic(XmlValue::boolean(true));
assert!(item_type.matches_item(&str_value, &ctx));
assert!(item_type.matches_item(&int_value, &ctx));
assert!(item_type.matches_item(&bool_value, &ctx));
}
#[test]
fn test_item_matches_any() {
let item_type = ItemType::AnyItem;
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
let mut nav = RoXmlNavigator::new(&doc);
nav.move_to_first_child();
let node_item = XmlItem::Node(nav);
let atomic_item: XmlItem<RoXmlNavigator<'_>> = XmlItem::Atomic(XmlValue::string("hello"));
assert!(item_type.matches_item(&node_item, &ctx));
assert!(item_type.matches_item(&atomic_item, &ctx));
}
#[test]
fn test_node_rejects_atomic() {
let item_type = ItemType::AnyNode;
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let atomic_item: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::string("hello"));
assert!(!item_type.matches_item(&atomic_item, &ctx));
}
#[test]
fn test_atomic_rejects_node() {
let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
let mut nav = RoXmlNavigator::new(&doc);
nav.move_to_first_child();
let node_item = XmlItem::Node(nav);
assert!(!item_type.matches_item(&node_item, &ctx));
}
#[test]
fn test_element_item_type_matches_element() {
let item_type = ItemType::Element(None, None);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
let mut nav = RoXmlNavigator::new(&doc);
nav.move_to_first_child();
let node_item = XmlItem::Node(nav);
assert!(item_type.matches_item(&node_item, &ctx));
}
#[test]
fn test_text_item_type_matches_text() {
let item_type = ItemType::Text;
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root>text</root>").expect("parse xml");
let mut nav = RoXmlNavigator::new(&doc);
nav.move_to_first_child(); nav.move_to_first_child();
let node_item = XmlItem::Node(nav);
assert!(item_type.matches_item(&node_item, &ctx));
}
#[test]
fn test_sequence_type_mixed_types_fail() {
let seq_type = SequenceType::star(ItemType::AtomicType(XmlTypeCode::Integer));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let mixed_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::string("not an integer")),
];
assert!(!seq_type.matches_sequence(&mixed_items, &ctx));
}
#[test]
fn test_integer_derived_types() {
let item_type = ItemType::AtomicType(XmlTypeCode::Integer);
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let int_value: XmlItem<RoXmlNavigator<'static>> =
XmlItem::Atomic(XmlValue::integer(BigInt::from(42)));
assert!(item_type.matches_item(&int_value, &ctx));
}
#[test]
fn test_empty_sequence_matches_empty() {
let seq_type = SequenceType::empty();
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let empty: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![];
assert!(seq_type.matches_sequence(&empty, &ctx));
}
#[test]
fn test_empty_sequence_rejects_non_empty() {
let seq_type = SequenceType::empty();
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let one_item: Vec<XmlItem<RoXmlNavigator<'static>>> =
vec![XmlItem::Atomic(XmlValue::integer(BigInt::from(42)))];
let two_items: Vec<XmlItem<RoXmlNavigator<'static>>> = vec![
XmlItem::Atomic(XmlValue::integer(BigInt::from(1))),
XmlItem::Atomic(XmlValue::integer(BigInt::from(2))),
];
assert!(!seq_type.matches_sequence(&one_item, &ctx));
assert!(!seq_type.matches_sequence(&two_items, &ctx));
}
#[test]
fn test_empty_sequence_display() {
assert_eq!(SequenceType::empty().to_string(), "empty-sequence()");
}
#[test]
fn test_is_empty_sequence() {
assert!(SequenceType::empty().is_empty_sequence());
assert!(!SequenceType::any().is_empty_sequence());
assert!(!SequenceType::integer().is_empty_sequence());
}
#[test]
fn test_document_node_with_element_single_element() {
let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(item_type.matches_node(&nav, &ctx));
}
#[test]
fn test_document_node_with_element_allows_comments() {
let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<!-- comment --><root/><!-- another -->")
.expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(item_type.matches_node(&nav, &ctx));
}
#[test]
fn test_document_node_with_element_allows_pi() {
let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<?target data?><root/>").expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(item_type.matches_node(&nav, &ctx));
}
#[test]
fn test_document_node_with_element_rejects_no_element() {
let item_type = ItemType::Document(Some(Box::new(ItemType::Element(None, None))));
let table = NameTable::new();
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<!-- comment only is invalid XML --><dummy/>")
.expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(item_type.matches_node(&nav, &ctx));
}
#[test]
fn test_document_node_with_specific_element_name() {
let table = NameTable::new();
let root_id = table.add("root");
let qname = QualifiedName::local(root_id);
let name_test = NameTest::QName(qname);
let item_type =
ItemType::Document(Some(Box::new(ItemType::Element(Some(name_test), None))));
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<root/>").expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(item_type.matches_node(&nav, &ctx));
}
#[test]
fn test_document_node_with_wrong_element_name() {
let table = NameTable::new();
let root_id = table.add("root");
let qname = QualifiedName::local(root_id);
let name_test = NameTest::QName(qname);
let item_type =
ItemType::Document(Some(Box::new(ItemType::Element(Some(name_test), None))));
let ctx = XPathContext::new(&table);
let doc = roxmltree::Document::parse("<other/>").expect("parse xml");
let nav = RoXmlNavigator::new(&doc);
assert!(!item_type.matches_node(&nav, &ctx));
}
}