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}