Skip to main content

chadpath/transform/
template.rs

1//! # Templates
2
3use std::cmp::Ordering;
4use std::fmt::{Debug, Formatter};
5use std::rc::Rc;
6use url::Url;
7
8use crate::transform::context::{Context, ContextBuilder, StaticContext};
9use crate::transform::{Order, Transform, do_sort};
10use crate::xdmerror::Error;
11use crate::{Node, Pattern, Sequence};
12use crate::names::QName;
13
14#[derive(Clone)]
15pub struct Template<N: Node> {
16    pub(crate) pattern: Pattern<N>,
17    pub(crate) body: Transform<N>,
18    pub(crate) priority: Option<f64>,
19    pub(crate) import: Vec<usize>,
20    pub(crate) document_order: Option<usize>,
21    pub(crate) mode: Option<QName>,
22    pub(crate) mtch: String, // used for debugging
23}
24
25impl<N: Node> Template<N> {
26    pub fn new(
27        pattern: Pattern<N>,
28        body: Transform<N>,
29        priority: Option<f64>,
30        import: Vec<usize>,
31        document_order: Option<usize>,
32        mode: Option<QName>,
33        mtch: String,
34    ) -> Self {
35        Template {
36            pattern,
37            body,
38            priority,
39            import,
40            document_order,
41            mode,
42            mtch,
43        }
44    }
45}
46
47/// Two templates are equal if they have the same priority, import precedence, and mode.
48impl<N: Node> PartialEq for Template<N> {
49    fn eq(&self, other: &Self) -> bool {
50        self.priority == other.priority && self.import == other.import && self.mode == other.mode
51    }
52}
53impl<N: Node> Eq for Template<N> {}
54
55impl<N: Node> PartialOrd for Template<N> {
56    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
57        Some(self.cmp(other))
58    }
59}
60impl<N: Node> Ord for Template<N> {
61    fn cmp(&self, other: &Self) -> Ordering {
62        self.priority.map_or_else(
63            || {
64                other
65                    .priority
66                    .map_or_else(|| Ordering::Equal, |_| Ordering::Greater)
67            },
68            |s| {
69                other.priority.map_or_else(
70                    || Ordering::Less,
71                    |t| {
72                        if s < t {
73                            Ordering::Greater
74                        } else {
75                            Ordering::Less
76                        }
77                    },
78                )
79            },
80        )
81    }
82}
83
84impl<N: Node> Debug for Template<N> {
85    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
86        write!(
87            f,
88            "template match \"{:?}\" ({:?}) priority {:?} mode {:?}",
89            self.mtch, self.pattern, self.priority, self.mode
90        )
91    }
92}
93
94/// Apply templates to the select expression.
95pub(crate) fn apply_templates<
96    N: Node,
97    F: FnMut(&str) -> Result<(), Error>,
98    G: FnMut(&str) -> Result<N, Error>,
99    H: FnMut(&Url) -> Result<String, Error>,
100>(
101    ctxt: &Context<N>,
102    stctxt: &mut StaticContext<N, F, G, H>,
103    s: &Transform<N>,
104    m: &Option<QName>,
105    o: &Vec<(Order, Transform<N>)>, // sort keys
106) -> Result<Sequence<N>, Error> {
107    // s is the select expression. Evaluate it, and then iterate over its items.
108    // Each iteration becomes an item in the result sequence.
109    let mut seq = ctxt.dispatch(stctxt, s)?;
110    do_sort(&mut seq, o, ctxt, stctxt)?;
111    seq.iter().try_fold(vec![], |mut result, i| {
112        let templates = ctxt.find_templates(stctxt, i, m)?;
113        // If there are two or more templates with the same priority and import level, then take the one that has the higher document order
114        let matching = if templates.len() > 1 {
115            if templates[0].priority == templates[1].priority
116                && templates[0].import.len() == templates[1].import.len()
117            {
118                let mut candidates: Vec<Rc<Template<N>>> = templates
119                    .iter()
120                    .take_while(|t| {
121                        t.priority == templates[0].priority
122                            && t.import.len() == templates[0].import.len()
123                    })
124                    .cloned()
125                    .collect();
126                candidates.sort_unstable_by(|a, b| {
127                    a.document_order.map_or(Ordering::Greater, |v| {
128                        b.document_order.map_or(Ordering::Less, |u| v.cmp(&u))
129                    })
130                });
131                candidates.last().unwrap().clone()
132            } else {
133                templates[0].clone()
134            }
135        } else {
136            templates[0].clone()
137        };
138
139        // Check that the maximum depth limit will not be exceeded,
140        // if there is one set
141
142        if let Some(md) = ctxt.max_depth {
143            if md == ctxt.depth {
144                return Err(Error::new(
145                    crate::ErrorKind::LimitExceeded,
146                    format!("exceeded evaluation depth ({})", ctxt.depth),
147                ));
148            }
149        }
150        // Create a new context using the current templates, then evaluate the highest priority and highest import precedence
151        let mut u = ContextBuilder::from(ctxt)
152            .context(vec![i.clone()])
153            .context_item(Some(i.clone()))
154            .current_templates(templates)
155            .depth(ctxt.depth + 1)
156            .build()
157            .dispatch(stctxt, &matching.body)?;
158        result.append(&mut u);
159        Ok(result)
160    })
161}
162
163/// Apply template with a higher import precedence.
164pub(crate) fn apply_imports<
165    N: Node,
166    F: FnMut(&str) -> Result<(), Error>,
167    G: FnMut(&str) -> Result<N, Error>,
168    H: FnMut(&Url) -> Result<String, Error>,
169>(
170    ctxt: &Context<N>,
171    stctxt: &mut StaticContext<N, F, G, H>,
172) -> Result<Sequence<N>, Error> {
173    // Find the template with the next highest level within the same import tree
174    // current_templates[0] is the currently matching template
175    if ctxt.current_templates.is_empty() {
176        // TODO: select built-in templates instead
177        return Ok(vec![]);
178    }
179    let cur = &(ctxt.current_templates[0]);
180    let next: Vec<Rc<Template<N>>> = ctxt
181        .current_templates
182        .iter()
183        .skip(1)
184        .skip_while(|t| t.import.len() == cur.import.len()) // import level is the same (iow, different priority templates in the same import level)
185        .cloned()
186        .collect();
187
188    if !next.is_empty() {
189        ContextBuilder::from(ctxt)
190            .current_templates(next.clone())
191            .build()
192            .dispatch(stctxt, &next[0].body)
193    } else {
194        Ok(vec![])
195    }
196}
197
198/// Apply the next template that matches.
199pub(crate) fn next_match<
200    N: Node,
201    F: FnMut(&str) -> Result<(), Error>,
202    G: FnMut(&str) -> Result<N, Error>,
203    H: FnMut(&Url) -> Result<String, Error>,
204>(
205    ctxt: &Context<N>,
206    stctxt: &mut StaticContext<N, F, G, H>,
207) -> Result<Sequence<N>, Error> {
208    if ctxt.current_templates.len() > 2 {
209        ContextBuilder::from(ctxt)
210            .current_templates(ctxt.current_templates.iter().skip(1).cloned().collect())
211            .build()
212            .dispatch(stctxt, &ctxt.current_templates[1].body)
213    } else {
214        Ok(vec![])
215    }
216}