sxd_xpath/
function.rs

1//! Support for registering and creating XPath functions.
2
3use std::borrow::ToOwned;
4use std::collections::HashMap;
5use std::collections::hash_map::Entry;
6use std::ops::Index;
7use std::iter;
8
9use sxd_document::XmlChar;
10
11use ::{Value, str_to_num};
12use ::context;
13use ::nodeset::Nodeset;
14
15/// Types that can be used as XPath functions.
16pub trait Function {
17    /// Evaluate this function in a specific context with a specific
18    /// set of arguments.
19    fn evaluate<'c, 'd>(&self,
20                        context: &context::Evaluation<'c, 'd>,
21                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>;
22}
23
24/// Represents the kind of an XPath value without carrying a value.
25#[derive(Debug, Copy, Clone, PartialEq, Hash)]
26pub enum ArgumentType {
27    Boolean,
28    Number,
29    String,
30    Nodeset,
31}
32
33impl<'a> From<&'a Value<'a>> for ArgumentType {
34    fn from(other: &'a Value<'a>) -> ArgumentType {
35        match *other {
36            Value::Boolean(..) => ArgumentType::Boolean,
37            Value::Number(..)  => ArgumentType::Number,
38            Value::String(..)  => ArgumentType::String,
39            Value::Nodeset(..) => ArgumentType::Nodeset,
40        }
41    }
42}
43
44quick_error! {
45    /// The errors that may occur while evaluating a function
46    #[derive(Debug, Clone, PartialEq, Hash)]
47    pub enum Error {
48        TooManyArguments { expected: usize, actual: usize } {
49            description("too many arguments")
50            display("too many arguments, expected {} but had {}", expected, actual)
51        }
52        NotEnoughArguments { expected: usize, actual: usize } {
53            description("not enough arguments")
54            display("not enough arguments, expected {} but had {}", expected, actual)
55        }
56        ArgumentMissing {
57            description("attempted to use an argument that was not present")
58        }
59        ArgumentNotANodeset { actual: ArgumentType } {
60            description("argument was not a nodeset")
61            display("argument was expected to be a nodeset but was a {:?}", actual)
62        }
63        Other(what: String) {
64            description("an error occurred while evaluating a function")
65            display("could not evaluate function: {}", what)
66        }
67    }
68}
69
70impl Error {
71    fn not_a_nodeset(actual: &Value) -> Error {
72        Error::ArgumentNotANodeset { actual: actual.into() }
73    }
74}
75
76/// Provides common utility functions for dealing with function
77/// argument lists.
78pub struct Args<'d>(pub Vec<Value<'d>>);
79
80impl<'d> Args<'d> {
81    pub fn len(&self) -> usize { self.0.len() }
82    pub fn is_empty(&self) -> bool { self.0.is_empty() }
83
84    /// Ensures that there are at least the requested number of arguments.
85    pub fn at_least(&self, minimum: usize) -> Result<(), Error> {
86        let actual = self.0.len();
87        if actual < minimum {
88            Err(Error::NotEnoughArguments { expected: minimum, actual: actual })
89        } else {
90            Ok(())
91        }
92    }
93
94    /// Ensures that there are no more than the requested number of arguments.
95    pub fn at_most(&self, maximum: usize) -> Result<(), Error> {
96        let actual = self.0.len();
97        if actual > maximum {
98            Err(Error::TooManyArguments { expected: maximum, actual: actual })
99        } else {
100            Ok(())
101        }
102    }
103
104    /// Ensures that there are exactly the requested number of arguments.
105    pub fn exactly(&self, expected: usize) -> Result<(), Error> {
106        let actual = self.0.len();
107        if actual < expected {
108            Err(Error::NotEnoughArguments { expected: expected, actual: actual })
109        } else if actual > expected {
110            Err(Error::TooManyArguments { expected: expected, actual: actual })
111        } else {
112            Ok(())
113        }
114    }
115
116    /// Converts all the arguments into strings.
117    fn into_strings(self) -> Vec<String> {
118        self.0.into_iter().map(Value::into_string).collect()
119    }
120
121    /// Removes the **last** argument and ensures it is a boolean. If
122    /// the argument is not a boolean, it is converted to one.
123    pub fn pop_boolean(&mut self) -> Result<bool, Error> {
124        let v = self.0.pop().ok_or(Error::ArgumentMissing)?;
125        Ok(v.into_boolean())
126    }
127
128    /// Removes the **last** argument and ensures it is a number. If
129    /// the argument is not a number, it is converted to one.
130    pub fn pop_number(&mut self) -> Result<f64, Error> {
131        let v = self.0.pop().ok_or(Error::ArgumentMissing)?;
132        Ok(v.into_number())
133    }
134
135    /// Removes the **last** argument and ensures it is a string. If
136    /// the argument is not a string, it is converted to one.
137    pub fn pop_string(&mut self) -> Result<String, Error> {
138        let v = self.0.pop().ok_or(Error::ArgumentMissing)?;
139        Ok(v.into_string())
140    }
141
142    /// Removes the **last** argument and ensures it is a nodeset. If
143    /// the argument is not a nodeset, a type mismatch error is
144    /// returned.
145    pub fn pop_nodeset(&mut self) -> Result<Nodeset<'d>, Error> {
146        let v = self.0.pop().ok_or(Error::ArgumentMissing)?;
147        match v {
148            Value::Nodeset(v) => Ok(v),
149            a => Err(Error::not_a_nodeset(&a)),
150        }
151    }
152
153    /// Removes the **last** argument. If no argument is present, the
154    /// context node is returned as a nodeset.
155    fn pop_value_or_context_node<'c>(&mut self, context: &context::Evaluation<'c, 'd>) -> Value<'d> {
156        self.0.pop()
157            .unwrap_or_else(|| Value::Nodeset(nodeset![context.node]))
158    }
159
160    /// Removes the **last** argument if it is a string. If no
161    /// argument is present, the context node is converted to a string
162    /// and returned. If there is an argument but it is not a string,
163    /// it is converted to one.
164    fn pop_string_value_or_context_node(&mut self, context: &context::Evaluation) -> String {
165        self.0.pop()
166            .map(Value::into_string)
167            .unwrap_or_else(|| context.node.string_value())
168    }
169
170    /// Removes the **last** argument if it is a nodeset. If no
171    /// argument is present, the context node is added to a nodeset
172    /// and returned. If there is an argument but it is not a nodeset,
173    /// a type mismatch error is returned.
174    fn pop_nodeset_or_context_node<'c>(&mut self, context: &context::Evaluation<'c, 'd>)
175                                       -> Result<Nodeset<'d>, Error>
176    {
177        match self.0.pop() {
178            Some(Value::Nodeset(ns)) => Ok(ns),
179            Some(arg) => Err(Error::not_a_nodeset(&arg)),
180            None => Ok(nodeset![context.node]),
181        }
182    }
183}
184
185impl<'d> Index<usize> for Args<'d> {
186    type Output = Value<'d>;
187
188    fn index(&self, index: usize) -> &Value<'d> { self.0.index(index) }
189}
190
191struct Last;
192
193impl Function for Last {
194    fn evaluate<'c, 'd>(&self,
195                        context: &context::Evaluation<'c, 'd>,
196                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
197    {
198        let args = Args(args);
199        try!(args.exactly(0));
200        Ok(Value::Number(context.size as f64))
201    }
202}
203
204struct Position;
205
206impl Function for Position {
207    fn evaluate<'c, 'd>(&self,
208                        context: &context::Evaluation<'c, 'd>,
209                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
210    {
211        let args = Args(args);
212        try!(args.exactly(0));
213        Ok(Value::Number(context.position as f64))
214    }
215}
216
217struct Count;
218
219impl Function for Count {
220    fn evaluate<'c, 'd>(&self,
221                        _context: &context::Evaluation<'c, 'd>,
222                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
223    {
224        let mut args = Args(args);
225        try!(args.exactly(1));
226        let arg = try!(args.pop_nodeset());
227        Ok(Value::Number(arg.size() as f64))
228    }
229}
230
231struct LocalName;
232
233impl Function for LocalName {
234    fn evaluate<'c, 'd>(&self,
235                        context: &context::Evaluation<'c, 'd>,
236                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
237    {
238        let mut args = Args(args);
239        try!(args.at_most(1));
240        let arg = try!(args.pop_nodeset_or_context_node(context));
241        let name =
242            arg.document_order_first()
243            .and_then(|n| n.expanded_name())
244            .map(|q| q.local_part())
245            .unwrap_or("");
246        Ok(Value::String(name.to_owned()))
247    }
248}
249
250struct NamespaceUri;
251
252impl Function for NamespaceUri {
253    fn evaluate<'c, 'd>(&self,
254                        context: &context::Evaluation<'c, 'd>,
255                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
256    {
257        let mut args = Args(args);
258        try!(args.at_most(1));
259        let arg = try!(args.pop_nodeset_or_context_node(context));
260        let name =
261            arg.document_order_first()
262            .and_then(|n| n.expanded_name())
263            .and_then(|q| q.namespace_uri())
264            .unwrap_or("");
265        Ok(Value::String(name.to_owned()))
266    }
267}
268
269struct Name;
270
271impl Function for Name {
272    fn evaluate<'c, 'd>(&self,
273                        context: &context::Evaluation<'c, 'd>,
274                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
275    {
276        let mut args = Args(args);
277        try!(args.at_most(1));
278        let arg = try!(args.pop_nodeset_or_context_node(context));
279        let name =
280            arg.document_order_first()
281            .and_then(|n| n.prefixed_name())
282            .unwrap_or_else(String::new);
283        Ok(Value::String(name))
284    }
285}
286
287struct StringFn;
288
289impl Function for StringFn {
290    fn evaluate<'c, 'd>(&self,
291                        context: &context::Evaluation<'c, 'd>,
292                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
293    {
294        let mut args = Args(args);
295        try!(args.at_most(1));
296        let arg = args.pop_value_or_context_node(context);
297        Ok(Value::String(arg.string()))
298    }
299}
300
301struct Concat;
302
303impl Function for Concat {
304    fn evaluate<'c, 'd>(&self,
305                        _context: &context::Evaluation<'c, 'd>,
306                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
307    {
308        let args = Args(args);
309        try!(args.at_least(2));
310        let args = args.into_strings();
311        Ok(Value::String(args.concat()))
312    }
313}
314
315struct TwoStringPredicate(fn(&str, &str) -> bool);
316
317impl Function for TwoStringPredicate {
318    fn evaluate<'c, 'd>(&self,
319                        _context: &context::Evaluation<'c, 'd>,
320                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
321    {
322        let args = Args(args);
323        try!(args.exactly(2));
324        let args = args.into_strings();
325        let v = self.0(&args[0], &args[1]);
326        Ok(Value::Boolean(v))
327    }
328}
329
330fn starts_with() -> TwoStringPredicate {
331    fn imp(a: &str, b: &str) -> bool { str::starts_with(a, b) };
332    TwoStringPredicate(imp)
333}
334fn contains() -> TwoStringPredicate {
335    fn imp(a: &str, b: &str) -> bool { str::contains(a, b) };
336    TwoStringPredicate(imp)
337}
338
339struct SubstringCommon(for<'s> fn(&'s str, &'s str) -> &'s str);
340
341impl Function for SubstringCommon {
342    fn evaluate<'c, 'd>(&self,
343                        _context: &context::Evaluation<'c, 'd>,
344                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
345    {
346        let args = Args(args);
347        try!(args.exactly(2));
348        let args = args.into_strings();
349        let s = self.0(&args[0], &args[1]);
350        Ok(Value::String(s.to_owned()))
351    }
352}
353
354fn substring_before() -> SubstringCommon {
355    fn inner<'a>(haystack: &'a str, needle: &'a str) -> &'a str {
356        match haystack.find(needle) {
357            Some(pos) => &haystack[..pos],
358            None => "",
359        }
360    }
361    SubstringCommon(inner)
362}
363
364fn substring_after() -> SubstringCommon {
365    fn inner<'a>(haystack: &'a str, needle: &'a str) -> &'a str {
366        match haystack.find(needle) {
367            Some(pos) => &haystack[pos + needle.len()..],
368            None => "",
369        }
370    }
371    SubstringCommon(inner)
372}
373
374struct Substring;
375
376impl Function for Substring {
377    fn evaluate<'c, 'd>(&self,
378                        _context: &context::Evaluation<'c, 'd>,
379                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
380    {
381        let mut args = Args(args);
382        try!(args.at_least(2));
383        try!(args.at_most(3));
384
385        let len = if args.len() == 3 {
386            let len = try!(args.pop_number());
387            round_ties_to_positive_infinity(len)
388        } else {
389            ::std::f64::INFINITY
390        };
391
392        let start = try!(args.pop_number());
393        let start = round_ties_to_positive_infinity(start);
394        let s = try!(args.pop_string());
395
396        let chars = s.chars().enumerate();
397        let selected_chars = chars.filter_map(|(p, s)| {
398            let p = (p+1) as f64; // 1-based indexing
399            if p >= start && p < start + len {
400                Some(s)
401            } else {
402                None
403            }
404        }).collect() ;
405
406        Ok(Value::String(selected_chars))
407    }
408}
409
410struct StringLength;
411
412impl Function for StringLength {
413    fn evaluate<'c, 'd>(&self,
414                        context: &context::Evaluation<'c, 'd>,
415                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
416    {
417        let mut args = Args(args);
418        try!(args.at_most(1));
419        let arg = args.pop_string_value_or_context_node(context);
420        Ok(Value::Number(arg.chars().count() as f64))
421    }
422}
423
424struct NormalizeSpace;
425
426impl Function for NormalizeSpace {
427    fn evaluate<'c, 'd>(&self,
428                        context: &context::Evaluation<'c, 'd>,
429                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
430    {
431        let mut args = Args(args);
432        try!(args.at_most(1));
433        let arg = args.pop_string_value_or_context_node(context);
434        // TODO: research itertools or another pure-iterator solution
435        let s: Vec<_> = arg.split(XmlChar::is_space_char).filter(|s| !s.is_empty()).collect();
436        let s = s.join(" ");
437        Ok(Value::String(s))
438    }
439}
440
441struct Translate;
442
443impl Function for Translate {
444    fn evaluate<'c, 'd>(&self,
445                        _context: &context::Evaluation<'c, 'd>,
446                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
447    {
448        let mut args = Args(args);
449        try!(args.exactly(3));
450
451        let to = try!(args.pop_string());
452        let from = try!(args.pop_string());
453        let s = try!(args.pop_string());
454
455        let mut replacements = HashMap::new();
456        let pairs = from.chars().zip(to.chars().map(Some).chain(iter::repeat(None)));
457        for (from, to) in pairs {
458            if let Entry::Vacant(entry) = replacements.entry(from) {
459                entry.insert(to);
460            }
461        }
462
463        let s = s.chars().filter_map(|c| {
464            replacements.get(&c).cloned().unwrap_or_else(|| Some(c))
465        }).collect();
466
467        Ok(Value::String(s))
468    }
469}
470
471struct BooleanFn;
472
473impl Function for BooleanFn {
474    fn evaluate<'c, 'd>(&self,
475                        _context: &context::Evaluation<'c, 'd>,
476                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
477    {
478        let args = Args(args);
479        try!(args.exactly(1));
480        Ok(Value::Boolean(args[0].boolean()))
481    }
482}
483
484struct Not;
485
486impl Function for Not {
487    fn evaluate<'c, 'd>(&self,
488                        _context: &context::Evaluation<'c, 'd>,
489                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
490    {
491        let mut args = Args(args);
492        try!(args.exactly(1));
493        let arg = try!(args.pop_boolean());
494        Ok(Value::Boolean(!arg))
495    }
496}
497
498struct BooleanLiteral(bool);
499
500impl Function for BooleanLiteral {
501    fn evaluate<'c, 'd>(&self,
502                        _context: &context::Evaluation<'c, 'd>,
503                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
504    {
505        let args = Args(args);
506        try!(args.exactly(0));
507        Ok(Value::Boolean(self.0))
508    }
509}
510
511fn true_fn() -> BooleanLiteral { BooleanLiteral(true) }
512fn false_fn() -> BooleanLiteral { BooleanLiteral(false) }
513
514struct NumberFn;
515
516impl Function for NumberFn {
517    fn evaluate<'c, 'd>(&self,
518                        context: &context::Evaluation<'c, 'd>,
519                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
520    {
521        let mut args = Args(args);
522        try!(args.at_most(1));
523        let arg = args.pop_value_or_context_node(context);
524        Ok(Value::Number(arg.number()))
525    }
526}
527
528struct Sum;
529
530impl Function for Sum {
531    fn evaluate<'c, 'd>(&self,
532                        _context: &context::Evaluation<'c, 'd>,
533                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
534    {
535        let mut args = Args(args);
536        try!(args.exactly(1));
537        let arg = try!(args.pop_nodeset());
538        let r = arg.iter().map(|n| str_to_num(&n.string_value())).fold(0.0, |acc, i| acc + i);
539        Ok(Value::Number(r))
540    }
541}
542
543struct NumberConvert(fn(f64) -> f64);
544
545impl Function for NumberConvert {
546    fn evaluate<'c, 'd>(&self,
547                        _context: &context::Evaluation<'c, 'd>,
548                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
549    {
550        let mut args = Args(args);
551        try!(args.exactly(1));
552        let arg = try!(args.pop_number());
553        Ok(Value::Number(self.0(arg)))
554    }
555}
556
557fn floor() -> NumberConvert { NumberConvert(f64::floor) }
558fn ceiling() -> NumberConvert { NumberConvert(f64::ceil) }
559
560// http://stackoverflow.com/a/28124775/155423
561fn round_ties_to_positive_infinity(x: f64) -> f64 {
562    let y = x.floor();
563    if x == y {
564        x
565    } else {
566        let z = (2.0 * x - y).floor();
567        // Should use copysign
568        if x.is_sign_positive() ^ z.is_sign_positive() {
569            -z
570        } else {
571            z
572        }
573    }
574}
575
576fn round() -> NumberConvert { NumberConvert(round_ties_to_positive_infinity) }
577
578/// Adds the [XPath 1.0 core function library][corelib].
579///
580/// [corelib]: https://www.w3.org/TR/xpath/#corelib
581pub fn register_core_functions(context: &mut context::Context) {
582    context.set_function("last", Last);
583    context.set_function("position", Position);
584    context.set_function("count", Count);
585    context.set_function("local-name", LocalName);
586    context.set_function("namespace-uri", NamespaceUri);
587    context.set_function("name", Name);
588    context.set_function("string", StringFn);
589    context.set_function("concat", Concat);
590    context.set_function("starts-with", starts_with());
591    context.set_function("contains", contains());
592    context.set_function("substring-before", substring_before());
593    context.set_function("substring-after", substring_after());
594    context.set_function("substring", Substring);
595    context.set_function("string-length", StringLength);
596    context.set_function("normalize-space", NormalizeSpace);
597    context.set_function("translate", Translate);
598    context.set_function("boolean", BooleanFn);
599    context.set_function("not", Not);
600    context.set_function("true", true_fn());
601    context.set_function("false", false_fn());
602    context.set_function("number", NumberFn);
603    context.set_function("sum", Sum);
604    context.set_function("floor", floor());
605    context.set_function("ceiling", ceiling());
606    context.set_function("round", round());
607}
608
609#[cfg(test)]
610mod test {
611    use std::borrow::ToOwned;
612    use std::{fmt, f64};
613
614    use sxd_document::Package;
615
616    use ::{LiteralValue, Value};
617    use ::context;
618    use ::nodeset::Node;
619
620    use super::{
621        Function,
622        Error,
623        Last,
624        Position,
625        Count,
626        LocalName,
627        NamespaceUri,
628        Name,
629        StringFn,
630        Concat,
631        Substring,
632        StringLength,
633        NormalizeSpace,
634        Translate,
635        BooleanFn,
636        NumberFn,
637        Sum,
638        starts_with,
639        contains,
640        substring_before,
641        substring_after,
642        floor,
643        ceiling,
644        round,
645    };
646
647    /// Converts each argument into a `Value` and packs them into a
648    /// vector.
649    macro_rules! args {
650        ( $($val:expr,)* ) => {
651            vec![
652                $( Value::from($val), )*
653            ]
654        };
655        ( $($val:expr),* ) => {
656            args![$($val, )*]
657        };
658    }
659
660    struct Setup<'d> {
661        context: context::Context<'d>,
662    }
663
664    impl<'d> Setup<'d> {
665        fn new() -> Setup<'d> {
666            Setup {
667                context: context::Context::without_core_functions(),
668            }
669        }
670
671        fn evaluate<N, F>(&self, node: N, f: F, args: Vec<Value<'d>>)
672            -> Result<Value<'d>, Error>
673            where N: Into<Node<'d>>,
674                  F: Function
675        {
676            let context = context::Evaluation::new(&self.context, node.into());
677            f.evaluate(&context, args)
678        }
679    }
680
681    fn evaluate_literal<F, F2, T>(f: F, args: Vec<LiteralValue>, rf: F2) -> T
682        where F: Function,
683              F2: FnOnce(Result<Value, Error>) -> T,
684    {
685        let package = Package::new();
686        let doc = package.as_document();
687        let setup = Setup::new();
688
689        rf(setup.evaluate(doc.root(), f, args))
690    }
691
692    #[test]
693    fn last_returns_context_size() {
694        evaluate_literal(Last, args![], |r| {
695            assert_eq!(Ok(Value::Number(1.0)), r);
696        });
697    }
698
699    #[test]
700    fn position_returns_context_position() {
701        evaluate_literal(Position, args![], |r| {
702            assert_eq!(Ok(Value::Number(1.0)), r);
703        });
704    }
705
706    #[test]
707    fn count_counts_nodes_in_nodeset() {
708        let package = Package::new();
709        let doc = package.as_document();
710        let setup = Setup::new();
711
712        let r = setup.evaluate(doc.root(), Count, args![nodeset![doc.root()]]);
713
714        assert_eq!(Ok(Value::Number(1.0)), r);
715    }
716
717    #[test]
718    fn local_name_gets_name_of_element() {
719        let package = Package::new();
720        let doc = package.as_document();
721        let setup = Setup::new();
722
723        let e = doc.create_element(("uri", "wow"));
724        doc.root().append_child(e);
725
726        let r = setup.evaluate(doc.root(), LocalName, args![nodeset![e]]);
727
728        assert_eq!(Ok(Value::String("wow".to_owned())), r);
729    }
730
731    #[test]
732    fn local_name_is_empty_for_empty_nodeset() {
733        evaluate_literal(LocalName, args![nodeset![]], |r| {
734            assert_eq!(Ok(Value::String("".to_owned())), r);
735        });
736    }
737
738    #[test]
739    fn namespace_uri_gets_uri_of_element() {
740        let package = Package::new();
741        let doc = package.as_document();
742        let setup = Setup::new();
743
744        let e = doc.create_element(("uri", "wow"));
745        doc.root().append_child(e);
746
747        let r = setup.evaluate(doc.root(), NamespaceUri, args![nodeset![e]]);
748
749        assert_eq!(Ok(Value::String("uri".to_owned())), r);
750    }
751
752    #[test]
753    fn name_uses_declared_prefix() {
754        let package = Package::new();
755        let doc = package.as_document();
756        let setup = Setup::new();
757
758        let e = doc.create_element(("uri", "wow"));
759        e.register_prefix("prefix", "uri");
760        doc.root().append_child(e);
761
762        let r = setup.evaluate(doc.root(), Name, args![nodeset![e]]);
763
764        assert_eq!(Ok(Value::String("prefix:wow".to_owned())), r);
765    }
766
767    #[test]
768    fn string_converts_to_string() {
769        evaluate_literal(StringFn, args![true], |r| {
770            assert_eq!(Ok(Value::String("true".to_owned())), r);
771        });
772    }
773
774    #[test]
775    fn concat_combines_strings() {
776        evaluate_literal(Concat, args!["hello", " ", "world"], |r| {
777            assert_eq!(Ok(Value::String("hello world".to_owned())), r);
778        });
779    }
780
781    #[test]
782    fn starts_with_checks_prefixes() {
783        evaluate_literal(starts_with(), args!["hello", "he"], |r| {
784            assert_eq!(Ok(Value::Boolean(true)), r);
785        });
786    }
787
788    #[test]
789    fn contains_looks_for_a_needle() {
790        evaluate_literal(contains(), args!["astronomer", "ono"], |r| {
791            assert_eq!(Ok(Value::Boolean(true)), r);
792        });
793    }
794
795    #[test]
796    fn substring_before_slices_before() {
797        evaluate_literal(substring_before(), args!["1999/04/01", "/"], |r| {
798            assert_eq!(Ok(Value::String("1999".to_owned())), r);
799        });
800    }
801
802    #[test]
803    fn substring_after_slices_after() {
804        evaluate_literal(substring_after(), args!["1999/04/01", "/"], |r| {
805            assert_eq!(Ok(Value::String("04/01".to_owned())), r);
806        });
807    }
808
809    #[test]
810    fn substring_is_one_indexed() {
811        evaluate_literal(Substring, args!["あいうえお", 2.0], |r| {
812            assert_eq!(Ok(Value::String("いうえお".to_owned())), r);
813        });
814    }
815
816    #[test]
817    fn substring_has_optional_length() {
818        evaluate_literal(Substring, args!["あいうえお", 2.0, 3.0], |r| {
819            assert_eq!(Ok(Value::String("いうえ".to_owned())), r);
820        });
821    }
822
823    fn substring_test(s: &str, start: f64, len: f64) -> String {
824        evaluate_literal(Substring, args![s, start, len], |r| {
825            match r {
826                Ok(Value::String(s)) => s,
827                r => panic!("substring failed: {:?}", r),
828            }
829        })
830    }
831
832    #[test]
833    fn substring_rounds_values() {
834        assert_eq!("いうえ", substring_test("あいうえお", 1.5, 2.6));
835    }
836
837    #[test]
838    fn substring_is_a_window_of_the_characters() {
839        assert_eq!("あい", substring_test("あいうえお", 0.0, 3.0));
840    }
841
842    #[test]
843    fn substring_with_nan_start_is_empty() {
844        assert_eq!("", substring_test("あいうえお", f64::NAN, 3.0));
845    }
846
847    #[test]
848    fn substring_with_nan_len_is_empty() {
849        assert_eq!("", substring_test("あいうえお", 1.0, f64::NAN));
850    }
851
852    #[test]
853    fn substring_with_infinite_len_goes_to_end_of_string() {
854        assert_eq!("あいうえお", substring_test("あいうえお", -42.0, f64::INFINITY));
855    }
856
857    #[test]
858    fn substring_with_negative_infinity_start_is_empty() {
859        assert_eq!("", substring_test("あいうえお", f64::NEG_INFINITY, f64::INFINITY));
860    }
861
862    #[test]
863    fn string_length_counts_characters() {
864        evaluate_literal(StringLength, args!["日本語"], |r| {
865            assert_eq!(Ok(Value::Number(3.0)), r);
866        });
867    }
868
869    #[test]
870    fn normalize_space_removes_leading_space() {
871        evaluate_literal(NormalizeSpace, args!["\t hello"], |r| {
872            assert_eq!(Ok(Value::String("hello".to_owned())), r);
873        });
874    }
875
876    #[test]
877    fn normalize_space_removes_trailing_space() {
878        evaluate_literal(NormalizeSpace, args!["hello\r\n"], |r| {
879            assert_eq!(Ok(Value::String("hello".to_owned())), r);
880        });
881    }
882
883    #[test]
884    fn normalize_space_squashes_intermediate_space() {
885        evaluate_literal(NormalizeSpace, args!["hello\t\r\n world"], |r| {
886            assert_eq!(Ok(Value::String("hello world".to_owned())), r);
887        });
888    }
889
890    fn translate_test(s: &str, from: &str, to: &str) -> String {
891        evaluate_literal(Translate, args![s, from, to], |r| {
892            match r {
893                Ok(Value::String(s)) => s,
894                r => panic!("translate failed: {:?}", r)
895            }
896        })
897    }
898
899    #[test]
900    fn translate_replaces_characters() {
901        assert_eq!("イエ", translate_test("いえ", "あいうえお", "アイウエオ"));
902    }
903
904    #[test]
905    fn translate_removes_characters_without_replacement() {
906        assert_eq!("イ", translate_test("いえ", "あいうえお", "アイ"));
907    }
908
909    #[test]
910    fn translate_replaces_each_char_only_once() {
911        assert_eq!("b", translate_test("a", "ab", "bc"));
912    }
913
914    #[test]
915    fn translate_uses_first_replacement() {
916        assert_eq!("b", translate_test("a", "aa", "bc"));
917    }
918
919    #[test]
920    fn translate_ignores_extra_replacements() {
921        assert_eq!("b", translate_test("a", "a", "bc"));
922    }
923
924    #[test]
925    fn boolean_converts_to_boolean() {
926        evaluate_literal(BooleanFn, args!["false"], |r| {
927            assert_eq!(Ok(Value::Boolean(true)), r);
928        });
929    }
930
931    #[test]
932    fn number_converts_to_number() {
933        evaluate_literal(NumberFn, args![" -1.2 "], |r| {
934            assert_eq!(Ok(Value::Number(-1.2)), r);
935        });
936    }
937
938    #[test]
939    fn number_fails_with_nan() {
940        evaluate_literal(NumberFn, args![" nope "], |r| assert_number(f64::NAN, r));
941    }
942
943    #[test]
944    fn sum_adds_up_nodeset() {
945        let package = Package::new();
946        let doc = package.as_document();
947        let setup = Setup::new();
948
949        let c = doc.create_comment("-32.0");
950        let t = doc.create_text("98.7");
951
952        let r = setup.evaluate(doc.root(), Sum, args![nodeset![c, t]]);
953
954        assert_eq!(Ok(Value::Number(66.7)), r);
955    }
956
957    /// By default, NaN != NaN and -0.0 == 0.0. We don't want either
958    /// of those to be true.
959    struct PedanticNumber(f64);
960
961    impl PedanticNumber {
962        fn non_nan_key(&self) -> (bool, bool, f64) {
963            (self.0.is_finite(), self.0.is_sign_positive(), self.0)
964        }
965    }
966
967    impl PartialEq for PedanticNumber {
968        fn eq(&self, other: &Self) -> bool {
969            if self.0.is_nan() {
970                other.0.is_nan()
971            } else {
972                self.non_nan_key() == other.non_nan_key()
973            }
974        }
975    }
976
977    impl fmt::Debug for PedanticNumber {
978        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
979            write!(f, "{{ {}, NaN: {}, finite: {}, positive: {} }}",
980                   self.0,
981                   self.0.is_nan(),
982                   self.0.is_finite(),
983                   self.0.is_sign_positive())
984        }
985    }
986
987    fn assert_number(expected: f64, actual: Result<Value, Error>) {
988        match actual {
989            Ok(Value::Number(n)) => assert_eq!(PedanticNumber(n), PedanticNumber(expected)),
990            _ => assert!(false, "{:?} did not evaluate correctly", actual),
991        }
992    }
993
994    #[test]
995    fn floor_rounds_down() {
996        evaluate_literal(floor(), args![199.99], |r| assert_number(199.0, r));
997    }
998
999    #[test]
1000    fn ceiling_rounds_up() {
1001        evaluate_literal(ceiling(), args![199.99], |r| assert_number(200.0, r));
1002    }
1003
1004    #[test]
1005    fn round_nan_to_nan() {
1006        evaluate_literal(round(), args![f64::NAN], |r| assert_number(f64::NAN, r));
1007    }
1008
1009    #[test]
1010    fn round_pos_inf_to_pos_inf() {
1011        evaluate_literal(round(), args![f64::INFINITY], |r| {
1012            assert_number(f64::INFINITY, r)
1013        });
1014    }
1015
1016    #[test]
1017    fn round_neg_inf_to_neg_inf() {
1018        evaluate_literal(round(), args![f64::NEG_INFINITY], |r| {
1019            assert_number(f64::NEG_INFINITY, r)
1020        });
1021    }
1022
1023    #[test]
1024    fn round_pos_zero_to_pos_zero() {
1025        evaluate_literal(round(), args![0.0], |r| assert_number(0.0, r));
1026    }
1027
1028    #[test]
1029    fn round_neg_zero_to_neg_zero() {
1030        evaluate_literal(round(), args![-0.0], |r| assert_number(-0.0, r));
1031    }
1032
1033    #[test]
1034    fn round_neg_zero_point_five_to_neg_zero() {
1035        evaluate_literal(round(), args![-0.5], |r| assert_number(-0.0, r));
1036    }
1037
1038    #[test]
1039    fn round_neg_five_to_neg_five() {
1040        evaluate_literal(round(), args![-5.0], |r| assert_number(-5.0, r));
1041    }
1042
1043    #[test]
1044    fn round_pos_zero_point_five_to_pos_one() {
1045        evaluate_literal(round(), args![0.5], |r| assert_number(1.0, r));
1046    }
1047}