xee_interpreter/atomic/
xpath_repr.rs

1use xot::xmlname::NameStrInfo;
2
3use super::{Atomic, StringType};
4
5impl Atomic {
6    /// XPath representation of the atomic value.
7    pub fn xpath_representation(&self) -> String {
8        match self {
9            Atomic::String(string_type, v) => match string_type {
10                StringType::String => string_literal(v),
11                _ => {
12                    let schema_type = string_type.schema_type();
13                    format!("xs:{}({})", schema_type.local_name(), string_literal(v))
14                }
15            },
16
17            Atomic::Boolean(v) => {
18                if *v {
19                    "true()".to_string()
20                } else {
21                    "false()".to_string()
22                }
23            }
24            // for any numeric type the canonical notation is enough
25            Atomic::Decimal(_) | Atomic::Integer(_, _) | Atomic::Float(_) | Atomic::Double(_) => {
26                self.string_value()
27            }
28
29            // QName is not represented by casting, as according to 3.14.2
30            // in the XPath 3.1 spec casting to xs:QName can cause surprises
31            // and it's preferable to use the fn:QName function
32            Atomic::QName(v) => {
33                format!(
34                    r#"fn:QName({}, {})"#,
35                    string_literal(v.namespace()),
36                    string_literal(v.local_name())
37                )
38            }
39            // everything else is represented by taking the canonical notation
40            // and then casting it into the required type
41            _ => self.canonical_xpath_representation(),
42        }
43    }
44
45    fn canonical_xpath_representation(&self) -> String {
46        format!(
47            "xs:{}({})",
48            self.schema_type().local_name(),
49            string_literal(&self.string_value())
50        )
51    }
52}
53
54fn string_literal(s: &str) -> String {
55    if s.contains('\"') {
56        if s.contains('\'') {
57            let s = s.replace('\"', r#""""#);
58            format!(r#""{}""#, s)
59        } else {
60            format!(r#"'{}'"#, s)
61        }
62    } else {
63        format!(r#""{}""#, s)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use ibig::IBig;
70    use rust_decimal_macros::dec;
71
72    use crate::atomic::{BinaryType, Duration, IntegerType};
73
74    use super::*;
75
76    #[test]
77    fn test_string_simple() {
78        let atomic: Atomic = "foo".into();
79        assert_eq!(atomic.xpath_representation(), r#""foo""#);
80    }
81
82    #[test]
83    fn test_string_with_single_quote() {
84        let atomic: Atomic = "foo'bar".into();
85        assert_eq!(atomic.xpath_representation(), r#""foo'bar""#);
86    }
87
88    #[test]
89    fn test_string_with_double_quote() {
90        let atomic: Atomic = r#"foo"bar"#.into();
91        assert_eq!(atomic.xpath_representation(), r#"'foo"bar'"#);
92    }
93
94    #[test]
95    fn test_string_with_both_quotes() {
96        let atomic: Atomic = r#"foo'bar"baz"#.into();
97        assert_eq!(atomic.xpath_representation(), r#""foo'bar""baz""#);
98    }
99
100    #[test]
101    fn test_normalized_string() {
102        let atomic = Atomic::String(StringType::NormalizedString, "foo".into());
103        assert_eq!(
104            atomic.xpath_representation(),
105            r#"xs:normalizedString("foo")"#
106        );
107    }
108
109    #[test]
110    fn test_untyped() {
111        let atomic = Atomic::Untyped("foo".into());
112        assert_eq!(atomic.xpath_representation(), r#"xs:untypedAtomic("foo")"#);
113    }
114
115    #[test]
116    fn test_boolean_true() {
117        let atomic = Atomic::Boolean(true);
118        assert_eq!(atomic.xpath_representation(), "true()");
119    }
120
121    #[test]
122    fn test_boolean_false() {
123        let atomic = Atomic::Boolean(false);
124        assert_eq!(atomic.xpath_representation(), "false()");
125    }
126
127    #[test]
128    fn test_decimal_left_right() {
129        let atomic = Atomic::Decimal(dec!(1.5).into());
130        assert_eq!(atomic.xpath_representation(), "1.5");
131    }
132
133    #[test]
134    fn test_decimal_is_integer() {
135        let atomic = Atomic::Decimal(dec!(1.0).into());
136        assert_eq!(atomic.xpath_representation(), "1");
137    }
138
139    #[test]
140    fn test_decimal_only_right() {
141        let atomic = Atomic::Decimal(dec!(0.5).into());
142        assert_eq!(atomic.xpath_representation(), "0.5");
143    }
144
145    #[test]
146    fn test_integer() {
147        let i: IBig = 1.into();
148        let atomic = Atomic::Integer(IntegerType::Integer, i.into());
149        assert_eq!(atomic.xpath_representation(), "1");
150    }
151
152    #[test]
153    fn test_qname() {
154        let name = xot::xmlname::OwnedName::new(
155            "foo".to_string(),
156            "http://example.com".to_string(),
157            "".to_string(),
158        );
159        let atomic = Atomic::QName(name.into());
160        assert_eq!(
161            atomic.xpath_representation(),
162            r#"fn:QName("http://example.com", "foo")"#
163        );
164    }
165
166    #[test]
167    fn test_hex_binary() {
168        let atomic = Atomic::Binary(BinaryType::Hex, vec![0xDE, 0xAD, 0xBE, 0xEF].into());
169        assert_eq!(atomic.xpath_representation(), "xs:hexBinary(\"DEADBEEF\")");
170    }
171
172    #[test]
173    fn test_base64_binary() {
174        let atomic = Atomic::Binary(BinaryType::Base64, vec![0xDE, 0xAD, 0xBE, 0xEF].into());
175        assert_eq!(
176            atomic.xpath_representation(),
177            "xs:base64Binary(\"3q2+7w==\")"
178        );
179    }
180
181    #[test]
182    fn test_duration() {
183        let duration = Duration::new(14, chrono::Duration::seconds(0));
184
185        let atomic = Atomic::Duration(duration.into());
186        assert_eq!(atomic.xpath_representation(), r#"xs:duration("P1Y2M")"#);
187    }
188
189    #[test]
190    fn test_day_time_duration() {
191        let atomic = Atomic::DayTimeDuration(chrono::Duration::seconds(641).into());
192        assert_eq!(
193            atomic.xpath_representation(),
194            r#"xs:dayTimeDuration("PT10M41S")"#
195        );
196    }
197}