Skip to main content

xrust/transform/
numbers.rs

1//! These functions are for features defined in XPath Functions 1.0 and 2.0.
2
3use qualname::{NcName, QName};
4use std::cmp::Ordering;
5use std::rc::Rc;
6use url::Url;
7
8use english_numbers::{Formatting, convert};
9use formato::Formato;
10use italian_numbers::roman_converter;
11
12use crate::item::{Item, Node, NodeType, Sequence, SequenceTrait};
13use crate::pattern::{Branch, Pattern, Step};
14use crate::transform::context::{Context, StaticContext};
15use crate::transform::{
16    ArithmeticOperand, ArithmeticOperator, Axis, KindTest, NameTest, NodeTest, Transform,
17};
18use crate::value::Value;
19use crate::xdmerror::{Error, ErrorKind};
20
21/// Level value for xsl:number. See XSLT 12.3.
22#[derive(Copy, Clone, Debug, Default, PartialEq)]
23pub enum Level {
24    #[default]
25    Single,
26    Multiple,
27    Any,
28}
29
30/// Specification for generating numbers. This is avoid recursive types in [Transform] and [Pattern].
31#[derive(Clone, Debug)]
32pub struct Numbering<N: Node> {
33    level: Level,
34    count: Option<Pattern<N>>,
35    from: Option<Pattern<N>>,
36}
37impl<N: Node> Numbering<N> {
38    pub fn new(level: Level, count: Option<Pattern<N>>, from: Option<Pattern<N>>) -> Self {
39        Numbering { level, count, from }
40    }
41}
42
43/// Generate a sequence of integers
44pub fn generate_integers<
45    N: Node,
46    F: FnMut(&str) -> Result<(), Error>,
47    G: FnMut(&str) -> Result<N, Error>,
48    H: FnMut(&Url) -> Result<String, Error>,
49>(
50    ctxt: &Context<N>,
51    stctxt: &mut StaticContext<N, F, G, H>,
52    _start_at: &Transform<N>,
53    select: &Transform<N>,
54    num: &Numbering<N>,
55) -> Result<Sequence<N>, Error> {
56    // This implements "single" level. "multiple" and "any" are TODO
57    if num.level != Level::Single {
58        return Err(Error::new(
59            ErrorKind::NotImplemented,
60            "only single level is implemented",
61        ));
62    }
63
64    // The select expression must evaluate to a single node item (XSLT error XTTE1000)
65    let n = ctxt.dispatch(stctxt, select)?;
66    if n.len() == 1 {
67        if let Item::Node(m) = &n[0] {
68            // Determine the count pattern
69            let count_pat = (num.count)
70                .clone()
71                .unwrap_or(Pattern::Selection(Branch::SingleStep(
72                    match m.node_type() {
73                        NodeType::Element => Step::new(
74                            Axis::SelfAxis,
75                            Axis::SelfAxis,
76                            NodeTest::Name(NameTest::Name(m.name().unwrap())),
77                        ),
78                        NodeType::Text => Step::new(
79                            Axis::SelfAxis,
80                            Axis::SelfAxis,
81                            NodeTest::Kind(KindTest::Text),
82                        ),
83                        _ => {
84                            return Err(Error::new(
85                                ErrorKind::TypeError,
86                                "cannot match this type of node",
87                            ));
88                        }
89                    },
90                )));
91
92            // let a = $S/ancestor-or-self::node()[matches-count(.)][1]
93            // TODO: Don't Panic
94            let a = if count_pat.matches(ctxt, stctxt, &Item::Node(m.clone())) {
95                vec![m.clone()]
96            } else {
97                m.ancestor_iter()
98                    .filter(|i| count_pat.matches(ctxt, stctxt, &Item::Node(i.clone())))
99                    .take(1)
100                    .collect()
101            };
102            if a.is_empty() {
103                return Ok(vec![]);
104            }
105            // let f = $S/ancestor-or-self::node()[matches-from(.)][1]
106            // TODO: Don't Panic
107            let f: Vec<N> = if let Some(fr) = &num.from.clone() {
108                m.ancestor_iter()
109                    .filter(|i| {
110                        if i.node_type() == NodeType::Document {
111                            true
112                        } else {
113                            fr.matches(ctxt, stctxt, &Item::Node(i.clone()))
114                        }
115                    })
116                    .take(1)
117                    .collect()
118            } else {
119                // When there is no from pattern specified then use the root node
120                vec![m.owner_document().clone()]
121            };
122            if f.is_empty() {
123                return Ok(vec![]);
124            }
125            // let af = $a[ancestor-or-self::node()[. is $f]]
126            let af_test: Vec<N> = if a[0].is_same(&f[0]) {
127                vec![a[0].clone()]
128            } else {
129                a[0].ancestor_iter().filter(|i| i.is_same(&f[0])).collect()
130            };
131            let af = if af_test.is_empty() { vec![] } else { a };
132            if af.is_empty() {
133                return Ok(vec![]);
134            }
135            // 1 + count($af/preceding-sibling::node()[matches-count(.)])
136            let result: Vec<N> = af[0]
137                .prev_iter()
138                .filter(|i| count_pat.matches(ctxt, stctxt, &Item::Node(i.clone())))
139                .collect();
140            Ok(vec![Item::Value(Rc::new(Value::from(1 + result.len())))])
141        } else {
142            Err(Error::new_with_code(
143                ErrorKind::TypeError,
144                "not a singleton node",
145                Some(QName::from_local_name(
146                    NcName::try_from("XTTE1000").unwrap(),
147                )),
148            ))
149        }
150    } else {
151        Err(Error::new_with_code(
152            ErrorKind::TypeError,
153            "not a singleton node",
154            Some(QName::from_local_name(
155                NcName::try_from("XTTE1000").unwrap(),
156            )),
157        ))
158    }
159}
160
161/// XPath number function.
162pub fn number<
163    N: Node,
164    F: FnMut(&str) -> Result<(), Error>,
165    G: FnMut(&str) -> Result<N, Error>,
166    H: FnMut(&Url) -> Result<String, Error>,
167>(
168    ctxt: &Context<N>,
169    stctxt: &mut StaticContext<N, F, G, H>,
170    num: &Transform<N>,
171) -> Result<Sequence<N>, Error> {
172    let n = ctxt.dispatch(stctxt, num)?;
173    match n.len() {
174        1 => {
175            // First try converting to an integer
176            match n[0].to_int() {
177                Ok(i) => Ok(vec![Item::Value(Rc::new(Value::from(i)))]),
178                _ => {
179                    // Otherwise convert to double.
180                    // NB. This can't fail. At worst it returns NaN.
181                    Ok(vec![Item::Value(Rc::new(Value::from(n[0].to_double())))])
182                }
183            }
184        }
185        _ => Err(Error::new(
186            ErrorKind::TypeError,
187            String::from("not a singleton sequence"),
188        )),
189    }
190}
191
192/// XPath sum function.
193pub fn sum<
194    N: Node,
195    F: FnMut(&str) -> Result<(), Error>,
196    G: FnMut(&str) -> Result<N, Error>,
197    H: FnMut(&Url) -> Result<String, Error>,
198>(
199    ctxt: &Context<N>,
200    stctxt: &mut StaticContext<N, F, G, H>,
201    s: &Transform<N>,
202) -> Result<Sequence<N>, Error> {
203    Ok(vec![Item::Value(Rc::new(Value::from(
204        ctxt.dispatch(stctxt, s)?.iter().fold(0.0, |mut acc, i| {
205            acc += i.to_double();
206            acc
207        }),
208    )))])
209}
210
211/// XPath 2.0 avg function.
212pub fn avg<
213    N: Node,
214    F: FnMut(&str) -> Result<(), Error>,
215    G: FnMut(&str) -> Result<N, Error>,
216    H: FnMut(&Url) -> Result<String, Error>,
217>(
218    ctxt: &Context<N>,
219    stctxt: &mut StaticContext<N, F, G, H>,
220    s: &Transform<N>,
221) -> Result<Sequence<N>, Error> {
222    let seq = ctxt.dispatch(stctxt, s)?;
223    if seq.is_empty() {
224        return Ok(seq);
225    }
226
227    // XPath 2.0 has rules for type conversion
228    let sum = seq.iter().fold(0.0, |mut acc, i| {
229        acc += i.to_double();
230        acc
231    });
232    Ok(vec![Item::Value(Rc::new(Value::from(
233        sum / (seq.len() as f64),
234    )))])
235}
236
237/// XPath 2.0 min function.
238pub fn min<
239    N: Node,
240    F: FnMut(&str) -> Result<(), Error>,
241    G: FnMut(&str) -> Result<N, Error>,
242    H: FnMut(&Url) -> Result<String, Error>,
243>(
244    ctxt: &Context<N>,
245    stctxt: &mut StaticContext<N, F, G, H>,
246    s: &Transform<N>,
247) -> Result<Sequence<N>, Error> {
248    let seq = ctxt.dispatch(stctxt, s)?;
249    // XPath 2.0 has rules for type conversion
250    if seq.is_empty() {
251        Ok(seq)
252    } else {
253        Ok(vec![Item::Value(Rc::new(Value::from(
254            seq.iter().skip(1).fold(seq[0].to_double(), |acc, i| {
255                if acc > i.to_double() {
256                    i.to_double()
257                } else {
258                    acc
259                }
260            }),
261        )))])
262    }
263}
264
265/// XPath 2.0 max function.
266pub fn max<
267    N: Node,
268    F: FnMut(&str) -> Result<(), Error>,
269    G: FnMut(&str) -> Result<N, Error>,
270    H: FnMut(&Url) -> Result<String, Error>,
271>(
272    ctxt: &Context<N>,
273    stctxt: &mut StaticContext<N, F, G, H>,
274    s: &Transform<N>,
275) -> Result<Sequence<N>, Error> {
276    let seq = ctxt.dispatch(stctxt, s)?;
277    // XPath 2.0 has rules for type conversion
278    if seq.is_empty() {
279        Ok(seq)
280    } else {
281        Ok(vec![Item::Value(Rc::new(Value::from(
282            seq.iter().skip(1).fold(seq[0].to_double(), |acc, i| {
283                if acc < i.to_double() {
284                    i.to_double()
285                } else {
286                    acc
287                }
288            }),
289        )))])
290    }
291}
292
293/// XPath floor function.
294pub fn floor<
295    N: Node,
296    F: FnMut(&str) -> Result<(), Error>,
297    G: FnMut(&str) -> Result<N, Error>,
298    H: FnMut(&Url) -> Result<String, Error>,
299>(
300    ctxt: &Context<N>,
301    stctxt: &mut StaticContext<N, F, G, H>,
302    f: &Transform<N>,
303) -> Result<Sequence<N>, Error> {
304    let n = ctxt.dispatch(stctxt, f)?;
305    match n.len() {
306        1 => Ok(vec![Item::Value(Rc::new(Value::from(
307            n[0].to_double().floor(),
308        )))]),
309        _ => Err(Error::new(
310            ErrorKind::TypeError,
311            String::from("not a singleton sequence"),
312        )),
313    }
314}
315
316/// XPath ceiling function.
317pub fn ceiling<
318    N: Node,
319    F: FnMut(&str) -> Result<(), Error>,
320    G: FnMut(&str) -> Result<N, Error>,
321    H: FnMut(&Url) -> Result<String, Error>,
322>(
323    ctxt: &Context<N>,
324    stctxt: &mut StaticContext<N, F, G, H>,
325    c: &Transform<N>,
326) -> Result<Sequence<N>, Error> {
327    let n = ctxt.dispatch(stctxt, c)?;
328    match n.len() {
329        1 => Ok(vec![Item::Value(Rc::new(Value::from(
330            n[0].to_double().ceil(),
331        )))]),
332        _ => Err(Error::new(
333            ErrorKind::TypeError,
334            String::from("not a singleton sequence"),
335        )),
336    }
337}
338
339/// XPath round function.
340pub fn round<
341    N: Node,
342    F: FnMut(&str) -> Result<(), Error>,
343    G: FnMut(&str) -> Result<N, Error>,
344    H: FnMut(&Url) -> Result<String, Error>,
345>(
346    ctxt: &Context<N>,
347    stctxt: &mut StaticContext<N, F, G, H>,
348    r: &Transform<N>,
349    pr: &Option<Box<Transform<N>>>,
350) -> Result<Sequence<N>, Error> {
351    match pr {
352        Some(p) => {
353            let n = ctxt.dispatch(stctxt, r)?;
354            let m = ctxt.dispatch(stctxt, p)?;
355            match (n.len(), m.len()) {
356                (1, 1) => Ok(vec![Item::Value(Rc::new(Value::from(
357                    ((n[0].to_double() * (10.0_f64).powi(m[0].to_int().unwrap() as i32)).round())
358                        * (10.0_f64).powi(-m[0].to_int().unwrap() as i32),
359                )))]),
360                _ => Err(Error::new(
361                    ErrorKind::TypeError,
362                    String::from("not a singleton sequence"),
363                )),
364            }
365        }
366        None => {
367            // precision is 0, i.e. round to nearest whole number
368            let n = ctxt.dispatch(stctxt, r)?;
369            match n.len() {
370                1 => Ok(vec![Item::Value(Rc::new(Value::from(
371                    n[0].to_double().round(),
372                )))]),
373                _ => Err(Error::new(
374                    ErrorKind::TypeError,
375                    String::from("not a singleton sequence"),
376                )),
377            }
378        }
379    }
380}
381
382/// Generate a sequence with a range of integers.
383pub(crate) fn tr_range<
384    N: Node,
385    F: FnMut(&str) -> Result<(), Error>,
386    G: FnMut(&str) -> Result<N, Error>,
387    H: FnMut(&Url) -> Result<String, Error>,
388>(
389    ctxt: &Context<N>,
390    stctxt: &mut StaticContext<N, F, G, H>,
391    start: &Transform<N>,
392    end: &Transform<N>,
393) -> Result<Sequence<N>, Error> {
394    let s = ctxt.dispatch(stctxt, start)?;
395    let e = ctxt.dispatch(stctxt, end)?;
396    if s.is_empty() || e.is_empty() {
397        // Empty sequence is the result
398        return Ok(vec![]);
399    }
400    if s.len() != 1 || e.len() != 1 {
401        return Err(Error::new(
402            ErrorKind::TypeError,
403            String::from("operands must be singleton sequence"),
404        ));
405    }
406    let i = s[0].to_int()?;
407    let j = e[0].to_int()?;
408    match i.cmp(&j) {
409        Ordering::Greater => Ok(vec![]),
410        Ordering::Less => {
411            let mut result = Sequence::new();
412            for k in i..=j {
413                result.push_value(&Rc::new(Value::from(k)))
414            }
415            Ok(result)
416        }
417        Ordering::Equal => {
418            let mut seq = Sequence::new();
419            seq.push_value(&Rc::new(Value::from(i)));
420            Ok(seq)
421        }
422    }
423}
424
425/// Perform an arithmetic operation.
426pub(crate) fn arithmetic<
427    N: Node,
428    F: FnMut(&str) -> Result<(), Error>,
429    G: FnMut(&str) -> Result<N, Error>,
430    H: FnMut(&Url) -> Result<String, Error>,
431>(
432    ctxt: &Context<N>,
433    stctxt: &mut StaticContext<N, F, G, H>,
434    ops: &Vec<ArithmeticOperand<N>>,
435) -> Result<Sequence<N>, Error> {
436    // Type: the result will be a number, but integer or double?
437    // If all of the operands are integers, then the result is integer otherwise double
438    // TODO: check the type of all operands to determine type of result (can probably do this in static analysis phase)
439    // In the meantime, let's assume the result will be double and convert any integers
440    let mut acc = 0.0;
441    for o in ops {
442        let j = match ctxt.dispatch(stctxt, &o.operand) {
443            Ok(s) => s,
444            Err(_) => {
445                acc = f64::NAN;
446                break;
447            }
448        };
449        if j.len() != 1 {
450            acc = f64::NAN;
451            break;
452        }
453        let u = j[0].to_double();
454        match o.op {
455            ArithmeticOperator::Noop => acc = u,
456            ArithmeticOperator::Add => acc += u,
457            ArithmeticOperator::Subtract => acc -= u,
458            ArithmeticOperator::Multiply => acc *= u,
459            ArithmeticOperator::Divide => acc /= u,
460            ArithmeticOperator::IntegerDivide => acc /= u, // TODO: convert to integer
461            ArithmeticOperator::Modulo => acc %= u,
462        }
463    }
464    Ok(vec![Item::Value(Rc::new(Value::from(acc)))])
465}
466
467/// XPath format-number function.
468pub fn format_number<
469    N: Node,
470    F: FnMut(&str) -> Result<(), Error>,
471    G: FnMut(&str) -> Result<N, Error>,
472    H: FnMut(&Url) -> Result<String, Error>,
473>(
474    ctxt: &Context<N>,
475    stctxt: &mut StaticContext<N, F, G, H>,
476    num: &Transform<N>,
477    picture: &Transform<N>,
478    _name: &Option<Box<Transform<N>>>,
479) -> Result<Sequence<N>, Error> {
480    let p = ctxt.dispatch(stctxt, picture)?.to_string();
481    let n = ctxt.dispatch(stctxt, num)?;
482    match n.len() {
483        1 => {
484            // First try converting to an integer
485            match n[0].to_int() {
486                Ok(i) => Ok(vec![Item::Value(Rc::new(Value::from(
487                    i.formato(p.as_str()),
488                )))]),
489                _ => {
490                    // Otherwise convert to double.
491                    // NB. This can't fail. At worst it returns NaN.
492                    Ok(vec![Item::Value(Rc::new(Value::from(
493                        n[0].to_double().formato(p.as_str()),
494                    )))])
495                }
496            }
497        }
498        _ => Err(Error::new(
499            ErrorKind::TypeError,
500            String::from("not a singleton sequence"),
501        )),
502    }
503}
504
505/// XSLT xsl:number and XPath format-integer function.
506pub fn format_integer<
507    N: Node,
508    F: FnMut(&str) -> Result<(), Error>,
509    G: FnMut(&str) -> Result<N, Error>,
510    H: FnMut(&Url) -> Result<String, Error>,
511>(
512    ctxt: &Context<N>,
513    stctxt: &mut StaticContext<N, F, G, H>,
514    num: &Transform<N>,
515    picture: &Transform<N>,
516) -> Result<Sequence<N>, Error> {
517    let p = ctxt.dispatch(stctxt, picture)?.to_string();
518    let numbers = ctxt.dispatch(stctxt, num)?;
519    let mut nit = numbers.iter();
520
521    let mut result = String::new();
522
523    // Interpret the picture string.
524    // Most of the tokens are one character, except for 'Ww'.
525    let mut pit = p.chars().peekable();
526    loop {
527        let c = pit.next();
528        if let Some(d) = c {
529            if d.is_alphanumeric() {
530                match d {
531                    '0' => {
532                        // 01, 02, 03, 04, ...
533                        // length specification
534                        // TODO: non-arabic-roman numerals
535                        let mut token = String::from(d);
536
537                        while let Some(p) = pit.peek() {
538                            if p.eq(&'0') {
539                                pit.next();
540                                token.push('0');
541                            } else if p.eq(&'1') {
542                                pit.next();
543                                token.push('1');
544                            } else {
545                                break;
546                            }
547                        }
548
549                        if let Some(num) = nit.next() {
550                            result.push_str(
551                                format!("{:0>1$}", num.to_int()?.to_string(), token.len()).as_str(),
552                            );
553                        } else {
554                            break;
555                        }
556                    }
557                    '1' => {
558                        // 1, 2, 3, ...
559                        if let Some(num) = nit.next() {
560                            result.push_str(num.to_int()?.to_string().as_str())
561                        } else {
562                            break;
563                        }
564                    }
565                    'A' => {
566                        // A, B, C, ..., AA, BB, CC, ...
567                    }
568                    'a' => {
569                        // a, b, c, ..., aa, bb, cc, ...
570                    }
571                    'i' => {
572                        // i, ii, iii, iv, v, vi, ...
573                        if let Some(num) = nit.next() {
574                            result.push_str(
575                                roman_converter(u16::try_from(num.to_int()?).map_err(|e| {
576                                    Error::new(ErrorKind::ParseError, e.to_string())
577                                })?)
578                                .map_err(|e| Error::new(ErrorKind::ParseError, e))?
579                                .to_lowercase()
580                                .as_str(),
581                            )
582                        } else {
583                            break;
584                        }
585                    }
586                    'I' => {
587                        // I, II, III, IV, V, VI, ...
588                        if let Some(num) = nit.next() {
589                            result.push_str(
590                                roman_converter(u16::try_from(num.to_int()?).map_err(|e| {
591                                    Error::new(ErrorKind::ParseError, e.to_string())
592                                })?)
593                                .map_err(|e| Error::new(ErrorKind::ParseError, e))?
594                                .as_str(),
595                            )
596                        } else {
597                            break;
598                        }
599                    }
600                    'w' => {
601                        // one, two, three, ...
602                        if let Some(num) = nit.next() {
603                            result.push_str(
604                                convert(
605                                    num.to_int()?,
606                                    Formatting {
607                                        title_case: false,
608                                        spaces: true,
609                                        conjunctions: false,
610                                        commas: false,
611                                        dashes: false,
612                                    },
613                                )
614                                .to_string()
615                                .as_str(),
616                            )
617                        } else {
618                            break;
619                        }
620                    }
621                    'W' => {
622                        // 'Ww'
623                        if let Some('w') = pit.peek() {
624                            // One, Two, Three, ...
625                            pit.next();
626                            if let Some(num) = nit.next() {
627                                result.push_str(
628                                    convert(
629                                        num.to_int()?,
630                                        Formatting {
631                                            title_case: true,
632                                            spaces: true,
633                                            conjunctions: false,
634                                            commas: false,
635                                            dashes: false,
636                                        },
637                                    )
638                                    .to_string()
639                                    .as_str(),
640                                )
641                            } else {
642                                break;
643                            }
644                        } else {
645                            // ONE, TWO, THREE, ...
646                            if let Some(num) = nit.next() {
647                                result.push_str(
648                                    convert(
649                                        num.to_int()?,
650                                        Formatting {
651                                            title_case: false,
652                                            spaces: true,
653                                            conjunctions: false,
654                                            commas: false,
655                                            dashes: false,
656                                        },
657                                    )
658                                    .to_string()
659                                    .to_uppercase()
660                                    .as_str(),
661                                )
662                            } else {
663                                break;
664                            }
665                        }
666                    }
667                    // TODO: non-English words
668                    // Use french-numbers crate
669                    // Use italian-numbers crate
670                    _ => {}
671                }
672            } else {
673                result.push(d)
674            }
675        } else {
676            break;
677        }
678    }
679
680    Ok(vec![Item::Value(Rc::new(Value::from(result)))])
681}