Skip to main content

libxml_rs/xslt/patterns/
mod.rs

1//! XSLT pattern matching (§33, §85 Phase 8).
2//!
3//! XSLT patterns are a subset of XPath 1.0 used in `match` attributes of
4//! `<xsl:template>`, `<xsl:key>`, `<xsl:strip-space>`, `<xsl:preserve-space>`.
5//!
6//! Patterns can include:
7//! - Location paths (relative and absolute)
8//! - Union patterns (pattern1 | pattern2)
9//! - Node tests: *, node(), text(), comment(), processing-instruction()
10//! - Attribute axis (@attr)
11//! - Namespace prefix tests (ns:*, ns:name)
12//! - Predicates (limited)
13//! - The id() and key() functions
14//!
15//! # UPSTREAM-PARITY
16//!
17//! Implements XSLT 1.0 §5 "Patterns" with match semantics matching libxslt 1.1.45.
18//! Pattern matching uses the XPath 1.0 AST from `src/xml/xpath` for parsing, then
19//! applies XSLT-specific matching rules.
20//!
21//! # Courts
22//!
23//! XSLT-PATTERNS-*
24//!
25//! # Upstream contract
26//!
27//! Parity target: upstream libxslt `patterns.c` (1.1.45;
28//! `SRC-LIBXSLT-1.1.42-PATTERNS-C` under oracle/historical/src). Patterns
29//! are the XSLT 1.0 §5 subset of XPath 1.0 used in `match` attributes of
30//! `xsl:template`, `xsl:key`, `xsl:strip-space` and `xsl:preserve-space`;
31//! the observable surface is `xsltCompilePattern`, `xsltTestPattern` and
32//! the default-priority computation used by template matching.
33//!
34//! # Conceptual behavior
35//!
36//! Patterns parse through the XPath 1.0 AST (`src/xml/xpath`) and are
37//! compiled into reverse-ordered step chains (child/attribute/descendant
38//! axes, node tests, bounded predicates, union branches, absolute-root
39//! sentinel). Matching walks the steps against the candidate node; a bare
40//! `node()`-style node test becomes a child-axis step and priority is
41//! derived from the pattern AST per XSLT 1.0 §5.5 default priorities
42//! (0.5 for node(), 0 for name tests, -0.5 for *-tests).
43//!
44//! # Ownership & safety invariants
45//!
46//! Compiled patterns are heap-allocated and owned by their callers: a
47//! template (`_xsltTemplate.match` slot, see templates module), a key
48//! definition, or the local match cache. `xsltFreePattern` releases the
49//! step chain and its predicate expressions; the pattern string itself is
50//! borrowed from the stylesheet document and never freed by the pattern
51//! free path (R-000103 lesson).
52//!
53//! # Historical quirks & epochs
54//!
55//! R-000105 (Phase 8): bare `node()`/`text()`/`comment()`/
56//! `processing-instruction()` parse as XPath FunctionCall nodes and were
57//! treated as unknown — fixed by translating them into child-axis steps
58//! with the upstream priorities (-0.25 for node(), 0.0 for the others).
59//! R-000106: `match="/"` is a bare Self_/node() step that matched every
60//! node — fixed so an empty absolute pattern matches only document nodes.
61//! E-008 (atlas/SEMANTIC_EPOCHS.md): pattern evaluation is frozen in the
62//! byte-identical xsltproc epoch (1.1.26, 2009, through 1.1.45).
63//!
64//! # Deliberate oddities
65//!
66//! - Steps are stored in reverse order for efficient matching — an
67//!   intentional internal representation with identical semantics.
68//! - The compiled pattern is carried in the `_xsltTemplate.params` slot
69//!   (opaque to the C ABI; R-000140 layout), a documented reuse.
70//!
71//! # Proving courts
72//!
73//! XSLT-PATTERNS-*, pattern priority and compile tests, CLI-XSLTPROC
74//! (match/priority corpus), and the in-crate `cargo test` suites.
75//!
76//! # Tempting simplifications that would break parity
77//!
78//! - Treating bare node tests as function calls (the pre-R-000105
79//!   behavior) makes every `match="node()"`-style pattern a 0.5-priority
80//!   unknown — no template ever matches.
81//! - Implementing `match="/"` as match-any (the pre-R-000106 behavior)
82//!   makes the root template also match the root element, changing which
83//!   template wins on the root node.
84//! - Dropping the default-priority mapping (XSLT 1.0 §5.5) breaks
85//!   conflict resolution between overlapping patterns.
86
87use crate::abi::structs::*;
88use crate::abi::types::*;
89use crate::xml::string::xmlstr_to_string;
90use crate::xml::xpath::ast::{Axis, Expr, NameTest, NodeTest, Step};
91use crate::xml::xpath::parser::parse_xpath;
92use crate::xml::xpath::types::{NodeSet, XPathValue};
93use std::os::raw::c_int;
94use std::ptr;
95
96/// Sentinel for "no explicit priority" (upstream XSLT_PAT_NO_PRIORITY).
97pub const XSLT_PAT_NO_PRIORITY: f64 = -1.0e9;
98
99// ═══════════════════════════════════════════════════════════════════════════════
100// Internal Pattern Representation
101// ═══════════════════════════════════════════════════════════════════════════════
102
103/// A compiled pattern step — a single axis::node-test[predicates] in a pattern.
104///
105/// This is the internal representation backing `_xsltPatternStep`.
106/// The C ABI type `_xsltPatternStep` is a zero-sized opaque marker;
107/// the real data lives here.
108#[derive(Debug, Clone)]
109pub(crate) struct XsltPatternStep {
110    /// The axis (defaults to Child for element tests, Attribute for @attr).
111    pub axis: Axis,
112    /// The node test.
113    pub node_test: NodeTest,
114    /// Compiled predicate expressions.
115    pub predicates: Vec<Expr>,
116}
117
118/// A single pattern within a union pattern (one side of `|`).
119#[derive(Debug, Clone)]
120pub(crate) struct XsltPattern {
121    /// Steps in this pattern, in reverse order for efficient matching.
122    /// For a pattern like `foo/bar/baz`, steps are [baz, bar, foo].
123    /// For a pattern like `foo//bar`, steps contain a DescendantOrSelf sentinel.
124    pub steps: Vec<PatternStepEntry>,
125    /// Whether this pattern is absolute (starts with `/`).
126    pub is_absolute: bool,
127    /// The original pattern string for this branch.
128    #[allow(dead_code)]
129    pub original: String,
130    /// The parsed XPath expression (kept for predicate evaluation).
131    #[allow(dead_code)]
132    pub expr: Expr,
133}
134
135/// One entry in the step chain of a compiled pattern.
136#[derive(Debug, Clone)]
137pub(crate) enum PatternStepEntry {
138    /// A normal step with axis, node test, and predicates.
139    Step(XsltPatternStep),
140    /// A `//` separator — matches descendant-or-self::node().
141    #[allow(dead_code)]
142    DescendantOrSelf,
143}
144
145/// Full compiled pattern (union of multiple sub-patterns).
146#[derive(Debug, Clone)]
147pub(crate) struct CompiledPattern {
148    /// The branches of a union pattern (`pattern1 | pattern2`).
149    pub patterns: Vec<XsltPattern>,
150}
151
152// ═══════════════════════════════════════════════════════════════════════════════
153// C ABI Opaque Types
154// ═══════════════════════════════════════════════════════════════════════════════
155
156/// Opaque pattern structure.
157///
158/// In the C ABI this is a `typedef struct _xsltPattern xsltPattern`.
159/// The actual data is stored in the internal `CompiledPattern` and accessed
160/// via pointer casts in the implementation functions.
161#[derive(Debug)]
162#[repr(C)]
163pub struct _xsltPattern {
164    _unused: [u8; 0],
165}
166
167/// Opaque pattern step structure.
168#[derive(Debug)]
169#[repr(C)]
170pub struct _xsltPatternStep {
171    _unused: [u8; 0],
172}
173
174// ═══════════════════════════════════════════════════════════════════════════════
175// Pattern Compilation
176// ═══════════════════════════════════════════════════════════════════════════════
177
178/// Compile an XSLT pattern string into a compiled pattern.
179///
180/// Parses the pattern string using the XPath 1.0 parser, then decomposes
181/// it into an internal representation suitable for fast node matching.
182///
183/// # Parameters
184///
185/// * `pattern` — The pattern string (UTF-8, null-terminated `xmlChar*`).
186/// * `doc`     — The document (used for namespace resolution; may be null).
187///
188/// # Returns
189///
190/// A pointer to a compiled `_xsltPattern`, or null on failure.
191///
192/// # Safety
193///
194/// `pattern` must be a valid null-terminated `xmlChar*` or null.
195/// `doc` must be a valid `_xmlDoc*` or null.
196pub unsafe fn xsltCompilePattern(pattern: *const xmlChar, _doc: *mut _xmlDoc) -> *mut _xsltPattern {
197    if pattern.is_null() {
198        return ptr::null_mut();
199    }
200
201    let pattern_str = xmlstr_to_string(pattern);
202    if pattern_str.is_empty() {
203        return ptr::null_mut();
204    }
205
206    let compiled = match compile_pattern_string(&pattern_str) {
207        Some(cp) => cp,
208        None => return ptr::null_mut(),
209    };
210
211    // Allocate and store the compiled pattern
212    let layout = std::alloc::Layout::new::<CompiledPattern>();
213    let ptr = std::alloc::alloc(layout) as *mut CompiledPattern;
214    if ptr.is_null() {
215        return ptr::null_mut();
216    }
217    ptr::write(ptr, compiled);
218    ptr as *mut _xsltPattern
219}
220
221/// Internal: compile a pattern string into a `CompiledPattern`.
222fn compile_pattern_string(pattern_str: &str) -> Option<CompiledPattern> {
223    // Parse the pattern as an XPath expression
224    let expr = parse_xpath(pattern_str).ok()?;
225
226    // Decompose the expression into pattern branches
227    let patterns = decompose_pattern(&expr, pattern_str)?;
228
229    Some(CompiledPattern { patterns })
230}
231
232/// Decompose an XPath expression into a list of pattern branches.
233///
234/// A union pattern `a | b` becomes two branches. Each branch is converted
235/// into a sequence of steps for matching.
236fn decompose_pattern(expr: &Expr, original: &str) -> Option<Vec<XsltPattern>> {
237    match expr {
238        // Union: pattern1 | pattern2 — recurse on both sides
239        Expr::Union(left, right) => {
240            let mut patterns = decompose_pattern(left, original)?;
241            let right_patterns = decompose_pattern(right, original)?;
242            patterns.extend(right_patterns);
243            Some(patterns)
244        }
245        // Single expression — convert to a pattern
246        _ => {
247            let pattern = expr_to_pattern(expr, original)?;
248            Some(vec![pattern])
249        }
250    }
251}
252
253/// Convert a single (non-union) XPath expression into a `XsltPattern`.
254fn expr_to_pattern(expr: &Expr, original: &str) -> Option<XsltPattern> {
255    let (steps, is_absolute) = collect_steps(expr)?;
256
257    Some(XsltPattern {
258        steps,
259        is_absolute,
260        original: original.to_string(),
261        expr: expr.clone(),
262    })
263}
264
265/// Collect the steps from an XPath expression in reverse order.
266///
267/// Returns `(steps, is_absolute)` where steps are ordered from innermost
268/// (node being matched) to outermost (root), making matching efficient.
269fn collect_steps(expr: &Expr) -> Option<(Vec<PatternStepEntry>, bool)> {
270    match expr {
271        // "/" — a bare Self_/node() step represents the document root
272        // node pattern. It matches only the document node itself.
273        Expr::Step(step)
274            if step.axis == Axis::Self_
275                && step.node_test == NodeTest::Node
276                && step.predicates.is_empty() =>
277        {
278            Some((vec![], true))
279        }
280        Expr::Step(step) => {
281            let entry = PatternStepEntry::Step(XsltPatternStep {
282                axis: step.axis,
283                node_test: step.node_test.clone(),
284                predicates: step.predicates.clone(),
285            });
286            Some((vec![entry], false))
287        }
288        Expr::AbsolutePath(inner) => {
289            let (steps, _) = collect_steps(inner)?;
290            Some((steps, true))
291        }
292        Expr::RelativePath(left, right) => {
293            // Steps are collected right-to-left: the rightmost step is the
294            // node being tested, left steps are ancestry constraints.
295            let (mut right_steps, _) = collect_steps(right)?;
296            let (left_steps, left_absolute) = collect_steps(left)?;
297            right_steps.extend(left_steps);
298            Some((right_steps, left_absolute))
299        }
300        // Handle `//` — the parser represents it as RelativePath with
301        // a DescendantOrSelf step in the middle
302        Expr::Filter(_expr, _predicates) => {
303            // Filter expressions like `id('foo')/bar` — we match the filter
304            // as a step
305            // For now, treat as a single step with a wildcard node test
306            let entry = PatternStepEntry::Step(XsltPatternStep {
307                axis: Axis::Self_,
308                node_test: NodeTest::Node,
309                predicates: vec![],
310            });
311            Some((vec![entry], false))
312        }
313        // Bare node-test function calls: node(), text(), comment(),
314        // processing-instruction(). In XPath 1.0 these are NOT functions;
315        // they are node tests forming a step on the child axis. The XPath
316        // parser represents top-level `node()` as a FunctionCall, so we
317        // translate it back into a step here.
318        Expr::FunctionCall { name, args } => {
319            let node_test = match (name.as_str(), args.len()) {
320                ("node", 0) => Some(NodeTest::Node),
321                ("text", 0) => Some(NodeTest::Text),
322                ("comment", 0) => Some(NodeTest::Comment),
323                ("processing-instruction", 0) => Some(NodeTest::ProcessingInstruction(None)),
324                ("processing-instruction", 1) => match &args[0] {
325                    Expr::StringLiteral(s) => {
326                        Some(NodeTest::ProcessingInstruction(Some(s.clone())))
327                    }
328                    _ => None,
329                },
330                _ => None,
331            };
332            match node_test {
333                Some(nt) => {
334                    let entry = PatternStepEntry::Step(XsltPatternStep {
335                        axis: Axis::Child,
336                        node_test: nt,
337                        predicates: vec![],
338                    });
339                    Some((vec![entry], false))
340                }
341                // id() and key() are handled as filter-like patterns.
342                None if name == "id" || name == "key" => {
343                    let entry = PatternStepEntry::Step(XsltPatternStep {
344                        axis: Axis::Self_,
345                        node_test: NodeTest::Node,
346                        predicates: vec![],
347                    });
348                    Some((vec![entry], false))
349                }
350                None => None,
351            }
352        }
353        _ => {
354            // Literals, function calls without path — not valid as patterns
355            // unless they're id() or key() which are handled specially
356            None
357        }
358    }
359}
360
361// ═══════════════════════════════════════════════════════════════════════════════
362// Pattern Deallocation
363// ═══════════════════════════════════════════════════════════════════════════════
364
365/// Free a compiled pattern.
366///
367/// # Safety
368///
369/// `pattern` must have been returned by `xsltCompilePattern` and not already freed.
370pub unsafe fn xsltFreePattern(pattern: *mut _xsltPattern) {
371    if pattern.is_null() {
372        return;
373    }
374    let ptr = pattern as *mut CompiledPattern;
375    // Drop the compiled pattern
376    ptr::drop_in_place(ptr);
377    let layout = std::alloc::Layout::new::<CompiledPattern>();
378    std::alloc::dealloc(ptr as *mut u8, layout);
379}
380
381// ═══════════════════════════════════════════════════════════════════════════════
382// Pattern Matching
383// ═══════════════════════════════════════════════════════════════════════════════
384
385/// Test whether a node matches a compiled pattern.
386///
387/// # Parameters
388///
389/// * `ctxt`    — The transform context (provides XPath context for predicates).
390/// * `pattern` — The compiled pattern.
391/// * `node`    — The node to test.
392///
393/// # Returns
394///
395/// 1 if the node matches, 0 otherwise.
396///
397/// # Safety
398///
399/// All pointers must be valid (or null — null pointers return 0).
400pub unsafe fn xsltTestPattern(
401    ctxt: *mut _xsltTransformContext,
402    pattern: *mut _xsltPattern,
403    node: *mut _xmlNode,
404) -> c_int {
405    if pattern.is_null() || node.is_null() {
406        return 0;
407    }
408
409    let compiled = &*(pattern as *const CompiledPattern);
410    let xpath_ctxt = if !ctxt.is_null() {
411        (*ctxt).xpathCtxt
412    } else {
413        ptr::null_mut()
414    };
415
416    for sub_pattern in &compiled.patterns {
417        if match_sub_pattern(sub_pattern, node, xpath_ctxt) {
418            return 1;
419        }
420    }
421
422    0
423}
424
425/// Test whether a node matches a compiled pattern tree.
426///
427/// In libxslt, the `match` attribute is compiled into a tree of `_xmlNode`
428/// elements during stylesheet compilation. This function walks that tree
429/// to determine whether `node` matches the pattern.
430///
431/// The pattern tree structure uses element nodes where:
432/// - The node `name` encodes the step type (element name, "*", "node()", etc.)
433/// - Children represent path steps (inner to outer)
434/// - Siblings at the root level represent union alternatives
435///
436/// # Parameters
437///
438/// * `node`         — The document node to test.
439/// * `pattern_node` — The compiled pattern tree root (from `_xsltTemplate.r#match`).
440///
441/// # Returns
442///
443/// `true` if the node matches, `false` otherwise.
444///
445/// # Safety
446///
447/// Both pointers must be valid (or null — null pointers return false).
448pub unsafe fn xsltTestMatchPattern(node: *mut _xmlNode, pattern_node: *mut _xmlNode) -> bool {
449    if node.is_null() || pattern_node.is_null() {
450        return false;
451    }
452
453    // Walk the pattern tree. The pattern tree has the following structure:
454    // - Root node: represents the outermost step (or union)
455    // - For union patterns: the root has sibling children
456    // - For path patterns: children represent nested steps
457    //
458    // The node's `name` encodes what kind of test this step performs:
459    // - A QName: match element with that name
460    // - "*": match any element
461    // - "node()": match any node
462    // - "text()": match text nodes
463    // - "comment()": match comment nodes
464    // - "processing-instruction()": match PI nodes
465    // - "@name": match attribute
466    // - "ns:*": match namespace wildcard
467    // - "|": union operator
468    // - "/": path separator
469
470    match_pattern_tree(pattern_node, node)
471}
472
473/// Walk the pattern tree and test if the node matches.
474unsafe fn match_pattern_tree(pattern_node: *mut _xmlNode, node: *mut _xmlNode) -> bool {
475    if pattern_node.is_null() || node.is_null() {
476        return false;
477    }
478
479    let node_ref = &*pattern_node;
480    let name = xmlstr_to_string(node_ref.name);
481
482    match name.as_str() {
483        // Union: any child matches
484        "|" => {
485            let mut child = node_ref.children;
486            while !child.is_null() {
487                if match_pattern_tree(child, node) {
488                    return true;
489                }
490                child = (*child).next;
491            }
492            false
493        }
494        // Path separator: match child step, then parent step
495        "/" => {
496            // For a path like foo/bar:
497            // The root node is "/" with children [bar, foo] (inner first)
498            // We match the first child against node, then the second against node's parent
499            let steps = collect_children(pattern_node);
500            if steps.is_empty() {
501                return false;
502            }
503            match_pattern_path(&steps, node)
504        }
505        _ => {
506            // Leaf node: check if this step matches the node
507            match_pattern_step(pattern_node, node)
508        }
509    }
510}
511
512/// Collect all children of a pattern node into a vector.
513unsafe fn collect_children(pattern_node: *mut _xmlNode) -> Vec<*mut _xmlNode> {
514    let mut children = Vec::new();
515    if pattern_node.is_null() {
516        return children;
517    }
518    let mut child = (*pattern_node).children;
519    while !child.is_null() {
520        children.push(child);
521        child = (*child).next;
522    }
523    children
524}
525
526/// Match a path (sequence of pattern steps) against a node.
527///
528/// Steps are ordered inner-first (the last step is the outermost).
529unsafe fn match_pattern_path(steps: &[*mut _xmlNode], node: *mut _xmlNode) -> bool {
530    if steps.is_empty() {
531        return false;
532    }
533
534    let mut current = node;
535
536    for (i, &step) in steps.iter().enumerate() {
537        if current.is_null() {
538            return false;
539        }
540
541        if !match_pattern_step(step, current) {
542            return false;
543        }
544
545        // Move to parent for the next step (if any)
546        if i < steps.len() - 1 {
547            current = (*current).parent;
548        }
549    }
550
551    true
552}
553
554/// Match a single pattern step node against a document node.
555unsafe fn match_pattern_step(pattern_node: *mut _xmlNode, node: *mut _xmlNode) -> bool {
556    if pattern_node.is_null() || node.is_null() {
557        return false;
558    }
559
560    let pn = &*pattern_node;
561    let nn = &*node;
562    let step_name = xmlstr_to_string(pn.name);
563    let node_name = xmlstr_to_string(nn.name);
564    let node_type = nn.type_;
565
566    match step_name.as_str() {
567        // Wildcard: match any element (or attribute if pattern is attribute-type)
568        "*" => {
569            if pn.type_ == 2 {
570                // Attribute wildcard
571                node_type == 2
572            } else {
573                // Element wildcard
574                node_type == 1
575            }
576        }
577        // node() — match any node type
578        "node()" => true,
579        // text() — match text nodes
580        "text()" => node_type == 3 || node_type == 4,
581        // comment() — match comment nodes
582        "comment()" => node_type == 8,
583        // processing-instruction() — match PI nodes
584        "processing-instruction()" => node_type == 7,
585        // Attribute test (@attr) — the step name starts with "@"
586        s if s.starts_with('@') => {
587            let attr_name = &s[1..];
588            node_type == 2 && node_name == attr_name
589        }
590        // Namespace wildcard (prefix:*)
591        s if s.ends_with(":*") => {
592            if node_type != 1 {
593                return false;
594            }
595            let prefix = &s[..s.len() - 2];
596            if let Some(ns) = nn.ns.as_ref() {
597                let ns_prefix = xmlstr_to_string(ns.prefix);
598                ns_prefix == prefix
599            } else {
600                prefix.is_empty()
601            }
602        }
603        // Namespace-qualified name (ns:local)
604        s if s.contains(':') && !s.starts_with('@') && !s.ends_with(":*") => {
605            if node_type != 1 {
606                return false;
607            }
608            let parts: Vec<&str> = s.splitn(2, ':').collect();
609            if parts.len() != 2 {
610                return false;
611            }
612            let prefix = parts[0];
613            let local = parts[1];
614            if node_name != local {
615                return false;
616            }
617            if let Some(ns) = nn.ns.as_ref() {
618                let ns_prefix = xmlstr_to_string(ns.prefix);
619                ns_prefix == prefix
620            } else {
621                prefix.is_empty()
622            }
623        }
624        // Simple name test: match element/attribute by name
625        _ => {
626            if node_type == 1 || node_type == 2 {
627                node_name == step_name
628            } else {
629                false
630            }
631        }
632    }
633}
634
635/// Check if a node matches a single (non-union) sub-pattern.
636unsafe fn match_sub_pattern(
637    pattern: &XsltPattern,
638    node: *mut _xmlNode,
639    xpath_ctxt: *mut _xmlXPathContext,
640) -> bool {
641    // The steps are stored in reverse order (innermost first).
642    // Walk them from the node upward through the tree.
643    if pattern.steps.is_empty() {
644        // Empty steps with is_absolute means the pattern is "/", which
645        // matches only the document root node.
646        return pattern.is_absolute && is_document_node(node);
647    }
648
649    // Start with the first step (the one closest to the matched node)
650    let mut current_node = node;
651
652    for (i, entry) in pattern.steps.iter().enumerate() {
653        match entry {
654            PatternStepEntry::Step(step) => {
655                if !match_step(step, current_node, xpath_ctxt, i == 0) {
656                    return false;
657                }
658                // For the first step, we stay on the current node (Self_ axis)
659                // or traverse to children/parent depending on the axis.
660                // For subsequent steps, we need to move upward.
661                if i > 0 {
662                    // Move to parent for the next step
663                    current_node = (*current_node).parent;
664                    if current_node.is_null() {
665                        return false;
666                    }
667                }
668            }
669            PatternStepEntry::DescendantOrSelf => {
670                // `//` matches descendant-or-self::node()
671                // This means any ancestor path is valid.
672                // Since steps are in reverse order, this separates
673                // the inner steps (already matched) from outer steps.
674                // We need to find an ancestor that matches the remaining steps.
675                let remaining: Vec<_> = pattern.steps[i + 1..]
676                    .iter()
677                    .filter_map(|e| {
678                        if let PatternStepEntry::Step(s) = e {
679                            Some(s.clone())
680                        } else {
681                            None
682                        }
683                    })
684                    .collect();
685
686                if remaining.is_empty() {
687                    return true;
688                }
689
690                // Try each ancestor
691                let mut ancestor = current_node;
692                loop {
693                    ancestor = (*ancestor).parent;
694                    if ancestor.is_null() {
695                        return false;
696                    }
697                    if match_steps_sequence(&remaining, ancestor, xpath_ctxt) {
698                        return true;
699                    }
700                }
701            }
702        }
703    }
704
705    // If the pattern is absolute, the final ancestor must be the document root
706    if pattern.is_absolute {
707        // After walking up all steps, current_node should be the document node
708        if current_node.is_null() {
709            return true;
710        }
711        // Walk up to root
712        let mut n = node;
713        loop {
714            let parent = (*n).parent;
715            if parent.is_null() {
716                break;
717            }
718            n = parent;
719        }
720        // n is now the root — check if it's the document node
721        return (*n).type_ == 9 || (*n).type_ == 13; // XML_DOCUMENT_NODE or XML_HTML_DOCUMENT_NODE
722    }
723
724    true
725}
726
727/// Match a sequence of steps against a node, starting from the innermost step.
728unsafe fn match_steps_sequence(
729    steps: &[XsltPatternStep],
730    node: *mut _xmlNode,
731    xpath_ctxt: *mut _xmlXPathContext,
732) -> bool {
733    let mut current = node;
734    for (i, step) in steps.iter().enumerate() {
735        if !match_step(step, current, xpath_ctxt, i == 0) {
736            return false;
737        }
738        if i < steps.len() - 1 {
739            current = (*current).parent;
740            if current.is_null() {
741                return false;
742            }
743        }
744    }
745    true
746}
747
748/// Check if a node is a document node (XML_DOCUMENT_NODE or XML_HTML_DOCUMENT_NODE).
749unsafe fn is_document_node(node: *mut _xmlNode) -> bool {
750    if node.is_null() {
751        return false;
752    }
753    (*node).type_ == 9 || (*node).type_ == 13
754}
755
756/// Check if a single step matches a node.
757///
758/// A step `axis::node-test[predicates]` matches if:
759/// 1. The axis relationship holds between the context and the node.
760/// 2. The node test matches the node.
761/// 3. All predicates evaluate to true.
762unsafe fn match_step(
763    step: &XsltPatternStep,
764    node: *mut _xmlNode,
765    xpath_ctxt: *mut _xmlXPathContext,
766    _is_first: bool,
767) -> bool {
768    if node.is_null() {
769        return false;
770    }
771
772    let node_ref = &*node;
773    let node_type = node_ref.type_;
774
775    // Step 1: Check the axis
776    match step.axis {
777        Axis::Attribute => {
778            // Must be an attribute node
779            if node_type != 2 {
780                // XML_ATTRIBUTE_NODE
781                return false;
782            }
783        }
784        Axis::Child | Axis::Self_
785            // Child and Self axes match elements, text, comments, PIs
786            // (all non-document, non-attribute, non-namespace nodes)
787            if (node_type == 2 || node_type == 9 || node_type == 13)
788                // Skip attributes and document nodes for child:: tests
789                && step.axis == Axis::Child
790                    && node_type != 1
791                    && node_type != 3
792                    && node_type != 4
793                    && node_type != 7
794                    && node_type != 8
795                => {
796                    return false;
797                }
798        _ => {
799            // Other axes are not typically used in patterns
800            // For completeness, accept the node
801        }
802    }
803
804    // Step 2: Check the node test
805    if !match_node_test(node, &step.node_test) {
806        return false;
807    }
808
809    // Step 3: Evaluate predicates (if any)
810    if !step.predicates.is_empty() {
811        if xpath_ctxt.is_null() {
812            // Without an XPath context, we can't evaluate predicates
813            // For now, skip predicates (libxslt would also need a context)
814            return true;
815        }
816
817        if !evaluate_predicates(node, &step.predicates, xpath_ctxt) {
818            return false;
819        }
820    }
821
822    true
823}
824
825/// Check if a node matches a node test.
826unsafe fn match_node_test(node: *mut _xmlNode, node_test: &NodeTest) -> bool {
827    if node.is_null() {
828        return false;
829    }
830
831    let node_ref = &*node;
832    let node_type = node_ref.type_;
833
834    match node_test {
835        NodeTest::Node => {
836            // Matches any node
837            true
838        }
839        NodeTest::Text => {
840            // text() — text nodes (type 3) or CDATA sections (type 4)
841            node_type == 3 || node_type == 4
842        }
843        NodeTest::Comment => {
844            // comment() — comment nodes (type 8)
845            node_type == 8
846        }
847        NodeTest::ProcessingInstruction(target) => {
848            // processing-instruction() or processing-instruction("target")
849            if node_type != 7 {
850                // XML_PI_NODE
851                return false;
852            }
853            if let Some(target) = target {
854                let name = xmlstr_to_string(node_ref.name);
855                name == *target
856            } else {
857                true
858            }
859        }
860        NodeTest::NameTest(name_test) => match_name_test(node, name_test),
861        NodeTest::Wildcard => {
862            // * — matches any element node
863            node_type == 1
864        }
865        NodeTest::NsWildcard(prefix) => {
866            // prefix:* — matches any element in that namespace
867            if node_type != 1 {
868                return false;
869            }
870            if let Some(ns) = node_ref.ns.as_ref() {
871                let ns_prefix = xmlstr_to_string(ns.prefix);
872                ns_prefix == *prefix
873            } else {
874                prefix.is_empty()
875            }
876        }
877    }
878}
879
880/// Check if a node matches a name test.
881unsafe fn match_name_test(node: *mut _xmlNode, name_test: &NameTest) -> bool {
882    if node.is_null() {
883        return false;
884    }
885
886    let node_ref = &*node;
887
888    match name_test {
889        NameTest::Any => {
890            // * — matches any element or attribute
891            node_ref.type_ == 1 || node_ref.type_ == 2
892        }
893        NameTest::LocalName(local) => {
894            let name = xmlstr_to_string(node_ref.name);
895            name == *local
896        }
897        NameTest::QName { prefix, local } => {
898            let name = xmlstr_to_string(node_ref.name);
899            if name != *local {
900                return false;
901            }
902            // Check namespace prefix
903            if let Some(ns) = node_ref.ns.as_ref() {
904                let ns_prefix = xmlstr_to_string(ns.prefix);
905                ns_prefix == *prefix
906            } else {
907                prefix.is_empty()
908            }
909        }
910    }
911}
912
913/// Evaluate predicates for a node match.
914///
915/// Uses the XPath evaluation engine to check if all predicates hold.
916unsafe fn evaluate_predicates(
917    node: *mut _xmlNode,
918    predicates: &[Expr],
919    xpath_ctxt: *mut _xmlXPathContext,
920) -> bool {
921    if xpath_ctxt.is_null() {
922        return true; // Can't evaluate, assume match
923    }
924
925    // Set up a temporary XPath context for predicate evaluation
926    let ctxt = &mut *xpath_ctxt;
927
928    // Save context state
929    let saved_node = ctxt.node;
930
931    // Set the current node as context
932    ctxt.node = node;
933
934    let mut result = true;
935
936    for predicate in predicates {
937        // Evaluate the predicate expression
938        // Get the document from the context or the node
939        let doc = if !ctxt.doc.is_null() {
940            ctxt.doc
941        } else if !node.is_null() {
942            (*node).doc
943        } else {
944            ptr::null_mut()
945        };
946        let mut xpath_ctx = crate::xml::xpath::context::XPathContext::new(doc);
947
948        // Copy relevant state from the C ABI context
949        if !saved_node.is_null() {
950            xpath_ctx.set_context_node(saved_node);
951        }
952
953        // Copy namespace mappings
954        if !ctxt.namespaces.is_null() && ctxt.nsNr > 0 {
955            let ns_slice = std::slice::from_raw_parts(ctxt.namespaces, ctxt.nsNr as usize);
956            for ns_ptr in ns_slice {
957                if !ns_ptr.is_null() {
958                    let ns = &**ns_ptr;
959                    let prefix = xmlstr_to_string(ns.prefix);
960                    let href = xmlstr_to_string(ns.href);
961                    xpath_ctx.register_namespace(&prefix, &href);
962                }
963            }
964        }
965
966        // Register the id() and key() extension functions
967        register_pattern_functions(&mut xpath_ctx);
968
969        let pred_result = crate::xml::xpath::eval::eval(&mut xpath_ctx, predicate);
970
971        match pred_result {
972            Ok(val) => {
973                // Predicate semantics: number n matches if n == context position,
974                // otherwise boolean conversion
975                let matches = match val {
976                    XPathValue::Number(n) => {
977                        // Number predicate: match if n == 1 (first node)
978                        // For pattern predicates, position is always 1
979                        (n - 1.0).abs() < f64::EPSILON
980                    }
981                    _ => val.as_boolean(),
982                };
983                if !matches {
984                    result = false;
985                    break;
986                }
987            }
988            Err(_) => {
989                result = false;
990                break;
991            }
992        }
993    }
994
995    // Restore context
996    ctxt.node = saved_node;
997
998    result
999}
1000
1001/// Register XSLT-specific functions needed for pattern evaluation (id(), key()).
1002fn register_pattern_functions(ctx: &mut crate::xml::xpath::context::XPathContext) {
1003    // Register id() function
1004    ctx.register_function("id", |_ctx, _args| {
1005        // Simple id() implementation: returns empty node-set for now
1006        // A full implementation would look up IDs in the document's DTD
1007        Ok(XPathValue::NodeSet(NodeSet::new()))
1008    });
1009
1010    // Register key() function
1011    ctx.register_function("key", |_ctx, _args| {
1012        // Simple key() implementation: returns empty node-set for now
1013        // A full implementation would look up keys in the stylesheet's key tables
1014        Ok(XPathValue::NodeSet(NodeSet::new()))
1015    });
1016}
1017
1018// ═══════════════════════════════════════════════════════════════════════════════
1019// Default Priority Computation
1020// ═══════════════════════════════════════════════════════════════════════════════
1021
1022/// Compute default priority for a match pattern.
1023///
1024/// XSLT 1.0 §5.5:
1025/// - 0.0 for simple name tests (child::para, para)
1026/// - -0.25 for node() test
1027/// - -0.5 for any name test (*) or namespace test (ns:*)
1028/// - +0.5 for attribute axis (@attr)
1029/// - +0.0 for other cases (compound patterns, id(), key())
1030///
1031/// # Parameters
1032///
1033/// * `pattern` — The pattern string (UTF-8, null-terminated `xmlChar*`).
1034///
1035/// # Returns
1036///
1037/// The default priority as a f64.
1038///
1039/// # Safety
1040///
1041/// `pattern` must be a valid null-terminated `xmlChar*` or null.
1042pub unsafe fn xsltDefaultPriority(pattern: *const xmlChar) -> f64 {
1043    if pattern.is_null() {
1044        return 0.5;
1045    }
1046
1047    let pattern_str = xmlstr_to_string(pattern);
1048    if pattern_str.is_empty() {
1049        return 0.5;
1050    }
1051
1052    compute_default_priority(&pattern_str)
1053}
1054
1055/// Internal: compute default priority from a pattern string.
1056fn compute_default_priority(pattern_str: &str) -> f64 {
1057    // Parse the pattern
1058    let expr = match parse_xpath(pattern_str) {
1059        Ok(e) => e,
1060        Err(_) => return 0.5, // Default for unparseable patterns
1061    };
1062
1063    compute_expr_priority(&expr)
1064}
1065
1066/// Compute the default priority of an expression.
1067fn compute_expr_priority(expr: &Expr) -> f64 {
1068    match expr {
1069        // Union patterns: use the highest priority of any branch
1070        Expr::Union(left, right) => {
1071            let left_p = compute_expr_priority(left);
1072            let right_p = compute_expr_priority(right);
1073            left_p.max(right_p)
1074        }
1075
1076        // Absolute path: analyze the inner expression
1077        Expr::AbsolutePath(inner) => compute_expr_priority(inner),
1078
1079        // Relative path: priority is based on the final step (rightmost)
1080        Expr::RelativePath(_, right) => compute_expr_priority(right),
1081
1082        // Single step: determine priority from the node test and axis
1083        Expr::Step(step) => compute_step_priority(step),
1084
1085        // Filter expression: priority based on the primary expression
1086        Expr::Filter(primary, _) => compute_expr_priority(primary),
1087
1088        // id() and key() functions: priority 0.0
1089        Expr::FunctionCall { name, .. } => {
1090            if name == "id" || name == "key" {
1091                0.0
1092            } else {
1093                // Bare node tests (node(), text(), comment(),
1094                // processing-instruction()) parse as function calls at the
1095                // top level; translate them to their step priorities.
1096                match name.as_str() {
1097                    "node" => -0.25,
1098                    "text" | "comment" | "processing-instruction" => 0.0,
1099                    _ => 0.5,
1100                }
1101            }
1102        }
1103
1104        // Other expressions (literals, variables, etc.) — not typical patterns
1105        _ => 0.5,
1106    }
1107}
1108
1109/// Compute the default priority of a single step.
1110fn compute_step_priority(step: &Step) -> f64 {
1111    match &step.node_test {
1112        // node() test: -0.25
1113        NodeTest::Node => -0.25,
1114
1115        // text(), comment(), processing-instruction(): 0.0
1116        NodeTest::Text | NodeTest::Comment | NodeTest::ProcessingInstruction(_) => 0.0,
1117
1118        // Name test: 0.0 for a specific name on the child axis;
1119        // 0.5 on the attribute axis (@QName per XSLT 1.0 §5.5).
1120        NodeTest::NameTest(name_test) => match name_test {
1121            NameTest::LocalName(_) | NameTest::QName { .. } => {
1122                if step.axis == Axis::Attribute {
1123                    0.5
1124                } else {
1125                    0.0
1126                }
1127            }
1128            NameTest::Any => {
1129                // * in element context: -0.5
1130                // * in attribute context: +0.5
1131                if step.axis == Axis::Attribute {
1132                    0.5
1133                } else {
1134                    -0.5
1135                }
1136            }
1137        },
1138
1139        // * (Wildcard): -0.5 in element context, +0.5 in attribute context
1140        NodeTest::Wildcard => {
1141            if step.axis == Axis::Attribute {
1142                0.5
1143            } else {
1144                -0.5
1145            }
1146        }
1147
1148        // prefix:* namespace wildcard: -0.5
1149        NodeTest::NsWildcard(_) => {
1150            if step.axis == Axis::Attribute {
1151                0.5
1152            } else {
1153                -0.5
1154            }
1155        }
1156    }
1157}
1158
1159// ═══════════════════════════════════════════════════════════════════════════════
1160// Convenience Functions
1161// ═══════════════════════════════════════════════════════════════════════════════
1162
1163/// Check if a pattern string matches a single step (simple name test).
1164///
1165/// Returns true if the pattern is a simple name test like `para` or `foo:bar`,
1166/// without path separators, predicates, or union operators.
1167pub fn is_simple_name_pattern(pattern: &str) -> bool {
1168    let expr = match parse_xpath(pattern) {
1169        Ok(e) => e,
1170        Err(_) => return false,
1171    };
1172
1173    matches!(&expr, Expr::Step(Step {
1174        axis: Axis::Child,
1175        node_test: NodeTest::NameTest(name_test),
1176        predicates,
1177    }) if predicates.is_empty() && !matches!(name_test, NameTest::Any))
1178}
1179
1180/// Check if a pattern is a union pattern (contains `|`).
1181pub fn is_union_pattern(pattern: &str) -> bool {
1182    let expr = match parse_xpath(pattern) {
1183        Ok(e) => e,
1184        Err(_) => return false,
1185    };
1186
1187    matches!(&expr, Expr::Union(_, _))
1188}
1189
1190/// Get the names matched by a simple name-test pattern.
1191///
1192/// For a simple pattern like `para` or `foo | bar`, returns the list of
1193/// matched element names. Returns an empty vec for complex patterns.
1194pub fn get_pattern_matched_names(pattern: &str) -> Vec<String> {
1195    let expr = match parse_xpath(pattern) {
1196        Ok(e) => e,
1197        Err(_) => return vec![],
1198    };
1199
1200    let mut names = Vec::new();
1201    collect_matched_names(&expr, &mut names);
1202    names
1203}
1204
1205fn collect_matched_names(expr: &Expr, names: &mut Vec<String>) {
1206    match expr {
1207        Expr::Union(left, right) => {
1208            collect_matched_names(left, names);
1209            collect_matched_names(right, names);
1210        }
1211        Expr::Step(Step {
1212            node_test: NodeTest::NameTest(name_test),
1213            ..
1214        }) => match name_test {
1215            NameTest::LocalName(local) => names.push(local.clone()),
1216            NameTest::QName { prefix, local } => names.push(format!("{}:{}", prefix, local)),
1217            NameTest::Any => names.push("*".to_string()),
1218        },
1219        Expr::Step(Step {
1220            node_test: NodeTest::Wildcard,
1221            ..
1222        }) => {
1223            names.push("*".to_string());
1224        }
1225        Expr::Step(Step {
1226            node_test: NodeTest::NsWildcard(prefix),
1227            ..
1228        }) => {
1229            names.push(format!("{}:*", prefix));
1230        }
1231        _ => {}
1232    }
1233}
1234
1235// ═══════════════════════════════════════════════════════════════════════════════
1236// Tests
1237// ═══════════════════════════════════════════════════════════════════════════════
1238
1239#[cfg(test)]
1240mod tests {
1241    use super::*;
1242
1243    // ── Priority Tests ────────────────────────────────────────────────────
1244
1245    #[test]
1246    fn test_default_priority_name_test() {
1247        // Simple name test "para" → 0.0
1248        let priority = compute_default_priority("para");
1249        assert!(
1250            (priority - 0.0).abs() < f64::EPSILON,
1251            "Expected 0.0 for name test, got {}",
1252            priority
1253        );
1254    }
1255
1256    #[test]
1257    fn test_default_priority_qname() {
1258        // Qualified name "xslt:template" → 0.0
1259        let priority = compute_default_priority("xslt:template");
1260        assert!(
1261            (priority - 0.0).abs() < f64::EPSILON,
1262            "Expected 0.0 for QName, got {}",
1263            priority
1264        );
1265    }
1266
1267    #[test]
1268    fn test_default_priority_node_test() {
1269        // node() test → -0.25
1270        let priority = compute_default_priority("node()");
1271        assert!(
1272            (priority - (-0.25)).abs() < f64::EPSILON,
1273            "Expected -0.25 for node(), got {}",
1274            priority
1275        );
1276    }
1277
1278    #[test]
1279    fn test_default_priority_text_test() {
1280        // text() test → 0.0
1281        let priority = compute_default_priority("text()");
1282        assert!(
1283            (priority - 0.0).abs() < f64::EPSILON,
1284            "Expected 0.0 for text(), got {}",
1285            priority
1286        );
1287    }
1288
1289    #[test]
1290    fn test_default_priority_comment_test() {
1291        // comment() test → 0.0
1292        let priority = compute_default_priority("comment()");
1293        assert!(
1294            (priority - 0.0).abs() < f64::EPSILON,
1295            "Expected 0.0 for comment(), got {}",
1296            priority
1297        );
1298    }
1299
1300    #[test]
1301    fn test_default_priority_processing_instruction() {
1302        // processing-instruction() test → 0.0
1303        let priority = compute_default_priority("processing-instruction()");
1304        assert!(
1305            (priority - 0.0).abs() < f64::EPSILON,
1306            "Expected 0.0 for processing-instruction(), got {}",
1307            priority
1308        );
1309    }
1310
1311    #[test]
1312    fn test_default_priority_wildcard() {
1313        // * wildcard → -0.5
1314        let priority = compute_default_priority("*");
1315        assert!(
1316            (priority - (-0.5)).abs() < f64::EPSILON,
1317            "Expected -0.5 for *, got {}",
1318            priority
1319        );
1320    }
1321
1322    #[test]
1323    fn test_default_priority_ns_wildcard() {
1324        // ns:* wildcard → -0.5
1325        let priority = compute_default_priority("ns:*");
1326        assert!(
1327            (priority - (-0.5)).abs() < f64::EPSILON,
1328            "Expected -0.5 for ns:*, got {}",
1329            priority
1330        );
1331    }
1332
1333    #[test]
1334    fn test_default_priority_attribute() {
1335        // @attr → +0.5
1336        let priority = compute_default_priority("@attr");
1337        assert!(
1338            (priority - 0.5).abs() < f64::EPSILON,
1339            "Expected 0.5 for @attr, got {}",
1340            priority
1341        );
1342    }
1343
1344    #[test]
1345    fn test_default_priority_attribute_wildcard() {
1346        // @* → +0.5
1347        let priority = compute_default_priority("@*");
1348        assert!(
1349            (priority - 0.5).abs() < f64::EPSILON,
1350            "Expected 0.5 for @*, got {}",
1351            priority
1352        );
1353    }
1354
1355    #[test]
1356    fn test_default_priority_union() {
1357        // Union "para | *" → max(0.0, -0.5) = 0.0
1358        let priority = compute_default_priority("para | *");
1359        assert!(
1360            (priority - 0.0).abs() < f64::EPSILON,
1361            "Expected 0.0 for union, got {}",
1362            priority
1363        );
1364    }
1365
1366    #[test]
1367    fn test_default_priority_compound_path() {
1368        // Path "foo/bar" → priority of last step "bar" = 0.0
1369        let priority = compute_default_priority("foo/bar");
1370        assert!(
1371            (priority - 0.0).abs() < f64::EPSILON,
1372            "Expected 0.0 for foo/bar, got {}",
1373            priority
1374        );
1375    }
1376
1377    #[test]
1378    fn test_default_priority_empty() {
1379        // Empty pattern → 0.5
1380        let priority = compute_default_priority("");
1381        assert!(
1382            (priority - 0.5).abs() < f64::EPSILON,
1383            "Expected 0.5 for empty pattern, got {}",
1384            priority
1385        );
1386    }
1387
1388    // ── Pattern Matching Tests ────────────────────────────────────────────
1389
1390    /// Create a minimal element node for testing.
1391    unsafe fn create_test_node(name: &str, type_: c_int) -> *mut _xmlNode {
1392        let layout = std::alloc::Layout::new::<_xmlNode>();
1393        let ptr = std::alloc::alloc_zeroed(layout) as *mut _xmlNode;
1394        if ptr.is_null() {
1395            return ptr::null_mut();
1396        }
1397        let node = &mut *ptr;
1398        node.type_ = type_;
1399        // Allocate and copy the name
1400        let name_bytes = name.as_bytes();
1401        let name_buf = std::alloc::alloc_zeroed(
1402            std::alloc::Layout::array::<u8>(name_bytes.len() + 1).unwrap(),
1403        );
1404        if !name_buf.is_null() {
1405            std::ptr::copy_nonoverlapping(name_bytes.as_ptr(), name_buf, name_bytes.len());
1406        }
1407        node.name = name_buf as *mut xmlChar;
1408        ptr
1409    }
1410
1411    /// Free a test node.
1412    unsafe fn free_test_node(node: *mut _xmlNode) {
1413        if node.is_null() {
1414            return;
1415        }
1416        if !(*node).name.is_null() {
1417            let name = (*node).name;
1418            // Find length
1419            let len = crate::abi::exports_xml2::xmlStrlen(name) as usize;
1420            std::alloc::dealloc(
1421                name as *mut u8,
1422                std::alloc::Layout::array::<u8>(len + 1).unwrap(),
1423            );
1424        }
1425        let layout = std::alloc::Layout::new::<_xmlNode>();
1426        std::alloc::dealloc(node as *mut u8, layout);
1427    }
1428
1429    #[test]
1430    fn test_node_test_matching_element() {
1431        unsafe {
1432            let node = create_test_node("para", 1); // XML_ELEMENT_NODE
1433            assert!(!node.is_null());
1434
1435            // Name test
1436            let name_test = NodeTest::NameTest(NameTest::LocalName("para".to_string()));
1437            assert!(match_node_test(node, &name_test));
1438
1439            // Wrong name
1440            let wrong_test = NodeTest::NameTest(NameTest::LocalName("foo".to_string()));
1441            assert!(!match_node_test(node, &wrong_test));
1442
1443            // Wildcard
1444            let wildcard = NodeTest::Wildcard;
1445            assert!(match_node_test(node, &wildcard));
1446
1447            // Node test
1448            let node_test = NodeTest::Node;
1449            assert!(match_node_test(node, &node_test));
1450
1451            // Text test should not match element
1452            let text_test = NodeTest::Text;
1453            assert!(!match_node_test(node, &text_test));
1454
1455            free_test_node(node);
1456        }
1457    }
1458
1459    #[test]
1460    fn test_node_test_matching_text() {
1461        unsafe {
1462            let node = create_test_node("", 3); // XML_TEXT_NODE
1463            assert!(!node.is_null());
1464
1465            let text_test = NodeTest::Text;
1466            assert!(match_node_test(node, &text_test));
1467
1468            let node_test = NodeTest::Node;
1469            assert!(match_node_test(node, &node_test));
1470
1471            let comment_test = NodeTest::Comment;
1472            assert!(!match_node_test(node, &comment_test));
1473
1474            let element_test = NodeTest::NameTest(NameTest::LocalName("para".to_string()));
1475            assert!(!match_node_test(node, &element_test));
1476
1477            free_test_node(node);
1478        }
1479    }
1480
1481    #[test]
1482    fn test_compile_and_free_pattern() {
1483        unsafe {
1484            let pattern_str = c"para".as_ptr() as *const xmlChar;
1485            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1486            assert!(!compiled.is_null());
1487            xsltFreePattern(compiled);
1488        }
1489    }
1490
1491    #[test]
1492    fn test_compile_null_pattern() {
1493        unsafe {
1494            let compiled = xsltCompilePattern(ptr::null(), ptr::null_mut());
1495            assert!(compiled.is_null());
1496        }
1497    }
1498
1499    #[test]
1500    fn test_compile_empty_pattern() {
1501        unsafe {
1502            let pattern_str = c"".as_ptr() as *const xmlChar;
1503            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1504            assert!(compiled.is_null());
1505        }
1506    }
1507
1508    #[test]
1509    fn test_free_null_pattern() {
1510        unsafe {
1511            xsltFreePattern(ptr::null_mut());
1512            // Should not crash
1513        }
1514    }
1515
1516    #[test]
1517    fn test_is_simple_name_pattern() {
1518        assert!(is_simple_name_pattern("para"));
1519        assert!(is_simple_name_pattern("foo:bar"));
1520        assert!(!is_simple_name_pattern("foo/bar"));
1521        assert!(!is_simple_name_pattern("para | foo"));
1522        assert!(!is_simple_name_pattern("*"));
1523    }
1524
1525    #[test]
1526    fn test_is_union_pattern() {
1527        assert!(is_union_pattern("para | foo"));
1528        assert!(is_union_pattern("para | foo | bar"));
1529        assert!(!is_union_pattern("para"));
1530        assert!(!is_union_pattern("foo/bar"));
1531    }
1532
1533    #[test]
1534    fn test_get_pattern_matched_names() {
1535        let names = get_pattern_matched_names("para");
1536        assert_eq!(names, vec!["para"]);
1537
1538        let names = get_pattern_matched_names("foo | bar");
1539        assert_eq!(names.len(), 2);
1540        assert!(names.contains(&"foo".to_string()));
1541        assert!(names.contains(&"bar".to_string()));
1542
1543        let names = get_pattern_matched_names("foo/bar");
1544        assert!(names.is_empty());
1545    }
1546
1547    #[test]
1548    fn test_compile_union_pattern() {
1549        unsafe {
1550            let pattern_str = c"para | foo".as_ptr() as *const xmlChar;
1551            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1552            assert!(!compiled.is_null());
1553            xsltFreePattern(compiled);
1554        }
1555    }
1556
1557    #[test]
1558    fn test_compile_compound_pattern() {
1559        unsafe {
1560            let pattern_str = c"foo/bar".as_ptr() as *const xmlChar;
1561            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1562            assert!(!compiled.is_null());
1563            xsltFreePattern(compiled);
1564        }
1565    }
1566
1567    #[test]
1568    fn test_compile_absolute_pattern() {
1569        unsafe {
1570            let pattern_str = c"/foo/bar".as_ptr() as *const xmlChar;
1571            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1572            assert!(!compiled.is_null());
1573            xsltFreePattern(compiled);
1574        }
1575    }
1576
1577    #[test]
1578    fn test_compile_attribute_pattern() {
1579        unsafe {
1580            let pattern_str = c"@attr".as_ptr() as *const xmlChar;
1581            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1582            assert!(!compiled.is_null());
1583            xsltFreePattern(compiled);
1584        }
1585    }
1586
1587    #[test]
1588    fn test_compile_wildcard_pattern() {
1589        unsafe {
1590            let pattern_str = c"*".as_ptr() as *const xmlChar;
1591            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1592            assert!(!compiled.is_null());
1593            xsltFreePattern(compiled);
1594        }
1595    }
1596
1597    #[test]
1598    fn test_compile_ns_wildcard_pattern() {
1599        unsafe {
1600            let pattern_str = c"ns:*".as_ptr() as *const xmlChar;
1601            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1602            assert!(!compiled.is_null());
1603            xsltFreePattern(compiled);
1604        }
1605    }
1606
1607    #[test]
1608    fn test_compile_node_test_pattern() {
1609        unsafe {
1610            let pattern_str = c"node()".as_ptr() as *const xmlChar;
1611            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1612            assert!(!compiled.is_null());
1613            xsltFreePattern(compiled);
1614        }
1615    }
1616
1617    #[test]
1618    fn test_compile_text_pattern() {
1619        unsafe {
1620            let pattern_str = c"text()".as_ptr() as *const xmlChar;
1621            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1622            assert!(!compiled.is_null());
1623            xsltFreePattern(compiled);
1624        }
1625    }
1626
1627    #[test]
1628    fn test_compile_comment_pattern() {
1629        unsafe {
1630            let pattern_str = c"comment()".as_ptr() as *const xmlChar;
1631            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1632            assert!(!compiled.is_null());
1633            xsltFreePattern(compiled);
1634        }
1635    }
1636
1637    #[test]
1638    fn test_compile_pi_pattern() {
1639        unsafe {
1640            let pattern_str = c"processing-instruction()".as_ptr() as *const xmlChar;
1641            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1642            assert!(!compiled.is_null());
1643            xsltFreePattern(compiled);
1644        }
1645    }
1646
1647    #[test]
1648    fn test_compile_predicate_pattern() {
1649        unsafe {
1650            let pattern_str = c"para[1]".as_ptr() as *const xmlChar;
1651            let compiled = xsltCompilePattern(pattern_str, ptr::null_mut());
1652            assert!(!compiled.is_null());
1653            xsltFreePattern(compiled);
1654        }
1655    }
1656
1657    #[test]
1658    fn test_decompose_union() {
1659        let expr = parse_xpath("a | b").unwrap();
1660        let patterns = decompose_pattern(&expr, "a | b");
1661        assert!(patterns.is_some());
1662        let patterns = patterns.unwrap();
1663        assert_eq!(patterns.len(), 2);
1664        assert_eq!(patterns[0].original, "a | b");
1665        assert_eq!(patterns[1].original, "a | b");
1666    }
1667
1668    #[test]
1669    fn test_decompose_single() {
1670        let expr = parse_xpath("para").unwrap();
1671        let patterns = decompose_pattern(&expr, "para");
1672        assert!(patterns.is_some());
1673        let patterns = patterns.unwrap();
1674        assert_eq!(patterns.len(), 1);
1675    }
1676
1677    #[test]
1678    fn test_collect_steps_simple() {
1679        let expr = parse_xpath("para").unwrap();
1680        let (steps, is_absolute) = collect_steps(&expr).unwrap();
1681        assert!(!is_absolute);
1682        assert_eq!(steps.len(), 1);
1683        if let PatternStepEntry::Step(step) = &steps[0] {
1684            assert_eq!(step.axis, Axis::Child);
1685            assert!(
1686                matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "para")
1687            );
1688        } else {
1689            panic!("Expected Step entry");
1690        }
1691    }
1692
1693    #[test]
1694    fn test_collect_steps_absolute() {
1695        let expr = parse_xpath("/foo/bar").unwrap();
1696        let (steps, is_absolute) = collect_steps(&expr).unwrap();
1697        assert!(is_absolute);
1698        assert_eq!(steps.len(), 2);
1699    }
1700
1701    #[test]
1702    fn test_collect_steps_attribute() {
1703        let expr = parse_xpath("@attr").unwrap();
1704        let (steps, is_absolute) = collect_steps(&expr).unwrap();
1705        assert!(!is_absolute);
1706        assert_eq!(steps.len(), 1);
1707        if let PatternStepEntry::Step(step) = &steps[0] {
1708            assert_eq!(step.axis, Axis::Attribute);
1709        } else {
1710            panic!("Expected Step entry");
1711        }
1712    }
1713
1714    #[test]
1715    fn test_collect_steps_compound() {
1716        let expr = parse_xpath("foo/bar").unwrap();
1717        let (steps, is_absolute) = collect_steps(&expr).unwrap();
1718        assert!(!is_absolute);
1719        assert_eq!(steps.len(), 2);
1720        // First step should be "bar" (rightmost)
1721        if let PatternStepEntry::Step(step) = &steps[0] {
1722            assert!(
1723                matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "bar")
1724            );
1725        } else {
1726            panic!("Expected Step entry for bar");
1727        }
1728        // Second step should be "foo"
1729        if let PatternStepEntry::Step(step) = &steps[1] {
1730            assert!(
1731                matches!(&step.node_test, NodeTest::NameTest(NameTest::LocalName(n)) if n == "foo")
1732            );
1733        } else {
1734            panic!("Expected Step entry for foo");
1735        }
1736    }
1737
1738    #[test]
1739    fn test_match_name_test_local() {
1740        unsafe {
1741            let node = create_test_node("para", 1);
1742            assert!(!node.is_null());
1743
1744            assert!(match_name_test(
1745                node,
1746                &NameTest::LocalName("para".to_string())
1747            ));
1748            assert!(!match_name_test(
1749                node,
1750                &NameTest::LocalName("foo".to_string())
1751            ));
1752            assert!(match_name_test(node, &NameTest::Any));
1753
1754            free_test_node(node);
1755        }
1756    }
1757
1758    #[test]
1759    fn test_match_node_test_wildcard() {
1760        unsafe {
1761            let element = create_test_node("para", 1);
1762            let text = create_test_node("", 3);
1763            let comment = create_test_node("", 8);
1764
1765            let wildcard = NodeTest::Wildcard;
1766            assert!(match_node_test(element, &wildcard));
1767            assert!(!match_node_test(text, &wildcard));
1768            assert!(!match_node_test(comment, &wildcard));
1769
1770            free_test_node(element);
1771            free_test_node(text);
1772            free_test_node(comment);
1773        }
1774    }
1775
1776    #[test]
1777    fn test_match_node_test_ns_wildcard() {
1778        unsafe {
1779            let node = create_test_node("para", 1);
1780            // No namespace set — only empty prefix matches
1781            let ns_wildcard = NodeTest::NsWildcard("".to_string());
1782            assert!(match_node_test(node, &ns_wildcard));
1783
1784            let ns_wildcard = NodeTest::NsWildcard("foo".to_string());
1785            assert!(!match_node_test(node, &ns_wildcard));
1786
1787            free_test_node(node);
1788        }
1789    }
1790
1791    #[test]
1792    fn test_compute_priority_on_compiled_pattern() {
1793        unsafe {
1794            // Test through the C ABI function
1795            let pattern_str = c"para".as_ptr() as *const xmlChar;
1796            let priority = xsltDefaultPriority(pattern_str);
1797            assert!(
1798                (priority - 0.0).abs() < f64::EPSILON,
1799                "Expected 0.0 for 'para', got {}",
1800                priority
1801            );
1802
1803            let pattern_str = c"*".as_ptr() as *const xmlChar;
1804            let priority = xsltDefaultPriority(pattern_str);
1805            assert!(
1806                (priority - (-0.5)).abs() < f64::EPSILON,
1807                "Expected -0.5 for '*', got {}",
1808                priority
1809            );
1810
1811            let pattern_str = c"node()".as_ptr() as *const xmlChar;
1812            let priority = xsltDefaultPriority(pattern_str);
1813            assert!(
1814                (priority - (-0.25)).abs() < f64::EPSILON,
1815                "Expected -0.25 for 'node()', got {}",
1816                priority
1817            );
1818
1819            let pattern_str = c"@attr".as_ptr() as *const xmlChar;
1820            let priority = xsltDefaultPriority(pattern_str);
1821            assert!(
1822                (priority - 0.5).abs() < f64::EPSILON,
1823                "Expected 0.5 for '@attr', got {}",
1824                priority
1825            );
1826        }
1827    }
1828
1829    #[test]
1830    fn test_compute_priority_null() {
1831        unsafe {
1832            let priority = xsltDefaultPriority(ptr::null());
1833            assert!(
1834                (priority - 0.5).abs() < f64::EPSILON,
1835                "Expected 0.5 for null pattern, got {}",
1836                priority
1837            );
1838        }
1839    }
1840
1841    #[test]
1842    fn test_compute_priority_empty() {
1843        unsafe {
1844            let pattern_str = c"".as_ptr() as *const xmlChar;
1845            let priority = xsltDefaultPriority(pattern_str);
1846            assert!(
1847                (priority - 0.5).abs() < f64::EPSILON,
1848                "Expected 0.5 for empty pattern, got {}",
1849                priority
1850            );
1851        }
1852    }
1853
1854    #[test]
1855    fn test_xslt_test_pattern_null_args() {
1856        unsafe {
1857            let result = xsltTestPattern(ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
1858            assert_eq!(result, 0);
1859        }
1860    }
1861}