parse/
dotted_path.rs

1use roxmltree::Attribute;
2use std::borrow::Cow;
3use std::fmt::{
4    Debug,
5    Display,
6    Formatter,
7};
8use std::ops::Range;
9
10use super::{
11    LowerName,
12    PathError,
13    UpperName,
14};
15
16#[derive(Clone)]
17pub enum DottedPath {
18    Lower {
19        name: LowerName,
20        range: Range<usize>,
21        dot: Option<Box<DottedPath>>,
22    },
23    Upper {
24        name: UpperName,
25        range: Range<usize>,
26        dot: Option<Box<DottedPath>>,
27    },
28}
29
30impl DottedPath {
31    fn try_from(value: &str, mut range: Range<usize>) -> Result<Self, PathError> {
32        let mut first = true;
33        let mut upper = false;
34        let mut next = range.clone();
35        let mut chars = value.chars();
36
37        while let Some(c) = chars.next() {
38            if first {
39                first = false;
40
41                if c.is_ascii_uppercase() {
42                    upper = true;
43                } else if !c.is_ascii_lowercase() && c != '_' {
44                    return Err(PathError::Start {
45                        path: next.into(),
46                    });
47                }
48            } else if c == '.' {
49                if chars.as_str().is_empty() {
50                    return Err(PathError::EndWithDot {
51                        path: next.into(),
52                    });
53                } else {
54                    range.end = next.start;
55                    next.start += 1;
56                    let dot = Some(Box::new(Self::try_from(chars.as_str(), next)?));
57                    let value = &value[..range.end-range.start];
58                    if upper {
59                        return Ok(DottedPath::Upper {
60                            name: UpperName {
61                                inner: Cow::Owned(value.to_owned()),
62                            },
63                            dot,
64                            range,
65                        });
66                    } else {
67                        return Ok(DottedPath::Lower {
68                            name: LowerName {
69                                inner: Cow::Owned(value.to_owned()),
70                            },
71                            dot,
72                            range,
73                        });
74                    }
75                }
76            } else {
77                if !c.is_ascii_alphanumeric() {
78                    return Err(PathError::Alphanumeric {
79                        path: next.into(),
80                    });
81                }
82            }
83            next.start += 1;
84        }
85
86        if upper {
87            Ok(DottedPath::Upper {
88                name: UpperName {
89                    inner: Cow::Owned(value.to_owned()),
90                },
91                dot: None,
92                range,
93            })
94        } else {
95            Ok(DottedPath::Lower {
96                name: LowerName {
97                    inner: Cow::Owned(value.to_owned()),
98                },
99                dot: None,
100                range,
101            })
102        }
103    }
104}
105
106impl Debug for DottedPath {
107    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
108        write!(f, "DottedPath({})", self)
109    }
110}
111
112impl Display for DottedPath {
113    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
114        match self {
115            DottedPath::Lower { name, dot, .. } => {
116                write!(f, "{}", name.as_str())?;
117                if let Some(dot) = dot {
118                    write!(f, ".{}", dot)
119                } else {
120                    Ok(())
121                }
122            }
123
124            DottedPath::Upper { name, dot, .. } => {
125                write!(f, "{}", name.as_str())?;
126                if let Some(dot) = dot {
127                    write!(f, ".{}", dot)
128                } else {
129                    Ok(())
130                }
131            }
132        }
133    }
134}
135
136impl<'input> TryFrom<Attribute<'input, 'input>> for DottedPath {
137    type Error = PathError;
138
139    fn try_from(attribute: Attribute<'input, 'input>) -> Result<Self, Self::Error> {
140        if attribute.value().is_empty() {
141            return Err(PathError::Empty {
142                path: attribute.range().into(),
143            });
144        }
145
146        DottedPath::try_from(attribute.value(), attribute.range_value())
147    }
148}