1use roxmltree::{
2 Attribute,
3 Node,
4};
5use std::borrow::{
6 Cow,
7 Borrow,
8};
9use std::fmt::{
10 Debug,
11 Display,
12 Formatter,
13};
14use std::cmp::Ordering;
15use std::ops::Range;
16
17use super::NameError;
18
19#[derive(Clone)]
20#[derive(Eq, PartialEq)]
21#[derive(Ord, PartialOrd)]
22pub struct LowerName {
23 pub (crate) inner: Cow<'static, str>,
24}
25
26impl LowerName {
27 pub fn as_str(&self) -> &str {
28 self.inner.as_ref()
29 }
30
31 pub fn append(&self, rhs: &str) -> Self {
32 let lhs = self.as_str();
33 let mut s = String::with_capacity(lhs.len() + rhs.len());
34 s.push_str(lhs);
35 s.push_str(rhs);
36 LowerName {
37 inner: Cow::Owned(s),
38 }
39 }
40
41 fn try_from(value: &str, mut range: Range<usize>) -> Result<Self, NameError> {
42 let mut first = true;
43 for c in value.chars() {
44 if first {
45 first = false;
46
47 if !c.is_ascii_lowercase() && c != '_' {
48 return Err(NameError::StartLowercase {
49 name: range.into(),
50 });
51 }
52 } else {
53 if !c.is_ascii_alphanumeric() {
54 return Err(NameError::Alphanumeric {
55 name: range.into(),
56 });
57 }
58 }
59 range.start += 1;
60 }
61
62 Ok(LowerName {
63 inner: Cow::Owned(value.to_owned()),
64 })
65 }
66
67 pub fn unchecked_from(str: &'static str) -> Self {
68 LowerName {
69 inner: Cow::Borrowed(str),
70 }
71 }
72}
73
74impl Borrow<str> for LowerName {
75 fn borrow(&self) -> &str {
76 self.as_str()
77 }
78}
79
80impl Debug for LowerName {
81 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
82 write!(f, "LowerName({})", self.as_str())
83 }
84}
85
86impl Display for LowerName {
87 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
88 write!(f, "{}", self.as_str())
89 }
90}
91
92impl<'input> TryFrom<Attribute<'input, 'input>> for LowerName {
93 type Error = NameError;
94
95 fn try_from(attribute: Attribute<'input, 'input>) -> Result<Self, Self::Error> {
96 if attribute.value().is_empty() {
97 return Err(NameError::Empty {
98 name: attribute.range().into(),
99 });
100 }
101
102 LowerName::try_from(attribute.value(), attribute.range_value())
103 }
104}
105
106impl<'input> TryFrom<Node<'input, 'input>> for LowerName {
107 type Error = NameError;
108
109 fn try_from(node: Node<'input, 'input>) -> Result<Self, Self::Error> {
110 if let Some(value) = node.text() {
111 if value.is_empty() {
112 return Err(NameError::Empty {
113 name: node.range().into(),
114 });
115 }
116
117 LowerName::try_from(value, node.range())
118 } else {
119 Err(NameError::Empty {
120 name: node.range().into(),
121 })
122 }
123 }
124}
125
126impl PartialEq<str> for LowerName {
127 fn eq(&self, other: &str) -> bool {
128 PartialEq::eq(self.as_str(), other)
129 }
130}
131
132impl PartialOrd<str> for LowerName {
133 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
134 PartialOrd::partial_cmp(self.as_str(), other)
135 }
136}