Skip to main content

xrust/
xslt.rs

1/*! ## An XSLT compiler
2
3Compile an XSLT stylesheet into a [Transform]ation.
4
5Once the stylesheet has been compiled, it may then be evaluated with an appropriate context.
6
7NB. This module, by default, does not resolve include or import statements. See the xrust-net crate for a helper module to do that.
8
9```rust
10use std::rc::Rc;
11use xrust::xdmerror::{Error, ErrorKind};
12use xrust::item::{Item, Node, NodeType, Sequence, SequenceTrait};
13use xrust::transform::Transform;
14use xrust::transform::context::{StaticContext, StaticContextBuilder};
15use xrust::trees::smite::RNode;
16use xrust::parser::ParseError;
17use xrust::parser::xml::parse;
18use xrust::xslt::from_document;
19
20// A little helper function to parse an XML document
21fn make_from_str(s: &str) -> Result<RNode, Error> {
22    let doc = RNode::new_document();
23    let e = parse(doc.clone(), s,
24        Some(|_: &_| Err(ParseError::MissingNameSpace)))?;
25    Ok(doc)
26}
27
28// The source document (a tree)
29let src = Item::Node(
30    make_from_str("<Example><Title>XSLT in Rust</Title><Paragraph>A simple document.</Paragraph></Example>")
31    .expect("unable to parse XML")
32);
33
34// The XSL stylesheet
35let style = make_from_str("<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
36  <xsl:template match='child::Example'><html><xsl:apply-templates/></html></xsl:template>
37  <xsl:template match='child::Title'><head><title><xsl:apply-templates/></title></head></xsl:template>
38  <xsl:template match='child::Paragraph'><body><p><xsl:apply-templates/></p></body></xsl:template>
39</xsl:stylesheet>")
40    .expect("unable to parse stylesheet");
41
42// Create a static context (with dummy callbacks)
43let mut static_context = StaticContextBuilder::new()
44    .message(|_| Ok(()))
45    .fetcher(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
46    .parser(|_| Err(Error::new(ErrorKind::NotImplemented, "not implemented")))
47    .build();
48
49// Compile the stylesheet
50let mut ctxt = from_document(
51    style,
52    None,
53    make_from_str,
54    |_| Ok(String::new())
55).expect("failed to compile stylesheet");
56
57// Set the source document as the context item
58ctxt.context(vec![src], 0);
59// Make an empty result document
60ctxt.result_document(RNode::new_document());
61
62// Let 'er rip!
63// Evaluate the transformation
64let seq = ctxt.evaluate(&mut static_context)
65    .expect("evaluation failed");
66
67// Serialise the sequence as XML
68assert_eq!(seq.to_xml(), "<html><head><title>XSLT in Rust</title></head><body><p>A simple document.</p></body></html>")
69 */
70
71use std::collections::HashMap;
72use std::rc::Rc;
73
74use crate::item::{Item, Node, NodeType, Sequence};
75use crate::output::{OutputDefinition, OutputSpec};
76use crate::parser::avt::parse as parse_avt;
77use crate::parser::xpath::parse;
78use crate::pattern::{Branch, Pattern};
79use crate::transform::callable::{ActualParameters, Callable, FormalParameters};
80use crate::transform::context::{Context, ContextBuilder};
81use crate::transform::numbers::{Level, Numbering};
82use crate::transform::template::Template;
83use crate::transform::{
84    Axis, Grouping, KindTest, NameTest, NodeMatch, NodeTest, Order, Transform, WildcardOrName,
85    WildcardOrNamespaceUri, in_scope_namespaces,
86};
87use crate::value::Value;
88use crate::xdmerror::*;
89use qualname::{NamespaceUri, NcName, QName};
90use std::convert::TryFrom;
91use std::sync::LazyLock;
92use url::Url;
93
94// Define constant QNames for faster comparison
95static XSLTNS: LazyLock<Option<NamespaceUri>> =
96    LazyLock::new(|| Some(NamespaceUri::try_from("http://www.w3.org/1999/XSL/Transform").unwrap()));
97static XRUSTNS: LazyLock<Option<NamespaceUri>> =
98    LazyLock::new(|| Some(NamespaceUri::try_from("http://github.com/ballsteve/xrust").unwrap()));
99static XSLSTYLESHEET: LazyLock<QName> = LazyLock::new(|| {
100    QName::new_from_parts(NcName::try_from("stylesheet").unwrap(), XSLTNS.clone())
101});
102static XSLTRANSFORM: LazyLock<QName> =
103    LazyLock::new(|| QName::new_from_parts(NcName::try_from("transform").unwrap(), XSLTNS.clone()));
104static XSLOUTPUT: LazyLock<QName> =
105    LazyLock::new(|| QName::new_from_parts(NcName::try_from("output").unwrap(), XSLTNS.clone()));
106static XSLINCLUDE: LazyLock<QName> =
107    LazyLock::new(|| QName::new_from_parts(NcName::try_from("include").unwrap(), XSLTNS.clone()));
108static XRUSTIMPORT: LazyLock<QName> =
109    LazyLock::new(|| QName::new_from_parts(NcName::try_from("import").unwrap(), XRUSTNS.clone()));
110static XSLIMPORT: LazyLock<QName> =
111    LazyLock::new(|| QName::new_from_parts(NcName::try_from("import").unwrap(), XSLTNS.clone()));
112static XSLATTRIBUTESET: LazyLock<QName> = LazyLock::new(|| {
113    QName::new_from_parts(NcName::try_from("attribute-set").unwrap(), XSLTNS.clone())
114});
115static XSLATTRIBUTE: LazyLock<QName> =
116    LazyLock::new(|| QName::new_from_parts(NcName::try_from("attribute").unwrap(), XSLTNS.clone()));
117static XSLTEMPLATE: LazyLock<QName> =
118    LazyLock::new(|| QName::new_from_parts(NcName::try_from("template").unwrap(), XSLTNS.clone()));
119static XSLKEY: LazyLock<QName> =
120    LazyLock::new(|| QName::new_from_parts(NcName::try_from("key").unwrap(), XSLTNS.clone()));
121static XSLPARAM: LazyLock<QName> =
122    LazyLock::new(|| QName::new_from_parts(NcName::try_from("param").unwrap(), XSLTNS.clone()));
123static XSLFUNCTION: LazyLock<QName> =
124    LazyLock::new(|| QName::new_from_parts(NcName::try_from("function").unwrap(), XSLTNS.clone()));
125static XSLVARIABLE: LazyLock<QName> =
126    LazyLock::new(|| QName::new_from_parts(NcName::try_from("variable").unwrap(), XSLTNS.clone()));
127static XSLVALUEOF: LazyLock<QName> =
128    LazyLock::new(|| QName::new_from_parts(NcName::try_from("value-of").unwrap(), XSLTNS.clone()));
129static XSLTEXT: LazyLock<QName> =
130    LazyLock::new(|| QName::new_from_parts(NcName::try_from("text").unwrap(), XSLTNS.clone()));
131static XSLAPPLYTEMPLATES: LazyLock<QName> = LazyLock::new(|| {
132    QName::new_from_parts(NcName::try_from("apply-templates").unwrap(), XSLTNS.clone())
133});
134static XSLAPPLYIMPORTS: LazyLock<QName> = LazyLock::new(|| {
135    QName::new_from_parts(NcName::try_from("apply-imports").unwrap(), XSLTNS.clone())
136});
137static XSLSEQUENCE: LazyLock<QName> =
138    LazyLock::new(|| QName::new_from_parts(NcName::try_from("sequence").unwrap(), XSLTNS.clone()));
139static XSLIF: LazyLock<QName> =
140    LazyLock::new(|| QName::new_from_parts(NcName::try_from("if").unwrap(), XSLTNS.clone()));
141static XSLCHOOSE: LazyLock<QName> =
142    LazyLock::new(|| QName::new_from_parts(NcName::try_from("choose").unwrap(), XSLTNS.clone()));
143static XSLWHEN: LazyLock<QName> =
144    LazyLock::new(|| QName::new_from_parts(NcName::try_from("when").unwrap(), XSLTNS.clone()));
145static XSLOTHERWISE: LazyLock<QName> =
146    LazyLock::new(|| QName::new_from_parts(NcName::try_from("otherwise").unwrap(), XSLTNS.clone()));
147static XSLFOREACH: LazyLock<QName> =
148    LazyLock::new(|| QName::new_from_parts(NcName::try_from("for-each").unwrap(), XSLTNS.clone()));
149static XSLFOREACHGROUP: LazyLock<QName> = LazyLock::new(|| {
150    QName::new_from_parts(NcName::try_from("for-each-group").unwrap(), XSLTNS.clone())
151});
152static XSLCOPY: LazyLock<QName> =
153    LazyLock::new(|| QName::new_from_parts(NcName::try_from("copy").unwrap(), XSLTNS.clone()));
154static XSLCOPYOF: LazyLock<QName> =
155    LazyLock::new(|| QName::new_from_parts(NcName::try_from("copy-of").unwrap(), XSLTNS.clone()));
156static XSLCALLTEMPLATE: LazyLock<QName> = LazyLock::new(|| {
157    QName::new_from_parts(NcName::try_from("call-template").unwrap(), XSLTNS.clone())
158});
159static XSLWITHPARAM: LazyLock<QName> = LazyLock::new(|| {
160    QName::new_from_parts(NcName::try_from("with-param").unwrap(), XSLTNS.clone())
161});
162static XSLELEMENT: LazyLock<QName> =
163    LazyLock::new(|| QName::new_from_parts(NcName::try_from("element").unwrap(), XSLTNS.clone()));
164static XSLCOMMENT: LazyLock<QName> =
165    LazyLock::new(|| QName::new_from_parts(NcName::try_from("comment").unwrap(), XSLTNS.clone()));
166static XSLPROCESSINGINSTRUCTION: LazyLock<QName> = LazyLock::new(|| {
167    QName::new_from_parts(
168        NcName::try_from("processing-instruction").unwrap(),
169        XSLTNS.clone(),
170    )
171});
172static XSLMESSAGE: LazyLock<QName> =
173    LazyLock::new(|| QName::new_from_parts(NcName::try_from("message").unwrap(), XSLTNS.clone()));
174static XSLNUMBER: LazyLock<QName> =
175    LazyLock::new(|| QName::new_from_parts(NcName::try_from("number").unwrap(), XSLTNS.clone()));
176static XSLDECIMALFORMAT: LazyLock<QName> = LazyLock::new(|| {
177    QName::new_from_parts(NcName::try_from("decimal-format").unwrap(), XSLTNS.clone())
178});
179static XSLSORT: LazyLock<QName> =
180    LazyLock::new(|| QName::new_from_parts(NcName::try_from("sort").unwrap(), XSLTNS.clone()));
181static XSLSTRIPSPACE: LazyLock<QName> = LazyLock::new(|| {
182    QName::new_from_parts(NcName::try_from("strip-space").unwrap(), XSLTNS.clone())
183});
184static XSLPRESERVESPACE: LazyLock<QName> = LazyLock::new(|| {
185    QName::new_from_parts(NcName::try_from("preserve-space").unwrap(), XSLTNS.clone())
186});
187static ATTRINDENT: LazyLock<QName> =
188    LazyLock::new(|| QName::new_from_parts(NcName::try_from("indent").unwrap(), None));
189static ATTRHREF: LazyLock<QName> =
190    LazyLock::new(|| QName::new_from_parts(NcName::try_from("href").unwrap(), None));
191static ATTRNAME: LazyLock<QName> =
192    LazyLock::new(|| QName::new_from_parts(NcName::try_from("name").unwrap(), None));
193static ATTRMATCH: LazyLock<QName> =
194    LazyLock::new(|| QName::new_from_parts(NcName::try_from("match").unwrap(), None));
195static ATTRMODE: LazyLock<QName> =
196    LazyLock::new(|| QName::new_from_parts(NcName::try_from("mode").unwrap(), None));
197static ATTRPRIORITY: LazyLock<QName> =
198    LazyLock::new(|| QName::new_from_parts(NcName::try_from("priority").unwrap(), None));
199static ATTRUSE: LazyLock<QName> =
200    LazyLock::new(|| QName::new_from_parts(NcName::try_from("use").unwrap(), None));
201static ATTRSELECT: LazyLock<QName> =
202    LazyLock::new(|| QName::new_from_parts(NcName::try_from("select").unwrap(), None));
203static ATTRDOE: LazyLock<QName> = LazyLock::new(|| {
204    QName::new_from_parts(NcName::try_from("disable-output-escaping").unwrap(), None)
205});
206static ATTRTEST: LazyLock<QName> =
207    LazyLock::new(|| QName::new_from_parts(NcName::try_from("test").unwrap(), None));
208static ATTRGROUPBY: LazyLock<QName> =
209    LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-by").unwrap(), None));
210static ATTRGROUPADJACENT: LazyLock<QName> =
211    LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-adjacent").unwrap(), None));
212static ATTRGROUPSTARTINGWITH: LazyLock<QName> =
213    LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-starting-with").unwrap(), None));
214static ATTRGROUPENDINGWITH: LazyLock<QName> =
215    LazyLock::new(|| QName::new_from_parts(NcName::try_from("group-ending-with").unwrap(), None));
216//static ATTRUSEATTRIBUTESETS: LazyLock<QName> =
217//    LazyLock::new(|| QName::new_from_parts(NcName::try_from("use-attribute-sets").unwrap(), None));
218static XSLATTRUSEATTRIBUTESETS: LazyLock<QName> = LazyLock::new(|| {
219    QName::new_from_parts(
220        NcName::try_from("use-attribute-sets").unwrap(),
221        XSLTNS.clone(),
222    )
223});
224static ATTRTERMINATE: LazyLock<QName> =
225    LazyLock::new(|| QName::new_from_parts(NcName::try_from("terminate").unwrap(), None));
226static ATTRVALUE: LazyLock<QName> =
227    LazyLock::new(|| QName::new_from_parts(NcName::try_from("value").unwrap(), None));
228static ATTRLEVEL: LazyLock<QName> =
229    LazyLock::new(|| QName::new_from_parts(NcName::try_from("level").unwrap(), None));
230static ATTRCOUNT: LazyLock<QName> =
231    LazyLock::new(|| QName::new_from_parts(NcName::try_from("count").unwrap(), None));
232static ATTRFROM: LazyLock<QName> =
233    LazyLock::new(|| QName::new_from_parts(NcName::try_from("from").unwrap(), None));
234static ATTRFORMAT: LazyLock<QName> =
235    LazyLock::new(|| QName::new_from_parts(NcName::try_from("format").unwrap(), None));
236static ATTRORDER: LazyLock<QName> =
237    LazyLock::new(|| QName::new_from_parts(NcName::try_from("order").unwrap(), None));
238static ATTRELEMENTS: LazyLock<QName> =
239    LazyLock::new(|| QName::new_from_parts(NcName::try_from("elements").unwrap(), None));
240
241/// The XSLT trait allows an object to use an XSL Stylesheet to transform a document into a [Sequence].
242pub trait XSLT: Node {
243    /// Interpret the object as an XSL Stylesheet and transform a source document.
244    /// The [Node] that is given as the source document becomes the initial context for the transformation.
245    fn transform<N: Node, F, G>(
246        &self,
247        src: Rc<Item<N>>,
248        b: Option<Url>,
249        f: F,
250        g: G,
251    ) -> Result<Sequence<N>, Error>
252    where
253        F: Fn(&str) -> Result<N, Error>,
254        G: Fn(&Url) -> Result<String, Error>;
255    //    {
256    //        let sc = from_document(self.clone(), b, f, g)?;
257    //        let ctxt = ContextBuilder::from(&sc)
258    //            .current(vec![src])
259    //            .build();
260    //        ctxt.evaluate()
261    //    }
262}
263
264/// Compiles a [Node] into a transformation [Context].
265/// NB. Due to whitespace stripping, this is destructive of the stylesheet.
266/// The argument f is a closure that parses a string to a [Node].
267/// The argument g is a closure that resolves a URL to a string.
268/// These are used for include and import modules.
269/// They are not included in this module since some environments, in particular Wasm, do not have I/O facilities.
270pub fn from_document<N: Node, F, G>(
271    styledoc: N,
272    base: Option<Url>,
273    f: F,
274    g: G,
275) -> Result<Context<N>, Error>
276where
277    F: Fn(&str) -> Result<N, Error>,
278    G: Fn(&Url) -> Result<String, Error>,
279{
280    // Check that this is a valid XSLT stylesheet
281    // There must be a single element as a child of the root node, and it must be named xsl:stylesheet or xsl:transform
282    let mut rnit = styledoc.child_iter();
283    let stylenode = match rnit.next() {
284        Some(root) => {
285            // TODO: intern strings so that comparison is fast
286            if !(root
287                .name()
288                .is_some_and(|rn| rn == *XSLSTYLESHEET || rn == *XSLTRANSFORM))
289            {
290                return Result::Err(Error::new(
291                    ErrorKind::TypeError,
292                    String::from("not an XSLT stylesheet"),
293                ));
294            } else {
295                root
296            }
297        }
298        None => {
299            return Result::Err(Error::new(
300                ErrorKind::TypeError,
301                String::from("document does not have document element"),
302            ));
303        }
304    };
305    if rnit.next().is_some() {
306        return Result::Err(Error::new(
307            ErrorKind::TypeError,
308            String::from("extra element: not an XSLT stylesheet"),
309        ));
310    }
311
312    // TODO: check version attribute
313
314    // Strip whitespace from the stylesheet
315    strip_whitespace(
316        styledoc.clone(),
317        true,
318        &vec![NodeTest::Name(NameTest::Wildcard(
319            WildcardOrNamespaceUri::Wildcard,
320            WildcardOrName::Wildcard,
321        ))],
322        &vec![NodeTest::Name(NameTest::Name(XSLTEXT.clone()))],
323    )?;
324
325    // Setup the serialization of the primary result document
326    let mut od = OutputDefinition::new();
327    if let Some(c) = stylenode
328        .child_iter()
329        .find(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLOUTPUT)))
330    {
331        let b: bool = matches!(
332            c.get_attribute(&ATTRINDENT).to_string().as_str(),
333            "yes" | "true" | "1"
334        );
335
336        od.set_indent(b);
337    };
338
339    // Iterate over children, looking for includes
340    // * resolve href
341    // * fetch document
342    // * parse XML
343    // * replace xsl:include element with content
344    stylenode
345        .child_iter()
346        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLINCLUDE))
347        .try_for_each(|mut c| {
348            let h = c.get_attribute(&ATTRHREF);
349            let url = match base.clone().map_or_else(
350                || Url::parse(h.to_string().as_str()),
351                |full| full.join(h.to_string().as_str()),
352            ) {
353                Ok(u) => u,
354                Err(_) => {
355                    return Result::Err(Error::new(
356                        ErrorKind::Unknown,
357                        format!(
358                            "unable to parse href URL \"{}\" baseurl \"{}\"",
359                            h,
360                            base.clone()
361                                .map_or(String::from("--no base--"), |b| b.to_string())
362                        ),
363                    ));
364                }
365            };
366            let xml = g(&url)?;
367            let module = f(xml.as_str().trim())?;
368            // TODO: check that the module is a valid XSLT stylesheet, etc
369            // Copy each top-level element of the module to the main stylesheet,
370            // inserting before the xsl:include node
371            let moddoc = module.first_child().unwrap();
372            moddoc.child_iter().try_for_each(|mc| {
373                c.insert_before(mc)?;
374                Ok::<(), Error>(())
375            })?;
376            // Remove the xsl:include element node
377            c.pop()?;
378            Ok(())
379        })?;
380
381    // Iterate over children, looking for imports
382    // * resolve href
383    // * fetch document
384    // * parse XML
385    // * replace xsl:import element with content
386    stylenode
387        .child_iter()
388        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLIMPORT))
389        .try_for_each(|mut c| {
390            let h = c.get_attribute(&ATTRHREF);
391            let url = match base.clone().map_or_else(
392                || Url::parse(h.to_string().as_str()),
393                |full| full.join(h.to_string().as_str()),
394            ) {
395                Ok(u) => u,
396                Err(_) => {
397                    return Result::Err(Error::new(
398                        ErrorKind::Unknown,
399                        format!(
400                            "unable to parse href URL \"{}\" baseurl \"{}\"",
401                            h,
402                            base.clone()
403                                .map_or(String::from("--no base--"), |b| b.to_string())
404                        ),
405                    ));
406                }
407            };
408            let xml = g(&url)?;
409            let module = f(xml.as_str().trim())?;
410            // TODO: check that the module is a valid XSLT stylesheet, etc
411            // Copy each top-level element of the module to the main stylesheet,
412            // inserting before the xsl:include node
413            // TODO: Don't Panic
414            let moddoc = module.first_child().unwrap();
415            moddoc.child_iter().try_for_each(|mc| {
416                if mc.node_type() == NodeType::Element {
417                    // Add the import precedence attribute
418                    let newnode = mc.deep_copy()?;
419                    let newat =
420                        styledoc.new_attribute(XRUSTIMPORT.clone(), Rc::new(Value::from(1)))?;
421                    newnode.add_attribute(newat)?;
422                    c.insert_before(newnode)?;
423                } else {
424                    let newnode = mc.deep_copy()?;
425                    c.insert_before(newnode)?;
426                }
427                Ok::<(), Error>(())
428            })?;
429            // Remove the xsl:import element node
430            c.pop()?;
431            Ok::<(), Error>(())
432        })?;
433
434    // Find named attribute sets
435
436    // Store for named attribute sets
437    let mut attr_sets: HashMap<QName, Vec<Transform<N>>> = HashMap::new();
438
439    stylenode
440        .child_iter()
441        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLATTRIBUTESET))
442        .try_for_each(|c| {
443            let name = c.get_attribute(&ATTRNAME);
444            let eqname = c.to_qname(name.to_string())?;
445            if eqname.to_string().is_empty() {
446                return Err(Error::new(
447                    ErrorKind::DynamicAbsent,
448                    "attribute sets must have a name",
449                ));
450            }
451            // xsl:attribute children
452            // TODO: check that there are no other children
453            let mut attrs = vec![];
454            c.child_iter()
455                .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLATTRIBUTE))
456                .try_for_each(|a| {
457                    attrs.push(to_transform(a, &attr_sets)?);
458                    Ok(())
459                })?;
460            attr_sets.insert(eqname, attrs);
461            Ok(())
462        })?;
463
464    // Iterate over children, looking for templates
465    // * compile match pattern
466    // * compile content into sequence constructor
467    // * register template in dynamic context
468    let mut templates: Vec<Template<N>> = vec![];
469    stylenode
470        .child_iter()
471        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLTEMPLATE))
472        .filter(|c| c.get_attribute_node(&ATTRMATCH).is_some())
473        .try_for_each(|c| {
474            let m = c.get_attribute(&ATTRMATCH);
475            let pat = Pattern::try_from((m.to_string(), c.clone())).map_err(|e| {
476                Error::new(
477                    e.kind,
478                    format!(
479                        "Error parsing match pattern \"{}\": {}",
480                        m.to_string(),
481                        e.message
482                    ),
483                )
484            })?;
485            if pat.is_err() {
486                return Err(pat.get_err().unwrap());
487            }
488            if let Pattern::Selection(Branch::Error(e)) = pat {
489                return Err(e.clone());
490            }
491            let mut body = vec![];
492            let mode = c.get_attribute_node(&ATTRMODE);
493            c.child_iter().try_for_each(|d| {
494                body.push(to_transform(d, &attr_sets)?);
495                Ok::<(), Error>(())
496            })?;
497            //sc.static_analysis(&mut pat);
498            //sc.static_analysis(&mut body);
499            // Determine the priority of the template
500            let pr = c.get_attribute(&ATTRPRIORITY);
501            let prio: f64 = match pr.to_string().as_str() {
502                "" => {
503                    // Calculate the default priority
504                    // TODO: more work to be done interpreting XSLT 6.5
505                    match &pat {
506                        Pattern::Predicate(p) => match p {
507                            Transform::Empty => -1.0,
508                            _ => 1.0,
509                        },
510                        Pattern::Selection(s) => {
511                            if let Branch::Error(e) = s {
512                                return Err(e.clone());
513                            }
514
515                            let (t, nt, q) = s.terminal_node_test();
516                            // If "/" then -0.5
517                            match (t, nt) {
518                                (Axis::SelfAttribute, _) => -0.5,
519                                (Axis::SelfAxis, Axis::Parent)
520                                | (Axis::SelfAxis, Axis::Ancestor)
521                                | (Axis::SelfAxis, Axis::AncestorOrSelf) => match q {
522                                    NodeTest::Name(nm) => match nm {
523                                        NameTest::Wildcard(_, _) => -0.5,
524                                        _ => 0.0,
525                                    },
526                                    NodeTest::Kind(_kt) => -0.5,
527                                },
528                                _ => 0.5,
529                            }
530                        }
531                        _ => -1.0,
532                    }
533                }
534                _ => pr.to_string().parse::<f64>().unwrap(), // TODO: better error handling
535            };
536            // Set the import precedence
537            let mut import: usize = 0;
538            let im = c.get_attribute(&XRUSTIMPORT);
539            if im.to_string() != "" {
540                import = im.to_int()? as usize
541            }
542            let mut qmode = None;
543            if let Some(modenode) = mode {
544                qmode = Some(modenode.to_qname(modenode.to_string())?)
545            }
546            templates.push(Template::new(
547                pat,
548                Transform::SequenceItems(body),
549                Some(prio),
550                vec![import],
551                None,
552                qmode,
553                m.to_string(),
554            ));
555            Ok::<(), Error>(())
556        })?;
557
558    // Iterate over the children, looking for key declarations.
559    // NB. could combine this with the previous loop, but performance shouldn't be an issue.
560    let mut keys = vec![];
561    stylenode
562        .child_iter()
563        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLKEY))
564        .try_for_each(|c| {
565            let name = c.get_attribute(&ATTRNAME);
566            let m = c.get_attribute(&ATTRMATCH);
567            let pat = Pattern::try_from(m.to_string())?;
568            let u = c.get_attribute(&ATTRUSE);
569            keys.push((
570                name,
571                pat,
572                parse::<N>(&u.to_string(), Some(c.clone()), None)?,
573            ));
574            Ok(())
575        })?;
576
577    let mut newctxt = ContextBuilder::new()
578        // Define the builtin templates
579        // See XSLT 6.7. This implements text-only-copy.
580        // TODO: Support deep-copy, shallow-copy, deep-skin, shallow-skip and fail
581        // This matches "/" and processes the root element
582        .template(Template::new(
583            Pattern::try_from("/")?,
584            Transform::ApplyTemplates(
585                Box::new(Transform::Step(NodeMatch::new(
586                    Axis::Child,
587                    NodeTest::Kind(KindTest::Any),
588                ))),
589                None,
590                vec![],
591            ),
592            None,
593            vec![0],
594            None,
595            None,
596            String::from("/"),
597        ))
598        // This matches "*" and applies templates to all children
599        .template(Template::new(
600            Pattern::try_from("child::*")?,
601            Transform::ApplyTemplates(
602                Box::new(Transform::Step(NodeMatch::new(
603                    Axis::Child,
604                    NodeTest::Kind(KindTest::Any),
605                ))),
606                None,
607                vec![],
608            ),
609            None,
610            vec![0],
611            None,
612            None,
613            String::from("child::*"),
614        ))
615        // This matches "text()" and copies content
616        .template(Template::new(
617            Pattern::try_from("child::text()")?,
618            Transform::ContextItem,
619            None,
620            vec![0],
621            None,
622            None,
623            String::from("child::text()"),
624        ))
625        .template_all(templates)
626        .output_definition(od)
627        .build();
628
629    keys.iter()
630        .for_each(|(name, m, u)| newctxt.declare_key(name.to_string(), m.clone(), u.clone()));
631
632    // Add named templates
633    stylenode
634        .child_iter()
635        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLTEMPLATE))
636        .filter(|c| !c.get_attribute(&ATTRNAME).to_string().is_empty())
637        .try_for_each(|c| {
638            let name = c.get_attribute(&ATTRNAME);
639            // xsl:param for formal parameters
640            // TODO: validate that xsl:param elements come first in the child list
641            // TODO: validate that xsl:param elements have unique name attributes
642            let mut params: Vec<(QName, Option<Transform<N>>)> = Vec::new();
643            c.child_iter()
644                .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM))
645                .try_for_each(|c| {
646                    let p_name = c.get_attribute(&ATTRNAME);
647                    if p_name.to_string().is_empty() {
648                        Err(Error::new(
649                            ErrorKind::StaticAbsent,
650                            "name attribute is missing",
651                        ))
652                    } else {
653                        let sel = c.get_attribute(&ATTRSELECT);
654                        if sel.to_string().is_empty() {
655                            // xsl:param content is the sequence constructor
656                            let mut body = vec![];
657                            c.child_iter().try_for_each(|d| {
658                                body.push(to_transform(d, &attr_sets)?);
659                                Ok(())
660                            })?;
661                            params.push((
662                                QName::from_local_name(
663                                    NcName::try_from(p_name.to_string().as_str()).map_err(
664                                        |_| Error::new(ErrorKind::ParseError, "not a QName"),
665                                    )?,
666                                ),
667                                Some(Transform::SequenceItems(body)),
668                            ));
669                            Ok(())
670                        } else {
671                            // select attribute value is an expression
672                            params.push((
673                                QName::from_local_name(
674                                    NcName::try_from(p_name.to_string().as_str()).map_err(
675                                        |_| Error::new(ErrorKind::ParseError, "not a QName"),
676                                    )?,
677                                ),
678                                Some(parse::<N>(&sel.to_string(), Some(c.clone()), None)?),
679                            ));
680                            Ok(())
681                        }
682                    }
683                })?;
684            // Content is the template body
685            let mut body = vec![];
686            c.child_iter()
687                .filter(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM)))
688                .try_for_each(|d| {
689                    body.push(to_transform(d, &attr_sets)?);
690                    Ok::<(), Error>(())
691                })?;
692            newctxt.callable_push(
693                QName::from_local_name(
694                    NcName::try_from(name.to_string().as_str())
695                        .map_err(|_| Error::new(ErrorKind::ParseError, "not a QName"))?,
696                ),
697                Callable::new(
698                    Transform::SequenceItems(body),
699                    FormalParameters::Named(params),
700                ),
701            );
702            Ok(())
703        })?;
704
705    // Add functions
706    stylenode
707        .child_iter()
708        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLFUNCTION))
709        .try_for_each(|c| {
710            let name = c.get_attribute(&ATTRNAME);
711            // Name must have a namespace. See XSLT 10.3.1.
712            let eqname = c.to_qname(name.to_string())?;
713            if eqname.namespace_uri().is_none() {
714                return Err(Error::new_with_code(
715                    ErrorKind::StaticAbsent,
716                    "function name must have a namespace",
717                    Some(QName::from_local_name(
718                        NcName::try_from("XTSE0740").unwrap(),
719                    )),
720                ));
721            }
722            // xsl:param for formal parameters
723            // TODO: validate that xsl:param elements come first in the child list
724            // TODO: validate that xsl:param elements have unique name attributes
725            let mut params: Vec<QName> = Vec::new();
726            c.child_iter()
727                .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM))
728                .try_for_each(|c| {
729                    let p_name = c.get_attribute(&ATTRNAME);
730                    if p_name.to_string().is_empty() {
731                        Err(Error::new(
732                            ErrorKind::StaticAbsent,
733                            "name attribute is missing",
734                        ))
735                    } else {
736                        // TODO: validate that xsl:param elements do not specify a default value. See XSLT 10.3.2.
737                        params.push(QName::from_local_name(
738                            NcName::try_from(p_name.to_string().as_str()).map_err(|_| {
739                                Error::new(ErrorKind::ParseError, "not a valid QName")
740                            })?,
741                        ));
742                        Ok(())
743                    }
744                })?;
745            // Content is the function body
746            let mut body = vec![];
747            c.child_iter()
748                .filter(|c| !(c.is_element() && c.name().is_some_and(|cn| cn == *XSLPARAM)))
749                .try_for_each(|d| {
750                    body.push(to_transform(d, &attr_sets)?);
751                    Ok::<(), Error>(())
752                })?;
753            newctxt.callable_push(
754                eqname,
755                Callable::new(
756                    Transform::SequenceItems(body),
757                    FormalParameters::Positional(params),
758                ),
759            );
760            Ok(())
761        })?;
762    // Add top-level variables
763    // TODO: stylesheet parameters
764    stylenode
765        .child_iter()
766        .filter(|c| c.is_element() && c.name().is_some_and(|cn| cn == *XSLVARIABLE))
767        .try_for_each(|c| {
768            let name = c.get_attribute(&ATTRNAME).to_string();
769            if name.is_empty() {
770                return Err(Error::new(
771                    ErrorKind::StaticAbsent,
772                    "variable must have a name",
773                ));
774            }
775            let sel = c.get_attribute(&ATTRSELECT).to_string();
776            if sel.is_empty() {
777                // Use element content
778                newctxt.pre_var_push(
779                    name,
780                    Transform::SequenceItems(c.child_iter().try_fold(vec![], |mut body, e| {
781                        body.push(to_transform(e, &attr_sets)?);
782                        Ok(body)
783                    })?),
784                );
785                Ok(())
786            } else {
787                // Parse XPath
788                newctxt.pre_var_push(name, parse::<N>(&sel.to_string(), Some(c.clone()), None)?);
789                Ok(())
790            }
791        })?;
792
793    Ok(newctxt)
794}
795
796/// Compile a node in a template to a sequence [Combinator]
797fn to_transform<N: Node>(
798    n: N,
799    attr_sets: &HashMap<QName, Vec<Transform<N>>>,
800) -> Result<Transform<N>, Error> {
801    // Define the in-scope namespaces once so they can be shared
802    //let ns = in_scope_namespaces(Some(n.clone()));
803
804    match n.node_type() {
805        NodeType::Text => Ok(Transform::Literal(Item::Value(Rc::new(Value::from(
806            n.to_string(),
807        ))))),
808        NodeType::Element => {
809            let qn = n.name().unwrap();
810            if qn == *XSLTEXT {
811                let doe = n.get_attribute(&ATTRDOE);
812                if !doe.to_string().is_empty() {
813                    match &doe.to_string()[..] {
814                        "yes" => Ok(Transform::Literal(Item::Value(Rc::new(Value::from(
815                            n.to_string(),
816                        ))))),
817                        "no" => {
818                            let text = n
819                                .to_string()
820                                .replace('&', "&amp;")
821                                .replace('>', "&gt;")
822                                .replace('<', "&lt;")
823                                .replace('\'', "&apos;")
824                                .replace('\"', "&quot;");
825                            Ok(Transform::Literal(Item::Value(Rc::new(Value::from(text)))))
826                        }
827                        _ => Err(Error::new(
828                            ErrorKind::TypeError,
829                            "disable-output-escaping only accepts values yes or no.".to_string(),
830                        )),
831                    }
832                } else {
833                    let text = n
834                        .to_string()
835                        .replace('&', "&amp;")
836                        .replace('>', "&gt;")
837                        .replace('<', "&lt;")
838                        .replace('\'', "&apos;")
839                        .replace('\"', "&quot;");
840                    Ok(Transform::Literal(Item::Value(Rc::new(Value::from(text)))))
841                }
842            } else if qn == *XSLVALUEOF {
843                let sel = n.get_attribute(&ATTRSELECT);
844                let doe = n.get_attribute(&ATTRDOE);
845                if !doe.to_string().is_empty() {
846                    match &doe.to_string()[..] {
847                        "yes" => Ok(Transform::LiteralText(
848                            Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
849                            OutputSpec::NoEscape,
850                        )),
851                        "no" => Ok(Transform::LiteralText(
852                            Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
853                            OutputSpec::Normal,
854                        )),
855                        _ => Err(Error::new(
856                            ErrorKind::TypeError,
857                            "disable-output-escaping only accepts values yes or no.".to_string(),
858                        )),
859                    }
860                } else {
861                    Ok(Transform::LiteralText(
862                        Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
863                        OutputSpec::Normal,
864                    ))
865                }
866            } else if qn == *XSLAPPLYTEMPLATES {
867                let sel = n.get_attribute(&ATTRSELECT);
868                let m = n.get_attribute_node(&ATTRMODE);
869
870                // If a mode is specified then convert it to a QName.
871                // This may fail, so allow for an error result.
872                let mut qm = None;
873                if let Some(s) = m {
874                    qm = Some(n.to_qname(s.value().to_string())?)
875                }
876
877                let sort_keys = get_sort_keys(&n)?;
878                if !sel.to_string().is_empty() {
879                    Ok(Transform::ApplyTemplates(
880                        Box::new(parse::<N>(&sel.to_string(), Some(n.clone()), None)?),
881                        qm,
882                        sort_keys,
883                    )) // TODO: don't panic
884                } else {
885                    // If there is no select attribute, then default is "child::node()"
886                    Ok(Transform::ApplyTemplates(
887                        Box::new(Transform::Step(NodeMatch::new(
888                            Axis::Child,
889                            NodeTest::Kind(KindTest::Any),
890                        ))),
891                        qm,
892                        sort_keys,
893                    )) // TODO: don't panic
894                }
895            } else if qn == *XSLAPPLYIMPORTS {
896                Ok(Transform::ApplyImports)
897            } else if qn == *XSLSEQUENCE {
898                let s = n.get_attribute(&ATTRSELECT);
899                if !s.to_string().is_empty() {
900                    Ok(parse::<N>(&s.to_string(), Some(n.clone()), None)?)
901                } else {
902                    Result::Err(Error::new(
903                        ErrorKind::TypeError,
904                        "missing select attribute".to_string(),
905                    ))
906                }
907            } else if qn == *XSLIF {
908                let t = n.get_attribute(&ATTRTEST);
909                if !t.to_string().is_empty() {
910                    Ok(Transform::Switch(
911                        vec![(
912                            parse::<N>(&t.to_string(), Some(n.clone()), None)?,
913                            Transform::SequenceItems(n.child_iter().try_fold(
914                                vec![],
915                                |mut body, e| {
916                                    body.push(to_transform(e, attr_sets)?);
917                                    Ok(body)
918                                },
919                            )?),
920                        )],
921                        Box::new(Transform::Empty),
922                    ))
923                } else {
924                    Result::Err(Error::new(
925                        ErrorKind::TypeError,
926                        "missing test attribute".to_string(),
927                    ))
928                }
929            } else if qn == *XSLCHOOSE {
930                let mut clauses: Vec<(Transform<N>, Transform<N>)> = Vec::new();
931                let mut otherwise: Option<Transform<N>> = None;
932                let mut status: Option<Error> = None;
933                n.child_iter().try_for_each(|m| {
934                    // look for when elements
935                    // then find an otherwise
936                    // fail on anything else (apart from whitespace, comments, PIs)
937                    match m.node_type() {
938                        NodeType::Element => {
939                            let mn = m.name().unwrap();
940                            if mn == *XSLWHEN {
941                                if otherwise.is_none() {
942                                    let t = m.get_attribute(&ATTRTEST);
943                                    if !t.to_string().is_empty() {
944                                        clauses.push((
945                                            parse::<N>(&t.to_string(), Some(n.clone()), None)?,
946                                            Transform::SequenceItems(m.child_iter().try_fold(
947                                                vec![],
948                                                |mut body, e| {
949                                                    body.push(to_transform(e, attr_sets)?);
950                                                    Ok(body)
951                                                },
952                                            )?),
953                                        ));
954                                    } else {
955                                        status.replace(Error::new(
956                                            ErrorKind::TypeError,
957                                            "missing test attribute".to_string(),
958                                        ));
959                                    }
960                                } else {
961                                    status.replace(Error::new(
962                                        ErrorKind::TypeError,
963                                        "invalid content in choose element: when follows otherwise"
964                                            .to_string(),
965                                    ));
966                                }
967                            } else if mn == *XSLOTHERWISE {
968                                if !clauses.is_empty() {
969                                    otherwise = Some(Transform::SequenceItems(
970                                        m.child_iter().try_fold(vec![], |mut o, e| {
971                                            o.push(to_transform(e, attr_sets)?);
972                                            Ok(o)
973                                        })?,
974                                    ));
975                                } else {
976                                    status.replace(Error::new(
977                                        ErrorKind::TypeError,
978                                        "invalid content in choose element: no when elements"
979                                            .to_string(),
980                                    ));
981                                }
982                            } else {
983                                status.replace(Error::new(
984                                    ErrorKind::TypeError,
985                                    "invalid element content in choose element".to_string(),
986                                ));
987                            }
988                        }
989                        NodeType::Text => {
990                            if !n.to_string().trim().is_empty() {
991                                status.replace(Error::new(
992                                    ErrorKind::TypeError,
993                                    "invalid text content in choose element".to_string(),
994                                ));
995                            }
996                        }
997                        NodeType::Comment | NodeType::ProcessingInstruction => {}
998                        _ => {
999                            status.replace(Error::new(
1000                                ErrorKind::TypeError,
1001                                "invalid content in choose element".to_string(),
1002                            ));
1003                        }
1004                    }
1005                    Ok::<(), Error>(())
1006                })?;
1007                match status {
1008                    Some(e) => Result::Err(e),
1009                    None => Ok(Transform::Switch(
1010                        clauses,
1011                        otherwise.map_or(Box::new(Transform::Empty), Box::new),
1012                    )),
1013                }
1014            } else if qn == *XSLFOREACH {
1015                let s = n.get_attribute(&ATTRSELECT);
1016                if !s.to_string().is_empty() {
1017                    Ok(Transform::ForEach(
1018                        None,
1019                        Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
1020                        Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1021                            vec![],
1022                            |mut body, e| {
1023                                body.push(to_transform(e, attr_sets)?);
1024                                Ok(body)
1025                            },
1026                        )?)),
1027                        get_sort_keys(&n)?,
1028                    ))
1029                } else {
1030                    Result::Err(Error::new(
1031                        ErrorKind::TypeError,
1032                        "missing select attribute".to_string(),
1033                    ))
1034                }
1035            } else if qn == *XSLFOREACHGROUP {
1036                let ord = get_sort_keys(&n)?;
1037                let s = n.get_attribute(&ATTRSELECT);
1038                if !s.to_string().is_empty() {
1039                    match (
1040                        n.get_attribute(&ATTRGROUPBY).to_string().as_str(),
1041                        n.get_attribute(&ATTRGROUPADJACENT).to_string().as_str(),
1042                        n.get_attribute(&ATTRGROUPSTARTINGWITH).to_string().as_str(),
1043                        n.get_attribute(&ATTRGROUPENDINGWITH).to_string().as_str(),
1044                    ) {
1045                        (by, "", "", "") => Ok(Transform::ForEach(
1046                            Some(Grouping::By(vec![parse::<N>(by, Some(n.clone()), None)?])),
1047                            Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
1048                            Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1049                                vec![],
1050                                |mut body, e| {
1051                                    body.push(to_transform(e, attr_sets)?);
1052                                    Ok(body)
1053                                },
1054                            )?)),
1055                            ord,
1056                        )),
1057                        ("", adj, "", "") => Ok(Transform::ForEach(
1058                            Some(Grouping::Adjacent(vec![parse::<N>(
1059                                adj,
1060                                Some(n.clone()),
1061                                None,
1062                            )?])),
1063                            Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
1064                            Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1065                                vec![],
1066                                |mut body, e| {
1067                                    body.push(to_transform(e, attr_sets)?);
1068                                    Ok(body)
1069                                },
1070                            )?)),
1071                            ord,
1072                        )),
1073                        ("", "", start, "") => Ok(Transform::ForEach(
1074                            Some(Grouping::StartingWith(Box::new(Pattern::try_from(start)?))),
1075                            Box::new(parse::<N>(&s.to_string(), Some(n.clone()), None)?),
1076                            Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1077                                vec![],
1078                                |mut body, e| {
1079                                    body.push(to_transform(e, attr_sets)?);
1080                                    Ok(body)
1081                                },
1082                            )?)),
1083                            ord,
1084                        )),
1085                        // TODO: group-ending-with
1086                        _ => Result::Err(Error::new(
1087                            ErrorKind::NotImplemented,
1088                            "invalid grouping attribute(s) specified".to_string(),
1089                        )),
1090                    }
1091                } else {
1092                    Result::Err(Error::new(
1093                        ErrorKind::TypeError,
1094                        "missing select attribute".to_string(),
1095                    ))
1096                }
1097            } else if qn == *XSLCOPY {
1098                // TODO: handle select attribute
1099                let mut content: Vec<Transform<N>> =
1100                    n.child_iter().try_fold(vec![], |mut body, e| {
1101                        body.push(to_transform(e, attr_sets)?);
1102                        Ok(body)
1103                    })?;
1104                // Process @xsl:use-attribute-sets
1105                let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
1106                let mut attrs = vec![];
1107                use_atts.to_string().split_whitespace().try_for_each(|a| {
1108                    let eqa = n.to_qname(a)?;
1109                    attr_sets
1110                        .get(&eqa)
1111                        .iter()
1112                        .cloned()
1113                        .for_each(|a| attrs.append(&mut a.clone()));
1114                    Ok(())
1115                })?;
1116                Ok(Transform::Copy(
1117                    Box::new(Transform::ContextItem), // TODO: this is where the select attribute would go
1118                    // The content of this element is a template for the content of the new item
1119                    Box::new(if content.is_empty() && attrs.is_empty() {
1120                        Transform::Empty
1121                    } else {
1122                        // Attributes always come first
1123                        attrs.append(&mut content);
1124                        Transform::SequenceItems(attrs)
1125                    }),
1126                ))
1127            } else if qn == *XSLCOPYOF {
1128                let s = n.get_attribute(&ATTRSELECT);
1129                if !s.to_string().is_empty() {
1130                    Ok(Transform::DeepCopy(Box::new(parse::<N>(
1131                        &s.to_string(),
1132                        Some(n.clone()),
1133                        None,
1134                    )?)))
1135                } else {
1136                    Ok(Transform::DeepCopy(Box::new(Transform::ContextItem)))
1137                }
1138            } else if qn == *XSLCALLTEMPLATE {
1139                let name = n.get_attribute(&ATTRNAME);
1140                if !name.to_string().is_empty() {
1141                    // Iterate over the xsl:with-param elements to get the actual parameters
1142                    // TODO: validate that the children are only xsl:with-param elements
1143                    let mut ap = vec![];
1144                    n.child_iter()
1145                        .filter(|c| c.is_element() && c.name().unwrap() == *XSLWITHPARAM)
1146                        .try_for_each(|c| {
1147                            let wp_name = c.get_attribute(&ATTRNAME);
1148                            if !wp_name.to_string().is_empty() {
1149                                let sel = c.get_attribute(&ATTRSELECT);
1150                                if sel.to_string().is_empty() {
1151                                    // xsl:with-param content is the sequence constructor
1152                                    let mut body = vec![];
1153                                    c.child_iter().try_for_each(|d| {
1154                                        body.push(to_transform(d, attr_sets)?);
1155                                        Ok(())
1156                                    })?;
1157                                    ap.push((
1158                                        QName::from_local_name(
1159                                            NcName::try_from(wp_name.to_string().as_str())
1160                                                .map_err(|_| {
1161                                                    Error::new(ErrorKind::ParseError, "not a QName")
1162                                                })?,
1163                                        ),
1164                                        Transform::SequenceItems(body),
1165                                    ));
1166                                    Ok(())
1167                                } else {
1168                                    // select attribute value is an expression
1169                                    ap.push((
1170                                        QName::from_local_name(
1171                                            NcName::try_from(wp_name.to_string().as_str())
1172                                                .map_err(|_| {
1173                                                    Error::new(ErrorKind::ParseError, "not a QName")
1174                                                })?,
1175                                        ),
1176                                        parse::<N>(&sel.to_string(), Some(n.clone()), None)?,
1177                                    ));
1178                                    Ok(())
1179                                }
1180                            } else {
1181                                Err(Error::new(
1182                                    ErrorKind::StaticAbsent,
1183                                    "missing name attribute",
1184                                ))
1185                            }
1186                        })?;
1187                    Ok(Transform::Invoke(
1188                        QName::from_local_name(
1189                            NcName::try_from(name.to_string().as_str())
1190                                .map_err(|_| Error::new(ErrorKind::ParseError, "not a NcName"))?,
1191                        ),
1192                        ActualParameters::Named(ap),
1193                        in_scope_namespaces(Some(n)),
1194                    ))
1195                } else {
1196                    Err(Error::new(
1197                        ErrorKind::StaticAbsent,
1198                        "name attribute missing",
1199                    ))
1200                }
1201            } else if qn == *XSLELEMENT {
1202                // TODO: insert namespace declaration if element's name is prefixed
1203                let m = n.get_attribute(&ATTRNAME);
1204                if m.to_string().is_empty() {
1205                    return Err(Error::new(ErrorKind::TypeError, "missing name attribute"));
1206                }
1207                let mut content = n.child_iter().try_fold(vec![], |mut body, e| {
1208                    body.push(to_transform(e, attr_sets)?);
1209                    Ok(body)
1210                })?;
1211                // Process @xsl:use-attribute-sets
1212                let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
1213                let mut attrs = vec![];
1214                use_atts.to_string().split_whitespace().try_for_each(|a| {
1215                    let eqa = n.to_qname(a)?;
1216                    attr_sets
1217                        .get(&eqa)
1218                        .iter()
1219                        .cloned()
1220                        .for_each(|a| attrs.append(&mut a.clone()));
1221                    Ok(())
1222                })?;
1223
1224                Ok(Transform::Element(
1225                    Box::new(parse_avt(m.to_string().as_str(), Some(n.clone()))?),
1226                    Box::new(if content.is_empty() && attrs.is_empty() {
1227                        Transform::Empty
1228                    } else {
1229                        // Attributes always come first
1230                        attrs.append(&mut content);
1231                        Transform::SequenceItems(attrs)
1232                    }),
1233                ))
1234            } else if qn == *XSLATTRIBUTE {
1235                let m = n.get_attribute(&ATTRNAME);
1236                if !m.to_string().is_empty() {
1237                    Ok(Transform::LiteralAttribute(
1238                        QName::from_local_name(
1239                            NcName::try_from(m.to_string().as_str())
1240                                .map_err(|_| Error::new(ErrorKind::ParseError, "not a NcName"))?,
1241                        ),
1242                        Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1243                            vec![],
1244                            |mut body, e| {
1245                                body.push(to_transform(e, attr_sets)?);
1246                                Ok(body)
1247                            },
1248                        )?)),
1249                    ))
1250                } else {
1251                    Err(Error::new(ErrorKind::TypeError, "missing name attribute"))
1252                }
1253            } else if qn == *XSLCOMMENT {
1254                Ok(Transform::LiteralComment(Box::new(
1255                    Transform::SequenceItems(n.child_iter().try_fold(vec![], |mut body, e| {
1256                        body.push(to_transform(e, attr_sets)?);
1257                        Ok(body)
1258                    })?),
1259                )))
1260            } else if qn == *XSLPROCESSINGINSTRUCTION {
1261                let m = n.get_attribute(&ATTRNAME);
1262                if m.to_string().is_empty() {
1263                    return Result::Err(Error::new(ErrorKind::TypeError, "missing name attribute"));
1264                }
1265                Ok(Transform::LiteralProcessingInstruction(
1266                    Box::new(parse_avt(m.to_string().as_str(), Some(n.clone()))?),
1267                    Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1268                        vec![],
1269                        |mut body, e| {
1270                            body.push(to_transform(e, attr_sets)?);
1271                            Ok(body)
1272                        },
1273                    )?)),
1274                ))
1275            } else if qn == *XSLMESSAGE {
1276                let t = n.get_attribute(&ATTRTERMINATE);
1277                Ok(Transform::Message(
1278                    Box::new(Transform::SequenceItems(n.child_iter().try_fold(
1279                        vec![],
1280                        |mut body, e| {
1281                            body.push(to_transform(e, attr_sets)?);
1282                            Ok(body)
1283                        },
1284                    )?)),
1285                    None,
1286                    Box::new(Transform::Empty),
1287                    Box::new(if t.to_string().is_empty() {
1288                        Transform::False
1289                    } else {
1290                        Transform::Literal(Item::Value(Rc::new(Value::from(t.to_string()))))
1291                    }),
1292                ))
1293            } else if qn == *XSLNUMBER {
1294                let value = n.get_attribute(&ATTRVALUE);
1295                let sel = n.get_attribute(&ATTRSELECT);
1296                let level = n.get_attribute(&ATTRLEVEL);
1297                if level.to_string() != "" && level.to_string() != "single" {
1298                    return Err(Error::new(
1299                        ErrorKind::NotImplemented,
1300                        "only single level numbering is supported",
1301                    ));
1302                }
1303                let count = n.get_attribute(&ATTRCOUNT);
1304                let from = n.get_attribute(&ATTRFROM);
1305                let format = n.get_attribute(&ATTRFORMAT);
1306                // TODO: lang, letter-value, ordinal, start-at, grouping-separator, grouping-size
1307                if value.to_string().is_empty() {
1308                    // Compute place marker
1309                    Ok(Transform::FormatInteger(
1310                        Box::new(Transform::GenerateIntegers(
1311                            Box::new(Transform::Empty), // start-at (TODO)
1312                            Box::new(if sel.to_string().is_empty() {
1313                                Transform::ContextItem
1314                            } else {
1315                                parse::<N>(&sel.to_string(), Some(n.clone()), None)?
1316                            }), // select
1317                            Box::new(Numbering::new(
1318                                Level::Single, // TODO: parse level attribute value
1319                                if count.to_string().is_empty() {
1320                                    None
1321                                } else {
1322                                    Some(Pattern::try_from(count.to_string())?)
1323                                },
1324                                if from.to_string().is_empty() {
1325                                    None
1326                                } else {
1327                                    Some(Pattern::try_from(from.to_string())?)
1328                                },
1329                            )),
1330                        )),
1331                        Box::new(Transform::Literal(Item::Value(
1332                            if format.to_string().is_empty() {
1333                                Rc::new(Value::from("1"))
1334                            } else {
1335                                format
1336                            },
1337                        ))),
1338                    ))
1339                } else {
1340                    // Place marker is supplied
1341                    Ok(Transform::FormatInteger(
1342                        Box::new(parse::<N>(&value.to_string(), Some(n.clone()), None)?),
1343                        Box::new(Transform::Literal(Item::Value(
1344                            if format.to_string().is_empty() {
1345                                Rc::new(Value::from("1"))
1346                            } else {
1347                                format
1348                            },
1349                        ))),
1350                    ))
1351                }
1352            } else if qn == *XSLDECIMALFORMAT {
1353                Ok(Transform::NotImplemented(String::from(
1354                    "unsupported XSL element \"decimal-format\"",
1355                )))
1356            } else if qn.namespace_uri() == *XSLTNS {
1357                Ok(Transform::NotImplemented(format!(
1358                    "unsupported XSL element \"{}\"",
1359                    qn.local_name().to_string()
1360                )))
1361            } else {
1362                let u = qn.namespace_uri();
1363                let a = qn.local_name();
1364
1365                // Uh-oh! Parsing the stylesheet has thrown away all qualified name prefixes
1366                // But there will be a namespace declaration, so recover it from there
1367                let mut prefix = None;
1368                if let Some(nsuri) = u.as_ref() {
1369                    if let Some(p) = n
1370                        .namespace_iter()
1371                        .find(|nsd| nsd.as_namespace_uri().unwrap() == nsuri)
1372                    {
1373                        if let Some(pp) = p.as_namespace_prefix()? {
1374                            prefix = Some(Box::new(Transform::Literal(Item::Value(Rc::new(
1375                                Value::from(pp.to_string()),
1376                            )))));
1377                        }
1378                    }
1379                }
1380
1381                // Process @xsl:use-attribute-sets
1382                let use_atts = n.get_attribute(&XSLATTRUSEATTRIBUTESETS);
1383                let mut attrs = vec![];
1384                use_atts.to_string().split_whitespace().try_for_each(|a| {
1385                    let eqa = n.to_qname(a)?; //QName::try_from((a, ns.clone()))?;
1386                    attr_sets
1387                        .get(&eqa)
1388                        .iter()
1389                        .cloned()
1390                        .for_each(|a| attrs.append(&mut a.clone()));
1391                    Ok(())
1392                })?;
1393                let mut content = vec![];
1394
1395                // Setup a namespace declaration if required for the element name
1396                if u.is_some() {
1397                    content.push(Transform::NamespaceDeclaration(
1398                        prefix,
1399                        Box::new(Transform::Literal(Item::Value(Rc::new(Value::from(
1400                            u.clone().unwrap(),
1401                        ))))),
1402                        Box::new(Transform::Literal(Item::Value(Rc::new(Value::from(true))))),
1403                    ));
1404                }
1405
1406                // Copy attributes to the result, except for XSLT directives
1407                n.attribute_iter()
1408                    .filter(|e| e.name().unwrap().namespace_uri() != *XSLTNS)
1409                    .try_for_each(|e| {
1410                        content.push(to_transform(e, attr_sets)?);
1411                        Ok::<(), Error>(())
1412                    })?;
1413                n.child_iter().try_for_each(|e| {
1414                    content.push(to_transform(e, attr_sets)?);
1415                    Ok::<(), Error>(())
1416                })?;
1417                Ok(Transform::LiteralElement(
1418                    QName::new_from_parts(a, u),
1419                    Box::new(if content.is_empty() && attrs.is_empty() {
1420                        Transform::Empty
1421                    } else {
1422                        // Attributes always come first
1423                        attrs.append(&mut content);
1424                        Transform::SequenceItems(attrs)
1425                    }),
1426                ))
1427            }
1428        }
1429        NodeType::Attribute => {
1430            let x = parse_avt(n.to_string().as_str(), Some(n.clone()))?;
1431            // Get value as a Value
1432            Ok(Transform::LiteralAttribute(
1433                n.name().unwrap(),
1434                Box::new(x),
1435                //Box::new(Transform::Literal(Item::Value(Rc::new(Value::String(
1436                //n.to_string(),
1437                //))))),
1438            ))
1439        }
1440        _ => {
1441            // TODO: literal elements, etc, pretty much everything in the XSLT spec
1442            Ok(Transform::NotImplemented(
1443                "other template content".to_string(),
1444            ))
1445        }
1446    }
1447}
1448
1449fn get_sort_keys<N: Node>(n: &N) -> Result<Vec<(Order, Transform<N>)>, Error> {
1450    let mut result = vec![];
1451    let mut nit = n.child_iter();
1452    loop {
1453        match nit.next() {
1454            None => break,
1455            Some(c) => match c.node_type() {
1456                NodeType::Element => {
1457                    if c.name().is_some_and(|d| d == *XSLSORT) {
1458                        let ordval = c.get_attribute(&ATTRORDER);
1459                        let ord = match ordval.to_string().as_str() {
1460                            "descending" => Order::Descending,
1461                            _ => Order::Ascending,
1462                        };
1463                        let sortsel = c.get_attribute(&ATTRSELECT);
1464                        result.push((
1465                            ord,
1466                            parse::<N>(&sortsel.to_string(), Some(n.clone()), None)?,
1467                        ));
1468                    } else {
1469                        break;
1470                    }
1471                }
1472                NodeType::Text => {
1473                    if c.value()
1474                        .to_string()
1475                        .as_str()
1476                        .find(|d: char| !d.is_whitespace())
1477                        .is_some()
1478                    {
1479                        break;
1480                    }
1481                }
1482                NodeType::Comment | NodeType::ProcessingInstruction => {}
1483                _ => break,
1484            },
1485        }
1486    }
1487    // Check that there are no more sort elements
1488    if nit.any(|c| c.node_type() == NodeType::Element && c.name().is_some_and(|d| d == *XSLSORT)) {
1489        Err(Error::new(ErrorKind::TypeError, "sort elements in body"))
1490    } else {
1491        Ok(result)
1492    }
1493}
1494
1495/// Strip whitespace nodes from a XDM tree.
1496/// See [XSLT 4.3](https://www.w3.org/TR/2017/REC-xslt-30-20170608/#stylesheet-stripping).
1497/// The [Node] argument must be the document node of the tree.
1498pub fn strip_whitespace<N: Node>(
1499    t: N,
1500    cpi: bool, // strip comments and PIs?
1501    strip: &Vec<NodeTest>,
1502    preserve: &Vec<NodeTest>,
1503) -> Result<(), Error> {
1504    t.child_iter().try_for_each(|n| {
1505        strip_whitespace_node(n, cpi, strip, preserve, true)?;
1506        Ok(())
1507    })?;
1508    Ok(())
1509}
1510
1511/// Strip whitespace nodes from a XDM tree.
1512/// This function operates under the direction of the xsl:strip-space and xsl:preserve-space directives in a XSLT stylesheet.
1513pub fn strip_source_document<N: Node>(src: N, style: N) -> Result<(), Error> {
1514    // Find strip-space element, if any, and use it to construct a vector of NodeTests.
1515    // Ditto for preserve-space.
1516    let mut ss: Vec<NodeTest> = vec![];
1517    let mut ps: Vec<NodeTest> = vec![];
1518    style.child_iter().try_for_each(|n| {
1519        // n should be the xsl:stylesheet element
1520        n.child_iter().try_for_each(|m| {
1521            let nm = m.name();
1522            if nm.as_ref().is_some_and(|nms| *nms == *XSLSTRIPSPACE) {
1523                let v = m.get_attribute(&ATTRELEMENTS);
1524                if !v.to_string().is_empty() {
1525                    v.to_string().split_whitespace().try_for_each(|t| {
1526                        ss.push(NodeTest::try_from(t)?);
1527                        Ok::<(), Error>(())
1528                    })?
1529                } else {
1530                    return Result::Err(Error::new(
1531                        ErrorKind::Unknown,
1532                        String::from("missing elements attribute"),
1533                    ));
1534                }
1535            } else if nm.as_ref().is_some_and(|nms| *nms == *XSLPRESERVESPACE) {
1536                let v = m.get_attribute(&ATTRELEMENTS);
1537                if !v.to_string().is_empty() {
1538                    v.to_string().split_whitespace().try_for_each(|t| {
1539                        ps.push(NodeTest::try_from(t)?);
1540                        Ok::<(), Error>(())
1541                    })?
1542                } else {
1543                    return Result::Err(Error::new(
1544                        ErrorKind::Unknown,
1545                        String::from("missing elements attribute"),
1546                    ));
1547                }
1548            }
1549            Ok::<(), Error>(())
1550        })?;
1551        Ok::<(), Error>(())
1552    })?;
1553
1554    strip_whitespace(src, false, &ss, &ps)
1555}
1556
1557// TODO: the rules for stripping/preserving are a lot more complex
1558fn strip_whitespace_node<N: Node>(
1559    mut n: N,
1560    cpi: bool, // strip comments and PIs?
1561    strip: &Vec<NodeTest>,
1562    preserve: &Vec<NodeTest>,
1563    keep: bool,
1564) -> Result<(), Error> {
1565    match n.node_type() {
1566        NodeType::Comment | NodeType::ProcessingInstruction => {
1567            if cpi {
1568                n.pop()?;
1569                // TODO: Merge text nodes that are now adjacent
1570            }
1571        }
1572        NodeType::Element => {
1573            // Determine if this element toggles the strip/preserve setting
1574            // Match a strip NodeTest or a preserve NodeTest
1575            // The 'strength' of the match determines which setting wins
1576            let mut ss = -1.0;
1577            let mut ps = -1.0;
1578            strip.iter().for_each(|t| match t {
1579                NodeTest::Kind(KindTest::Any) | NodeTest::Kind(KindTest::Element) => ss = -0.5,
1580                NodeTest::Name(nt) => match nt {
1581                    NameTest::Wildcard(
1582                        WildcardOrNamespaceUri::Wildcard,
1583                        WildcardOrName::Name(_),
1584                    ) => ss = -0.25,
1585                    NameTest::Wildcard(
1586                        WildcardOrNamespaceUri::NamespaceUri(_),
1587                        WildcardOrName::Wildcard,
1588                    ) => ss = -0.25,
1589                    NameTest::Wildcard(_, _) => ss = -0.5,
1590                    NameTest::Name(qn) => {
1591                        if *qn == n.name().unwrap() {
1592                            ss = 0.5
1593                        }
1594                    }
1595                },
1596                _ => {}
1597            });
1598            preserve.iter().for_each(|t| match t {
1599                NodeTest::Kind(KindTest::Any) | NodeTest::Kind(KindTest::Element) => ps = -0.5,
1600                NodeTest::Name(nt) => match nt {
1601                    NameTest::Wildcard(
1602                        WildcardOrNamespaceUri::Wildcard,
1603                        WildcardOrName::Name(_),
1604                    ) => ss = -0.25,
1605                    NameTest::Wildcard(
1606                        WildcardOrNamespaceUri::NamespaceUri(_),
1607                        WildcardOrName::Wildcard,
1608                    ) => ss = -0.25,
1609                    NameTest::Wildcard(_, _) => ss = -0.5,
1610                    NameTest::Name(qn) => {
1611                        if *qn == n.name().unwrap() {
1612                            ss = 0.5
1613                        }
1614                    }
1615                },
1616                _ => {}
1617            });
1618            n.child_iter().try_for_each(|m| {
1619                strip_whitespace_node(
1620                    m,
1621                    cpi,
1622                    strip,
1623                    preserve,
1624                    if ss > -1.0 {
1625                        ps >= ss
1626                    } else if ps > -1.0 {
1627                        true
1628                    } else {
1629                        keep
1630                    },
1631                )
1632            })?
1633        }
1634        NodeType::Text => {
1635            if n.to_string().trim().is_empty() && !keep {
1636                n.pop()?;
1637            }
1638        }
1639        _ => {}
1640    }
1641    Ok(())
1642}