Skip to main content

xrust/parser/datetime/
mod.rs

1//! # parsepicture
2//!
3//! A parser for XPath format picture strings, as a parser combinator.
4//!
5//! This implementation is a quick-and-dirty translation to strftime format.
6//!
7//! TODO: presentation modifiers, and width modifiers
8
9use crate::item::Node;
10use crate::parser::combinators::alt::alt4;
11use crate::parser::combinators::many::many0;
12use crate::parser::combinators::map::map;
13use crate::parser::combinators::opt::opt;
14use crate::parser::combinators::support::none_of;
15use crate::parser::combinators::tag::anychar;
16use crate::parser::combinators::tuple::{tuple2, tuple6};
17use crate::parser::{ParseError, ParseInput, ParserState, StaticState, StaticStateBuilder};
18use crate::xdmerror::*;
19use qualname::{NamespacePrefix, NamespaceUri};
20
21// This implementation translates an XPath picture string to a strftime format
22
23#[allow(dead_code)]
24fn picture<'a, N: Node, L>()
25-> impl Fn(ParseInput<'a, N>, &mut StaticState<L>) -> Result<(ParseInput<'a, N>, String), ParseError>
26where
27    L: FnMut(&NamespacePrefix) -> Result<NamespaceUri, ParseError>,
28{
29    map(
30        many0(alt4(open_escape(), close_escape(), literal(), marker())),
31        |v| v.iter().cloned().collect::<String>(),
32    )
33}
34
35#[allow(dead_code)]
36fn literal<'a, N: Node, L>()
37-> impl Fn(ParseInput<'a, N>, &mut StaticState<L>) -> Result<(ParseInput<'a, N>, String), ParseError>
38where
39    L: FnMut(&NamespacePrefix) -> Result<NamespaceUri, ParseError>,
40{
41    map(none_of("[]"), String::from)
42}
43
44#[allow(dead_code)]
45fn marker<'a, N: Node, L>()
46-> impl Fn(ParseInput<'a, N>, &mut StaticState<L>) -> Result<(ParseInput<'a, N>, String), ParseError>
47where
48    L: FnMut(&NamespacePrefix) -> Result<NamespaceUri, ParseError>,
49{
50    map(
51        tuple6(
52            anychar('['),
53            none_of("]"),
54            opt(none_of(",]")),
55            opt(none_of(",]")),
56            opt(tuple2(anychar(','), none_of("]"))),
57            anychar(']'),
58        ),
59        |(_, c, _p1, _p2, _w, _)| {
60            match c {
61                "Y" => String::from("%Y"),
62                "M" => String::from("%m"),
63                "D" => String::from("%d"),
64                "d" => String::from("%j"),
65                "F" => String::from("%A"),
66                "W" => String::from("%U"),
67                "w" => String::from(""), // not supported
68                "H" => String::from("%H"),
69                "h" => String::from("%I"),
70                "P" => String::from("%P"),
71                "m" => String::from("%M"),
72                "s" => String::from("%S"),
73                "f" => String::from("%f"),
74                "Z" => String::from("%Z"),
75                "z" => String::from("%:z"), // partial support
76                "C" => String::from(""),    // not supported
77                "E" => String::from(""),    // not supported
78                _ => String::from(""),      // error
79            }
80        },
81    )
82}
83
84#[allow(dead_code)]
85fn open_escape<'a, N: Node, L>()
86-> impl Fn(ParseInput<'a, N>, &mut StaticState<L>) -> Result<(ParseInput<'a, N>, String), ParseError>
87where
88    L: FnMut(&NamespacePrefix) -> Result<NamespaceUri, ParseError>,
89{
90    map(tuple2(anychar('['), anychar('[')), |_| String::from("["))
91}
92#[allow(dead_code)]
93fn close_escape<'a, N: Node, L>()
94-> impl Fn(ParseInput<'a, N>, &mut StaticState<L>) -> Result<(ParseInput<'a, N>, String), ParseError>
95where
96    L: FnMut(&NamespacePrefix) -> Result<NamespaceUri, ParseError>,
97{
98    map(tuple2(anychar(']'), anychar(']')), |_| String::from("]"))
99}
100
101pub fn parse<N: Node>(e: &str) -> Result<String, Error> {
102    let state: ParserState<N> = ParserState::new();
103    let mut static_state = StaticStateBuilder::new()
104        .namespace(|_| {
105            NamespaceUri::try_from("urn:xrust").map_err(|_| ParseError::MissingNameSpace)
106        })
107        .build();
108    match picture()((e, state), &mut static_state) {
109        Ok(((rem, _), value)) => {
110            if rem.is_empty() {
111                Ok(value)
112            } else {
113                Err(Error::new(
114                    ErrorKind::Unknown,
115                    format!("extra characters after expression: \"{}\"", rem),
116                ))
117            }
118        }
119        Err(_) => Err(Error::new(
120            ErrorKind::ParseError,
121            String::from("unable to parse picture"),
122        )),
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use crate::trees::nullo::Nullo;
130
131    #[test]
132    fn picture_empty() {
133        let pic = parse::<Nullo>("").expect("failed to parse picture \"\"");
134        assert_eq!(pic, "");
135    }
136
137    #[test]
138    fn picture_date() {
139        let pic = parse::<Nullo>("[D] [M] [Y]").expect("failed to parse picture \"[D] [M] [Y]\"");
140        assert_eq!(pic, "%d %m %Y");
141    }
142
143    #[test]
144    fn picture_time() {
145        let pic = parse::<Nullo>("Hr [h][P] Mins [m] secs [s],[f]")
146            .expect("failed to parse picture \"Hr [h][P] Mins [m] secs [s],[f]\"");
147        assert_eq!(pic, "Hr %I%P Mins %M secs %S,%f");
148    }
149
150    #[test]
151    fn picture_datetime() {
152        let pic = parse::<Nullo>("[D]/[M]/[Y] [H]:[m]:[s]")
153            .expect("failed to parse picture \"[D]/[M]/[Y] [H]:[m]:[s]\"");
154        assert_eq!(pic, "%d/%m/%Y %H:%M:%S");
155    }
156
157    #[test]
158    fn picture_escapes() {
159        let pic = parse::<Nullo>("[[[D]/[M]/[Y]]] [[[H]:[m]:[s]]]")
160            .expect("failed to parse picture \"[D]/[M]/[Y] [H]:[m]:[s]\"");
161        assert_eq!(pic, "[%d/%m/%Y] [%H:%M:%S]");
162    }
163}