mod common;
use fastxml::xpath::collect_text_values;
use fastxml::{evaluate, parse};
mod attribute_axis {
use super::*;
#[test]
fn test_attribute_shorthand() {
let xml = r#"<root><item id="1" name="first"/><item id="2" name="second"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item/@id").unwrap();
let values = collect_text_values(&result);
assert_eq!(values.len(), 2);
assert!(values.contains(&"1".to_string()));
assert!(values.contains(&"2".to_string()));
compare_with_libxml!(xpath: xml, "//item/@id", &doc);
}
#[test]
fn test_attribute_axis_explicit() {
let xml = r#"<root><item id="1" name="first"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item/attribute::id").unwrap();
let values = collect_text_values(&result);
assert_eq!(values.len(), 1);
assert_eq!(values[0], "1");
compare_with_libxml!(xpath: xml, "//item/attribute::id", &doc);
}
#[test]
fn test_attribute_wildcard() {
let xml = r#"<root><item id="1" name="first" type="A"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item/@*").unwrap();
let values = collect_text_values(&result);
assert_eq!(values.len(), 3);
assert!(values.contains(&"1".to_string()));
assert!(values.contains(&"first".to_string()));
assert!(values.contains(&"A".to_string()));
compare_with_libxml!(xpath: xml, "//item/@*", &doc);
}
#[test]
fn test_attribute_in_predicate() {
let xml = r#"<root>
<item id="1" status="active"/>
<item id="2" status="inactive"/>
<item id="3" status="active"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@status='active']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "//item[@status='active']", &doc);
}
#[test]
fn test_attribute_existence_check() {
let xml = r#"<root>
<item id="1"/>
<item name="no-id"/>
<item id="2"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@id]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "//item[@id]", &doc);
}
#[test]
fn test_multiple_attribute_predicates() {
let xml = r#"<root>
<item id="1" type="A" status="active"/>
<item id="2" type="A" status="inactive"/>
<item id="3" type="B" status="active"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@type='A' and @status='active']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
compare_with_libxml!(xpath: xml, "//item[@type='A' and @status='active']", &doc);
}
#[test]
fn test_attribute_or_predicates() {
let xml = r#"<root>
<item id="1" type="A"/>
<item id="2" type="B"/>
<item id="3" type="C"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@type='A' or @type='B']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "//item[@type='A' or @type='B']", &doc);
}
#[test]
fn test_attribute_not_predicate() {
let xml = r#"<root>
<item id="1" status="active"/>
<item id="2" status="inactive"/>
<item id="3" status="active"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[not(@status='active')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
compare_with_libxml!(xpath: xml, "//item[not(@status='active')]", &doc);
}
#[test]
fn test_attribute_wildcard_with_predicate() {
let xml = r#"<root>
<item id="1" name="first"/>
<item name="second"/>
<item id="3" type="B"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, ".//*[@*[local-name()='id']]").unwrap();
let nodes = result.into_nodes();
assert_eq!(
nodes.len(),
2,
"Only elements with an 'id' attribute should match, got {} matches",
nodes.len()
);
assert_eq!(nodes[0].get_attribute("id"), Some("1".to_string()));
assert_eq!(nodes[1].get_attribute("id"), Some("3".to_string()));
}
#[test]
fn test_attribute_wildcard_with_namespace_predicate() {
let xml = r#"<root xmlns:gml="http://www.opengis.net/gml"
xmlns:bldg="http://www.opengis.net/citygml/building/2.0">
<bldg:Building gml:id="building1">
<bldg:class codeSpace="http://example.com/code">1000</bldg:class>
<bldg:usage codeSpace="http://example.com/code">1010</bldg:usage>
<bldg:measuredHeight uom="m">10.5</bldg:measuredHeight>
</bldg:Building>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(
&doc,
".//*[@*[namespace-uri()='http://www.opengis.net/gml' and local-name()='id']]",
)
.unwrap();
let nodes = result.into_nodes();
assert_eq!(
nodes.len(),
1,
"Only the Building element with gml:id should match, got {} matches",
nodes.len()
);
assert_eq!(nodes[0].get_name(), "Building");
}
}
mod node_test {
use super::*;
#[test]
fn test_node_selects_all_nodes() {
let xml = r#"<root><child>text</child></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/node()").unwrap();
let nodes = result.into_nodes();
assert!(!nodes.is_empty());
compare_with_libxml!(xpath: xml, "/root/node()", &doc);
}
#[test]
fn test_node_with_descendant() {
let xml = r#"<root><a><b>text</b></a><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//node()").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 5);
compare_with_libxml!(xpath: xml, "//node()", &doc);
}
#[test]
fn test_node_vs_wildcard() {
let xml = r#"<root>text<child/>more text</root>"#;
let doc = parse(xml).unwrap();
let node_result = evaluate(&doc, "/root/node()").unwrap();
let wildcard_result = evaluate(&doc, "/root/*").unwrap();
let node_count = node_result.into_nodes().len();
let wildcard_count = wildcard_result.into_nodes().len();
assert!(node_count >= wildcard_count);
compare_with_libxml!(xpath: xml, "/root/node()", &doc);
compare_with_libxml!(xpath: xml, "/root/*", &doc);
}
#[test]
fn test_node_with_position() {
let xml = r#"<root><a/><b/><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/node()[2]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
compare_with_libxml!(xpath: xml, "/root/node()[2]", &doc);
}
}
mod complex_predicates {
use super::*;
#[test]
fn test_position_greater_than() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item><item>4</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() > 2]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_position_less_than() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item><item>4</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() < 3]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_position_range() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() >= 2 and position() <= 4]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
}
#[test]
fn test_string_length_predicate() {
let xml = r#"<root><item>ab</item><item>abcdef</item><item>abc</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[string-length() > 3]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("abcdef".to_string()));
}
#[test]
fn test_contains_in_predicate() {
let xml =
r#"<root><item>hello world</item><item>goodbye</item><item>world peace</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[contains(., 'world')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_starts_with_in_predicate() {
let xml = r#"<root><item>prefix_a</item><item>other</item><item>prefix_b</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[starts-with(., 'prefix')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_nested_predicate() {
let xml = r#"<root>
<parent>
<child status="active">keep</child>
<child status="inactive">skip</child>
</parent>
<parent>
<child status="active">also keep</child>
</parent>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//parent[child[@status='active']]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_count_in_predicate() {
let xml = r#"<root>
<group><item/><item/><item/></group>
<group><item/></group>
<group><item/><item/></group>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//group[count(item) >= 2]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_last_predicate() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[last()]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("3".to_string()));
}
#[test]
fn test_last_minus_one() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() = last() - 1]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("2".to_string()));
}
}
mod relative_paths {
use super::*;
use fastxml::{create_context, find_readonly_nodes_by_xpath, get_root_readonly_node};
#[test]
fn test_parent_navigation() {
let xml = r#"<root><parent><child/></parent></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/parent/child/..").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "parent");
}
#[test]
fn test_grandparent_navigation() {
let xml = r#"<root><parent><child><grandchild/></child></parent></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/parent/child/grandchild/../..").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "parent");
}
#[test]
fn test_self_reference() {
let xml = r#"<root><child/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/child/.").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "child");
}
#[test]
fn test_parent_then_sibling() {
let xml = r#"<root><a/><b/><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/b/../a").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "a");
}
#[test]
fn test_context_relative_descendant() {
let xml = r#"<root><parent><target>1</target></parent><target>2</target></root>"#;
let doc = parse(xml).unwrap();
let ctx = create_context(&doc).unwrap();
let root = get_root_readonly_node(&doc).unwrap();
let parents = find_readonly_nodes_by_xpath(&ctx, "//parent", &root).unwrap();
assert_eq!(parents.len(), 1);
let targets = find_readonly_nodes_by_xpath(&ctx, ".//target", &parents[0]).unwrap();
assert_eq!(targets.len(), 1);
assert_eq!(targets[0].get_content(), Some("1".to_string()));
}
#[test]
fn test_relative_wildcard() {
let xml = r#"<root><parent><a/><b/><c/></parent></root>"#;
let doc = parse(xml).unwrap();
let ctx = create_context(&doc).unwrap();
let root = get_root_readonly_node(&doc).unwrap();
let parents = find_readonly_nodes_by_xpath(&ctx, "//parent", &root).unwrap();
let children = find_readonly_nodes_by_xpath(&ctx, "./*", &parents[0]).unwrap();
assert_eq!(children.len(), 3);
}
}
mod union_edge_cases {
use super::*;
#[test]
fn test_union_removes_duplicates() {
let xml = r#"<root><item id="1"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item | //item").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_union_different_axes() {
let xml = r#"<root><a><b/></a><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//b | /root/c").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_union_with_text() {
let xml = r#"<root><a>text1</a><b>text2</b></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//a/text() | //b/text()").unwrap();
let values = collect_text_values(&result);
assert_eq!(values.len(), 2);
assert!(values.contains(&"text1".to_string()));
assert!(values.contains(&"text2".to_string()));
}
#[test]
fn test_union_preserves_document_order() {
let xml = r#"<root><a/><b/><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//c | //a | //b").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
let names: Vec<_> = nodes.iter().map(|n| n.get_name()).collect();
assert!(names.contains(&"a".to_string()));
assert!(names.contains(&"b".to_string()));
assert!(names.contains(&"c".to_string()));
}
#[test]
fn test_union_empty_result() {
let xml = r#"<root><item/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//nonexistent | //alsonothere").unwrap();
let nodes = result.into_nodes();
assert!(nodes.is_empty());
}
}
mod sibling_axes {
use super::*;
#[test]
fn test_following_sibling() {
let xml = r#"<root><a/><b/><c/><d/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/b/following-sibling::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0].get_name(), "c");
assert_eq!(nodes[1].get_name(), "d");
compare_with_libxml!(xpath: xml, "/root/b/following-sibling::*", &doc);
}
#[test]
fn test_preceding_sibling() {
let xml = r#"<root><a/><b/><c/><d/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/c/preceding-sibling::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "/root/c/preceding-sibling::*", &doc);
}
#[test]
fn test_following_sibling_with_name() {
let xml = r#"<root><item/><other/><item/><item/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/other/following-sibling::item").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "/root/other/following-sibling::item", &doc);
}
#[test]
fn test_preceding_sibling_first() {
let xml = r#"<root><a/><b/><c/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/c/preceding-sibling::*[1]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "b");
compare_with_libxml!(xpath: xml, "/root/c/preceding-sibling::*[1]", &doc);
}
}
mod ancestor_axes {
use super::*;
#[test]
fn test_ancestor() {
let xml = r#"<root><parent><child><target/></child></parent></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//target/ancestor::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
compare_with_libxml!(xpath: xml, "//target/ancestor::*", &doc);
}
#[test]
fn test_ancestor_with_name() {
let xml = r#"<root><parent><child><target/></child></parent></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//target/ancestor::parent").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "parent");
compare_with_libxml!(xpath: xml, "//target/ancestor::parent", &doc);
}
#[test]
fn test_ancestor_or_self() {
let xml = r#"<root><parent><target/></parent></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//target/ancestor-or-self::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
compare_with_libxml!(xpath: xml, "//target/ancestor-or-self::*", &doc);
}
#[test]
fn test_ancestor_predicate() {
let xml = r#"<root>
<container type="A"><item>1</item></container>
<container type="B"><item>2</item></container>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[ancestor::container[@type='A']]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("1".to_string()));
compare_with_libxml!(xpath: xml, "//item[ancestor::container[@type='A']]", &doc);
}
}
mod descendant_axes {
use super::*;
#[test]
fn test_descendant_or_self() {
let xml = r#"<root><child><grandchild/></child></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/descendant-or-self::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
compare_with_libxml!(xpath: xml, "/root/descendant-or-self::*", &doc);
}
#[test]
fn test_descendant_deep() {
let xml = r#"<root><a><b><c><d><target/></d></c></b></a></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/descendant::target").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
compare_with_libxml!(xpath: xml, "/root/descendant::target", &doc);
}
#[test]
fn test_descendant_multiple() {
let xml = r#"<root>
<a><target>1</target></a>
<b><c><target>2</target></c></b>
<target>3</target>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/descendant::target").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 3);
compare_with_libxml!(xpath: xml, "/root/descendant::target", &doc);
}
}
mod following_preceding_axes {
use super::*;
#[test]
fn test_following_axis() {
let xml = r#"<root><a><b/></a><c/><d/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/a/following::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "/root/a/following::*", &doc);
}
#[test]
fn test_preceding_axis() {
let xml = r#"<root><a/><b/><c><d/></c></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "/root/c/preceding::*").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
compare_with_libxml!(xpath: xml, "/root/c/preceding::*", &doc);
}
}
mod edge_cases {
use super::*;
#[test]
fn test_empty_string_comparison() {
let xml = r#"<root><item value=""/><item value="text"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@value='']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_whitespace_text() {
let xml = r#"<root><item> </item><item>text</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[normalize-space()='']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_numeric_string_comparison() {
let xml = r#"<root><item>2</item><item>10</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[. > 5]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("10".to_string()));
}
#[test]
fn test_empty_element() {
let xml = r#"<root><item/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[.='']").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_special_characters_in_text() {
let xml = r#"<root><item><tag></item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[contains(., '<')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_cdata_content() {
let xml = r#"<root><item><![CDATA[<not xml>]]></item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item").unwrap();
let values = collect_text_values(&result);
assert_eq!(values.len(), 1);
assert!(values[0].contains("<not xml>"));
}
}
mod arithmetic {
use super::*;
#[test]
fn test_addition_in_predicate() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() = 1 + 1]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("2".to_string()));
}
#[test]
fn test_multiplication_in_predicate() {
let xml = r#"<root><item>10</item><item>20</item><item>30</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[. = 2 * 10]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("20".to_string()));
}
#[test]
fn test_division_in_predicate() {
let xml = r#"<root><item>5</item><item>10</item><item>20</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[. = 20 div 2]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_content(), Some("10".to_string()));
}
#[test]
fn test_modulo_in_predicate() {
let xml = r#"<root><item>1</item><item>2</item><item>3</item><item>4</item></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[position() mod 2 = 1]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0].get_content(), Some("1".to_string()));
assert_eq!(nodes[1].get_content(), Some("3".to_string()));
}
}
mod boolean_logic {
use super::*;
#[test]
fn test_complex_and_or() {
let xml = r#"<root>
<item a="1" b="1"/>
<item a="1" b="2"/>
<item a="2" b="1"/>
<item a="2" b="2"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[(@a='1' and @b='1') or (@a='2' and @b='2')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_not_with_and() {
let xml = r#"<root>
<item status="active" type="A"/>
<item status="active" type="B"/>
<item status="inactive" type="A"/>
</root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[@status='active' and not(@type='A')]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
#[test]
fn test_double_negation() {
let xml = r#"<root><item status="active"/><item status="inactive"/></root>"#;
let doc = parse(xml).unwrap();
let result = evaluate(&doc, "//item[not(not(@status='active'))]").unwrap();
let nodes = result.into_nodes();
assert_eq!(nodes.len(), 1);
}
}