use crate::abi::structs::*;
use crate::abi::types::*;
use crate::xml::string::xmlstr_to_string;
use crate::xml::xpath::ast::{Axis, Expr, NameTest, NodeTest, Step};
use crate::xml::xpath::parser::parse_xpath;
use crate::xml::xpath::types::{NodeSet, XPathValue};
use std::os::raw::c_int;
use std::ptr;
pub const XSLT_PAT_NO_PRIORITY: f64 = -1.0e9;
#[derive(Debug, Clone)]
pub(crate) struct XsltPatternStep {
pub axis: Axis,
pub node_test: NodeTest,
pub predicates: Vec<Expr>,
}
#[derive(Debug, Clone)]
pub(crate) struct XsltPattern {
pub steps: Vec<PatternStepEntry>,
pub is_absolute: bool,
#[allow(dead_code)]
pub original: String,
#[allow(dead_code)]
pub expr: Expr,
}
#[derive(Debug, Clone)]
pub(crate) enum PatternStepEntry {
Step(XsltPatternStep),
#[allow(dead_code)]
DescendantOrSelf,
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledPattern {
pub patterns: Vec<XsltPattern>,
}
#[derive(Debug)]
#[repr(C)]
pub struct _xsltPattern {
_unused: [u8; 0],
}
#[derive(Debug)]
#[repr(C)]
pub struct _xsltPatternStep {
_unused: [u8; 0],
}
pub unsafe fn xsltCompilePattern(pattern: *const xmlChar, _doc: *mut _xmlDoc) -> *mut _xsltPattern {
if pattern.is_null() {
return ptr::null_mut();
}
let pattern_str = xmlstr_to_string(pattern);
if pattern_str.is_empty() {
return ptr::null_mut();
}
let compiled = match compile_pattern_string(&pattern_str) {
Some(cp) => cp,
None => return ptr::null_mut(),
};
let layout = std::alloc::Layout::new::<CompiledPattern>();
let ptr = std::alloc::alloc(layout) as *mut CompiledPattern;
if ptr.is_null() {
return ptr::null_mut();
}
ptr::write(ptr, compiled);
ptr as *mut _xsltPattern
}
fn compile_pattern_string(pattern_str: &str) -> Option<CompiledPattern> {
let expr = parse_xpath(pattern_str).ok()?;
let patterns = decompose_pattern(&expr, pattern_str)?;
Some(CompiledPattern { patterns })
}
fn decompose_pattern(expr: &Expr, original: &str) -> Option<Vec<XsltPattern>> {
match expr {
Expr::Union(left, right) => {
let mut patterns = decompose_pattern(left, original)?;
let right_patterns = decompose_pattern(right, original)?;
patterns.extend(right_patterns);
Some(patterns)
}
_ => {
let pattern = expr_to_pattern(expr, original)?;
Some(vec![pattern])
}
}
}
fn expr_to_pattern(expr: &Expr, original: &str) -> Option<XsltPattern> {
let (steps, is_absolute) = collect_steps(expr)?;
Some(XsltPattern {
steps,
is_absolute,
original: original.to_string(),
expr: expr.clone(),
})
}
fn collect_steps(expr: &Expr) -> Option<(Vec<PatternStepEntry>, bool)> {
match expr {
Expr::Step(step)
if step.axis == Axis::Self_
&& step.node_test == NodeTest::Node
&& step.predicates.is_empty() =>
{
Some((vec![], true))
}
Expr::Step(step) => {
let entry = PatternStepEntry::Step(XsltPatternStep {
axis: step.axis,
node_test: step.node_test.clone(),
predicates: step.predicates.clone(),
});
Some((vec![entry], false))
}
Expr::AbsolutePath(inner) => {
let (steps, _) = collect_steps(inner)?;
Some((steps, true))
}
Expr::RelativePath(left, right) => {
let (mut right_steps, _) = collect_steps(right)?;
let (left_steps, left_absolute) = collect_steps(left)?;
right_steps.extend(left_steps);
Some((right_steps, left_absolute))
}
Expr::Filter(_expr, _predicates) => {
let entry = PatternStepEntry::Step(XsltPatternStep {
axis: Axis::Self_,
node_test: NodeTest::Node,
predicates: vec![],
});
Some((vec![entry], false))
}
Expr::FunctionCall { name, args } => {
let node_test = match (name.as_str(), args.len()) {
("node", 0) => Some(NodeTest::Node),
("text", 0) => Some(NodeTest::Text),
("comment", 0) => Some(NodeTest::Comment),
("processing-instruction", 0) => Some(NodeTest::ProcessingInstruction(None)),
("processing-instruction", 1) => match &args[0] {
Expr::StringLiteral(s) => {
Some(NodeTest::ProcessingInstruction(Some(s.clone())))
}
_ => None,
},
_ => None,
};
match node_test {
Some(nt) => {
let entry = PatternStepEntry::Step(XsltPatternStep {
axis: Axis::Child,
node_test: nt,
predicates: vec![],
});
Some((vec![entry], false))
}
None if name == "id" || name == "key" => {
let entry = PatternStepEntry::Step(XsltPatternStep {
axis: Axis::Self_,
node_test: NodeTest::Node,
predicates: vec![],
});
Some((vec![entry], false))
}
None => None,
}
}
_ => {
None
}
}
}
pub unsafe fn xsltFreePattern(pattern: *mut _xsltPattern) {
if pattern.is_null() {
return;
}
let ptr = pattern as *mut CompiledPattern;
ptr::drop_in_place(ptr);
let layout = std::alloc::Layout::new::<CompiledPattern>();
std::alloc::dealloc(ptr as *mut u8, layout);
}
pub unsafe fn xsltTestPattern(
ctxt: *mut _xsltTransformContext,
pattern: *mut _xsltPattern,
node: *mut _xmlNode,
) -> c_int {
if pattern.is_null() || node.is_null() {
return 0;
}
let compiled = &*(pattern as *const CompiledPattern);
let xpath_ctxt = if !ctxt.is_null() {
(*ctxt).xpathCtxt
} else {
ptr::null_mut()
};
for sub_pattern in &compiled.patterns {
if match_sub_pattern(sub_pattern, node, xpath_ctxt) {
return 1;
}
}
0
}
pub unsafe fn xsltTestMatchPattern(node: *mut _xmlNode, pattern_node: *mut _xmlNode) -> bool {
if node.is_null() || pattern_node.is_null() {
return false;
}
match_pattern_tree(pattern_node, node)
}
unsafe fn match_pattern_tree(pattern_node: *mut _xmlNode, node: *mut _xmlNode) -> bool {
if pattern_node.is_null() || node.is_null() {
return false;
}
let node_ref = &*pattern_node;
let name = xmlstr_to_string(node_ref.name);
match name.as_str() {
"|" => {
let mut child = node_ref.children;
while !child.is_null() {
if match_pattern_tree(child, node) {
return true;
}
child = (*child).next;
}
false
}
"/" => {
let steps = collect_children(pattern_node);
if steps.is_empty() {
return false;
}
match_pattern_path(&steps, node)
}
_ => {
match_pattern_step(pattern_node, node)
}
}
}
unsafe fn collect_children(pattern_node: *mut _xmlNode) -> Vec<*mut _xmlNode> {
let mut children = Vec::new();
if pattern_node.is_null() {
return children;
}
let mut child = (*pattern_node).children;
while !child.is_null() {
children.push(child);
child = (*child).next;
}
children
}
unsafe fn match_pattern_path(steps: &[*mut _xmlNode], node: *mut _xmlNode) -> bool {
if steps.is_empty() {
return false;
}
let mut current = node;
for (i, &step) in steps.iter().enumerate() {
if current.is_null() {
return false;
}
if !match_pattern_step(step, current) {
return false;
}
if i < steps.len() - 1 {
current = (*current).parent;
}
}
true
}
unsafe fn match_pattern_step(pattern_node: *mut _xmlNode, node: *mut _xmlNode) -> bool {
if pattern_node.is_null() || node.is_null() {
return false;
}
let pn = &*pattern_node;
let nn = &*node;
let step_name = xmlstr_to_string(pn.name);
let node_name = xmlstr_to_string(nn.name);
let node_type = nn.type_;
match step_name.as_str() {
"*" => {
if pn.type_ == 2 {
node_type == 2
} else {
node_type == 1
}
}
"node()" => true,
"text()" => node_type == 3 || node_type == 4,
"comment()" => node_type == 8,
"processing-instruction()" => node_type == 7,
s if s.starts_with('@') => {
let attr_name = &s[1..];
node_type == 2 && node_name == attr_name
}
s if s.ends_with(":*") => {
if node_type != 1 {
return false;
}
let prefix = &s[..s.len() - 2];
if let Some(ns) = nn.ns.as_ref() {
let ns_prefix = xmlstr_to_string(ns.prefix);
ns_prefix == prefix
} else {
prefix.is_empty()
}
}
s if s.contains(':') && !s.starts_with('@') && !s.ends_with(":*") => {
if node_type != 1 {
return false;
}
let parts: Vec<&str> = s.splitn(2, ':').collect();
if parts.len() != 2 {
return false;
}
let prefix = parts[0];
let local = parts[1];
if node_name != local {
return false;
}
if let Some(ns) = nn.ns.as_ref() {
let ns_prefix = xmlstr_to_string(ns.prefix);
ns_prefix == prefix
} else {
prefix.is_empty()
}
}
_ => {
if node_type == 1 || node_type == 2 {
node_name == step_name
} else {
false
}
}
}
}
unsafe fn match_sub_pattern(
pattern: &XsltPattern,
node: *mut _xmlNode,
xpath_ctxt: *mut _xmlXPathContext,
) -> bool {
if pattern.steps.is_empty() {
return pattern.is_absolute && is_document_node(node);
}
let mut current_node = node;
for (i, entry) in pattern.steps.iter().enumerate() {
match entry {
PatternStepEntry::Step(step) => {
if !match_step(step, current_node, xpath_ctxt, i == 0) {
return false;
}
if i > 0 {
current_node = (*current_node).parent;
if current_node.is_null() {
return false;
}
}
}
PatternStepEntry::DescendantOrSelf => {
let remaining: Vec<_> = pattern.steps[i + 1..]
.iter()
.filter_map(|e| {
if let PatternStepEntry::Step(s) = e {
Some(s.clone())
} else {
None
}
})
.collect();
if remaining.is_empty() {
return true;
}
let mut ancestor = current_node;
loop {
ancestor = (*ancestor).parent;
if ancestor.is_null() {
return false;
}
if match_steps_sequence(&remaining, ancestor, xpath_ctxt) {
return true;
}
}
}
}
}
if pattern.is_absolute {
if current_node.is_null() {
return true;
}
let mut n = node;
loop {
let parent = (*n).parent;
if parent.is_null() {
break;
}
n = parent;
}
return (*n).type_ == 9 || (*n).type_ == 13; }
true
}
unsafe fn match_steps_sequence(
steps: &[XsltPatternStep],
node: *mut _xmlNode,
xpath_ctxt: *mut _xmlXPathContext,
) -> bool {
let mut current = node;
for (i, step) in steps.iter().enumerate() {
if !match_step(step, current, xpath_ctxt, i == 0) {
return false;
}
if i < steps.len() - 1 {
current = (*current).parent;
if current.is_null() {
return false;
}
}
}
true
}
unsafe fn is_document_node(node: *mut _xmlNode) -> bool {
if node.is_null() {
return false;
}
(*node).type_ == 9 || (*node).type_ == 13
}
unsafe fn match_step(
step: &XsltPatternStep,
node: *mut _xmlNode,
xpath_ctxt: *mut _xmlXPathContext,
_is_first: bool,
) -> bool {
if node.is_null() {
return false;
}
let node_ref = &*node;
let node_type = node_ref.type_;
match step.axis {
Axis::Attribute => {
if node_type != 2 {
return false;
}
}
Axis::Child | Axis::Self_
if (node_type == 2 || node_type == 9 || node_type == 13)
&& step.axis == Axis::Child
&& node_type != 1
&& node_type != 3
&& node_type != 4
&& node_type != 7
&& node_type != 8
=> {
return false;
}
_ => {
}
}
if !match_node_test(node, &step.node_test) {
return false;
}
if !step.predicates.is_empty() {
if xpath_ctxt.is_null() {
return true;
}
if !evaluate_predicates(node, &step.predicates, xpath_ctxt) {
return false;
}
}
true
}
unsafe fn match_node_test(node: *mut _xmlNode, node_test: &NodeTest) -> bool {
if node.is_null() {
return false;
}
let node_ref = &*node;
let node_type = node_ref.type_;
match node_test {
NodeTest::Node => {
true
}
NodeTest::Text => {
node_type == 3 || node_type == 4
}
NodeTest::Comment => {
node_type == 8
}
NodeTest::ProcessingInstruction(target) => {
if node_type != 7 {
return false;
}
if let Some(target) = target {
let name = xmlstr_to_string(node_ref.name);
name == *target
} else {
true
}
}
NodeTest::NameTest(name_test) => match_name_test(node, name_test),
NodeTest::Wildcard => {
node_type == 1
}
NodeTest::NsWildcard(prefix) => {
if node_type != 1 {
return false;
}
if let Some(ns) = node_ref.ns.as_ref() {
let ns_prefix = xmlstr_to_string(ns.prefix);
ns_prefix == *prefix
} else {
prefix.is_empty()
}
}
}
}
unsafe fn match_name_test(node: *mut _xmlNode, name_test: &NameTest) -> bool {
if node.is_null() {
return false;
}
let node_ref = &*node;
match name_test {
NameTest::Any => {
node_ref.type_ == 1 || node_ref.type_ == 2
}
NameTest::LocalName(local) => {
let name = xmlstr_to_string(node_ref.name);
name == *local
}
NameTest::QName { prefix, local } => {
let name = xmlstr_to_string(node_ref.name);
if name != *local {
return false;
}
if let Some(ns) = node_ref.ns.as_ref() {
let ns_prefix = xmlstr_to_string(ns.prefix);
ns_prefix == *prefix
} else {
prefix.is_empty()
}
}
}
}
unsafe fn evaluate_predicates(
node: *mut _xmlNode,
predicates: &[Expr],
xpath_ctxt: *mut _xmlXPathContext,
) -> bool {
if xpath_ctxt.is_null() {
return true; }
let ctxt = &mut *xpath_ctxt;
let saved_node = ctxt.node;
ctxt.node = node;
let mut result = true;
for predicate in predicates {
let doc = if !ctxt.doc.is_null() {
ctxt.doc
} else if !node.is_null() {
(*node).doc
} else {
ptr::null_mut()
};
let mut xpath_ctx = crate::xml::xpath::context::XPathContext::new(doc);
if !saved_node.is_null() {
xpath_ctx.set_context_node(saved_node);
}
if !ctxt.namespaces.is_null() && ctxt.nsNr > 0 {
let ns_slice = std::slice::from_raw_parts(ctxt.namespaces, ctxt.nsNr as usize);
for ns_ptr in ns_slice {
if !ns_ptr.is_null() {
let ns = &**ns_ptr;
let prefix = xmlstr_to_string(ns.prefix);
let href = xmlstr_to_string(ns.href);
xpath_ctx.register_namespace(&prefix, &href);
}
}
}
register_pattern_functions(&mut xpath_ctx);
let pred_result = crate::xml::xpath::eval::eval(&mut xpath_ctx, predicate);
match pred_result {
Ok(val) => {
let matches = match val {
XPathValue::Number(n) => {
(n - 1.0).abs() < f64::EPSILON
}
_ => val.as_boolean(),
};
if !matches {
result = false;
break;
}
}
Err(_) => {
result = false;
break;
}
}
}
ctxt.node = saved_node;
result
}
fn register_pattern_functions(ctx: &mut crate::xml::xpath::context::XPathContext) {
ctx.register_function("id", |_ctx, _args| {
Ok(XPathValue::NodeSet(NodeSet::new()))
});
ctx.register_function("key", |_ctx, _args| {
Ok(XPathValue::NodeSet(NodeSet::new()))
});
}
pub unsafe fn xsltDefaultPriority(pattern: *const xmlChar) -> f64 {
if pattern.is_null() {
return 0.5;
}
let pattern_str = xmlstr_to_string(pattern);
if pattern_str.is_empty() {
return 0.5;
}
compute_default_priority(&pattern_str)
}
fn compute_default_priority(pattern_str: &str) -> f64 {
let expr = match parse_xpath(pattern_str) {
Ok(e) => e,
Err(_) => return 0.5, };
compute_expr_priority(&expr)
}
fn compute_expr_priority(expr: &Expr) -> f64 {
match expr {
Expr::Union(left, right) => {
let left_p = compute_expr_priority(left);
let right_p = compute_expr_priority(right);
left_p.max(right_p)
}
Expr::AbsolutePath(inner) => compute_expr_priority(inner),
Expr::RelativePath(_, right) => compute_expr_priority(right),
Expr::Step(step) => compute_step_priority(step),
Expr::Filter(primary, _) => compute_expr_priority(primary),
Expr::FunctionCall { name, .. } => {
if name == "id" || name == "key" {
0.0
} else {
match name.as_str() {
"node" => -0.25,
"text" | "comment" | "processing-instruction" => 0.0,
_ => 0.5,
}
}
}
_ => 0.5,
}
}
fn compute_step_priority(step: &Step) -> f64 {
match &step.node_test {
NodeTest::Node => -0.25,
NodeTest::Text | NodeTest::Comment | NodeTest::ProcessingInstruction(_) => 0.0,
NodeTest::NameTest(name_test) => match name_test {
NameTest::LocalName(_) | NameTest::QName { .. } => {
if step.axis == Axis::Attribute {
0.5
} else {
0.0
}
}
NameTest::Any => {
if step.axis == Axis::Attribute {
0.5
} else {
-0.5
}
}
},
NodeTest::Wildcard => {
if step.axis == Axis::Attribute {
0.5
} else {
-0.5
}
}
NodeTest::NsWildcard(_) => {
if step.axis == Axis::Attribute {
0.5
} else {
-0.5
}
}
}
}
pub fn is_simple_name_pattern(pattern: &str) -> bool {
let expr = match parse_xpath(pattern) {
Ok(e) => e,
Err(_) => return false,
};
matches!(&expr, Expr::Step(Step {
axis: Axis::Child,
node_test: NodeTest::NameTest(name_test),
predicates,
}) if predicates.is_empty() && !matches!(name_test, NameTest::Any))
}
pub fn is_union_pattern(pattern: &str) -> bool {
let expr = match parse_xpath(pattern) {
Ok(e) => e,
Err(_) => return false,
};
matches!(&expr, Expr::Union(_, _))
}
pub fn get_pattern_matched_names(pattern: &str) -> Vec<String> {
let expr = match parse_xpath(pattern) {
Ok(e) => e,
Err(_) => return vec![],
};
let mut names = Vec::new();
collect_matched_names(&expr, &mut names);
names
}
fn collect_matched_names(expr: &Expr, names: &mut Vec<String>) {
match expr {
Expr::Union(left, right) => {
collect_matched_names(left, names);
collect_matched_names(right, names);
}
Expr::Step(Step {
node_test: NodeTest::NameTest(name_test),
..
}) => match name_test {
NameTest::LocalName(local) => names.push(local.clone()),
NameTest::QName { prefix, local } => names.push(format!("{}:{}", prefix, local)),
NameTest::Any => names.push("*".to_string()),
},
Expr::Step(Step {
node_test: NodeTest::Wildcard,
..
}) => {
names.push("*".to_string());
}
Expr::Step(Step {
node_test: NodeTest::NsWildcard(prefix),
..
}) => {
names.push(format!("{}:*", prefix));
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_priority_name_test() {
let priority = compute_default_priority("para");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for name test, got {}",
priority
);
}
#[test]
fn test_default_priority_qname() {
let priority = compute_default_priority("xslt:template");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for QName, got {}",
priority
);
}
#[test]
fn test_default_priority_node_test() {
let priority = compute_default_priority("node()");
assert!(
(priority - (-0.25)).abs() < f64::EPSILON,
"Expected -0.25 for node(), got {}",
priority
);
}
#[test]
fn test_default_priority_text_test() {
let priority = compute_default_priority("text()");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for text(), got {}",
priority
);
}
#[test]
fn test_default_priority_comment_test() {
let priority = compute_default_priority("comment()");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for comment(), got {}",
priority
);
}
#[test]
fn test_default_priority_processing_instruction() {
let priority = compute_default_priority("processing-instruction()");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for processing-instruction(), got {}",
priority
);
}
#[test]
fn test_default_priority_wildcard() {
let priority = compute_default_priority("*");
assert!(
(priority - (-0.5)).abs() < f64::EPSILON,
"Expected -0.5 for *, got {}",
priority
);
}
#[test]
fn test_default_priority_ns_wildcard() {
let priority = compute_default_priority("ns:*");
assert!(
(priority - (-0.5)).abs() < f64::EPSILON,
"Expected -0.5 for ns:*, got {}",
priority
);
}
#[test]
fn test_default_priority_attribute() {
let priority = compute_default_priority("@attr");
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for @attr, got {}",
priority
);
}
#[test]
fn test_default_priority_attribute_wildcard() {
let priority = compute_default_priority("@*");
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for @*, got {}",
priority
);
}
#[test]
fn test_default_priority_union() {
let priority = compute_default_priority("para | *");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for union, got {}",
priority
);
}
#[test]
fn test_default_priority_compound_path() {
let priority = compute_default_priority("foo/bar");
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for foo/bar, got {}",
priority
);
}
#[test]
fn test_default_priority_empty() {
let priority = compute_default_priority("");
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for empty pattern, got {}",
priority
);
}
unsafe fn create_test_node(name: &str, type_: c_int) -> *mut _xmlNode {
let layout = std::alloc::Layout::new::<_xmlNode>();
let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
if ptr.is_null() {
return ptr::null_mut();
}
let node = &mut *ptr;
node.type_ = type_;
let name_bytes = name.as_bytes();
let name_buf = std::alloc::alloc_zeroed(
std::alloc::Layout::array::<u8>(name_bytes.len() + 1).unwrap(),
);
if !name_buf.is_null() {
std::ptr::copy_nonoverlapping(name_bytes.as_ptr(), name_buf, name_bytes.len());
}
node.name = name_buf as *mut xmlChar;
ptr
}
unsafe fn free_test_node(node: *mut _xmlNode) {
if node.is_null() {
return;
}
if !(*node).name.is_null() {
let name = (*node).name;
let len = crate::abi::exports_xml2::xmlStrlen(name) as usize;
std::alloc::dealloc(
name as *mut u8,
std::alloc::Layout::array::<u8>(len + 1).unwrap(),
);
}
let layout = std::alloc::Layout::new::<_xmlNode>();
std::alloc::dealloc(node as *mut u8, layout);
}
#[test]
fn test_node_test_matching_element() {
unsafe {
let node = create_test_node("para", 1); assert!(!node.is_null());
let name_test = NodeTest::NameTest(NameTest::LocalName("para".to_string()));
assert!(match_node_test(node, &name_test));
let wrong_test = NodeTest::NameTest(NameTest::LocalName("foo".to_string()));
assert!(!match_node_test(node, &wrong_test));
let wildcard = NodeTest::Wildcard;
assert!(match_node_test(node, &wildcard));
let node_test = NodeTest::Node;
assert!(match_node_test(node, &node_test));
let text_test = NodeTest::Text;
assert!(!match_node_test(node, &text_test));
free_test_node(node);
}
}
#[test]
fn test_node_test_matching_text() {
unsafe {
let node = create_test_node("", 3); assert!(!node.is_null());
let text_test = NodeTest::Text;
assert!(match_node_test(node, &text_test));
let node_test = NodeTest::Node;
assert!(match_node_test(node, &node_test));
let comment_test = NodeTest::Comment;
assert!(!match_node_test(node, &comment_test));
let element_test = NodeTest::NameTest(NameTest::LocalName("para".to_string()));
assert!(!match_node_test(node, &element_test));
free_test_node(node);
}
}
#[test]
fn test_compile_and_free_pattern() {
unsafe {
let pattern_str = c"para".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_null_pattern() {
unsafe {
let compiled = xsltCompilePattern(ptr::null(), ptr::null_mut());
assert!(compiled.is_null());
}
}
#[test]
fn test_compile_empty_pattern() {
unsafe {
let pattern_str = c"".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(compiled.is_null());
}
}
#[test]
fn test_free_null_pattern() {
unsafe {
xsltFreePattern(ptr::null_mut());
}
}
#[test]
fn test_is_simple_name_pattern() {
assert!(is_simple_name_pattern("para"));
assert!(is_simple_name_pattern("foo:bar"));
assert!(!is_simple_name_pattern("foo/bar"));
assert!(!is_simple_name_pattern("para | foo"));
assert!(!is_simple_name_pattern("*"));
}
#[test]
fn test_is_union_pattern() {
assert!(is_union_pattern("para | foo"));
assert!(is_union_pattern("para | foo | bar"));
assert!(!is_union_pattern("para"));
assert!(!is_union_pattern("foo/bar"));
}
#[test]
fn test_get_pattern_matched_names() {
let names = get_pattern_matched_names("para");
assert_eq!(names, vec!["para"]);
let names = get_pattern_matched_names("foo | bar");
assert_eq!(names.len(), 2);
assert!(names.contains(&"foo".to_string()));
assert!(names.contains(&"bar".to_string()));
let names = get_pattern_matched_names("foo/bar");
assert!(names.is_empty());
}
#[test]
fn test_compile_union_pattern() {
unsafe {
let pattern_str = c"para | foo".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_compound_pattern() {
unsafe {
let pattern_str = c"foo/bar".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_absolute_pattern() {
unsafe {
let pattern_str = c"/foo/bar".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_attribute_pattern() {
unsafe {
let pattern_str = c"@attr".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_wildcard_pattern() {
unsafe {
let pattern_str = c"*".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_ns_wildcard_pattern() {
unsafe {
let pattern_str = c"ns:*".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_node_test_pattern() {
unsafe {
let pattern_str = c"node()".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_text_pattern() {
unsafe {
let pattern_str = c"text()".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_comment_pattern() {
unsafe {
let pattern_str = c"comment()".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_pi_pattern() {
unsafe {
let pattern_str = c"processing-instruction()".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_compile_predicate_pattern() {
unsafe {
let pattern_str = c"para[1]".as_ptr() as *const xmlChar;
let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
assert!(!compiled.is_null());
xsltFreePattern(compiled);
}
}
#[test]
fn test_decompose_union() {
let expr = parse_xpath("a | b").unwrap();
let patterns = decompose_pattern(&expr, "a | b");
assert!(patterns.is_some());
let patterns = patterns.unwrap();
assert_eq!(patterns.len(), 2);
assert_eq!(patterns[0].original, "a | b");
assert_eq!(patterns[1].original, "a | b");
}
#[test]
fn test_decompose_single() {
let expr = parse_xpath("para").unwrap();
let patterns = decompose_pattern(&expr, "para");
assert!(patterns.is_some());
let patterns = patterns.unwrap();
assert_eq!(patterns.len(), 1);
}
#[test]
fn test_collect_steps_simple() {
let expr = parse_xpath("para").unwrap();
let (steps, is_absolute) = collect_steps(&expr).unwrap();
assert!(!is_absolute);
assert_eq!(steps.len(), 1);
if let PatternStepEntry::Step(step) = &steps[0] {
assert_eq!(step.axis, Axis::Child);
assert!(
matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "para")
);
} else {
panic!("Expected Step entry");
}
}
#[test]
fn test_collect_steps_absolute() {
let expr = parse_xpath("/foo/bar").unwrap();
let (steps, is_absolute) = collect_steps(&expr).unwrap();
assert!(is_absolute);
assert_eq!(steps.len(), 2);
}
#[test]
fn test_collect_steps_attribute() {
let expr = parse_xpath("@attr").unwrap();
let (steps, is_absolute) = collect_steps(&expr).unwrap();
assert!(!is_absolute);
assert_eq!(steps.len(), 1);
if let PatternStepEntry::Step(step) = &steps[0] {
assert_eq!(step.axis, Axis::Attribute);
} else {
panic!("Expected Step entry");
}
}
#[test]
fn test_collect_steps_compound() {
let expr = parse_xpath("foo/bar").unwrap();
let (steps, is_absolute) = collect_steps(&expr).unwrap();
assert!(!is_absolute);
assert_eq!(steps.len(), 2);
if let PatternStepEntry::Step(step) = &steps[0] {
assert!(
matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "bar")
);
} else {
panic!("Expected Step entry for bar");
}
if let PatternStepEntry::Step(step) = &steps[1] {
assert!(
matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "foo")
);
} else {
panic!("Expected Step entry for foo");
}
}
#[test]
fn test_match_name_test_local() {
unsafe {
let node = create_test_node("para", 1);
assert!(!node.is_null());
assert!(match_name_test(
node,
&NameTest::LocalName("para".to_string())
));
assert!(!match_name_test(
node,
&NameTest::LocalName("foo".to_string())
));
assert!(match_name_test(node, &NameTest::Any));
free_test_node(node);
}
}
#[test]
fn test_match_node_test_wildcard() {
unsafe {
let element = create_test_node("para", 1);
let text = create_test_node("", 3);
let comment = create_test_node("", 8);
let wildcard = NodeTest::Wildcard;
assert!(match_node_test(element, &wildcard));
assert!(!match_node_test(text, &wildcard));
assert!(!match_node_test(comment, &wildcard));
free_test_node(element);
free_test_node(text);
free_test_node(comment);
}
}
#[test]
fn test_match_node_test_ns_wildcard() {
unsafe {
let node = create_test_node("para", 1);
let ns_wildcard = NodeTest::NsWildcard("".to_string());
assert!(match_node_test(node, &ns_wildcard));
let ns_wildcard = NodeTest::NsWildcard("foo".to_string());
assert!(!match_node_test(node, &ns_wildcard));
free_test_node(node);
}
}
#[test]
fn test_compute_priority_on_compiled_pattern() {
unsafe {
let pattern_str = c"para".as_ptr() as *const xmlChar;
let priority = xsltDefaultPriority(pattern_str);
assert!(
(priority - 0.0).abs() < f64::EPSILON,
"Expected 0.0 for 'para', got {}",
priority
);
let pattern_str = c"*".as_ptr() as *const xmlChar;
let priority = xsltDefaultPriority(pattern_str);
assert!(
(priority - (-0.5)).abs() < f64::EPSILON,
"Expected -0.5 for '*', got {}",
priority
);
let pattern_str = c"node()".as_ptr() as *const xmlChar;
let priority = xsltDefaultPriority(pattern_str);
assert!(
(priority - (-0.25)).abs() < f64::EPSILON,
"Expected -0.25 for 'node()', got {}",
priority
);
let pattern_str = c"@attr".as_ptr() as *const xmlChar;
let priority = xsltDefaultPriority(pattern_str);
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for '@attr', got {}",
priority
);
}
}
#[test]
fn test_compute_priority_null() {
unsafe {
let priority = xsltDefaultPriority(ptr::null());
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for null pattern, got {}",
priority
);
}
}
#[test]
fn test_compute_priority_empty() {
unsafe {
let pattern_str = c"".as_ptr() as *const xmlChar;
let priority = xsltDefaultPriority(pattern_str);
assert!(
(priority - 0.5).abs() < f64::EPSILON,
"Expected 0.5 for empty pattern, got {}",
priority
);
}
}
#[test]
fn test_xslt_test_pattern_null_args() {
unsafe {
let result = xsltTestPattern(ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
assert_eq!(result, 0);
}
}
}