xot/
xmlvalue.rs

1use std::fmt::Debug;
2
3use ahash::AHashMap;
4
5use crate::error::Error;
6use crate::id::{NameId, NamespaceId, PrefixId};
7
8/// The type of the XML node.
9///
10/// Access it using [`Value::value_type`] or
11/// [`Xot::value_type`](crate::xotdata::Xot::value_type).
12///
13///    
14/// The `ValueType` can be used if you are interested in
15/// the type of the value without needing to match on it.
16#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
17pub enum ValueType {
18    /// Document that holds everything. Note that this not the same as the
19    /// document element.
20    Document,
21    /// Element; it has a name, attributes and namespace information.
22    Element,
23    /// Text. You can get and set the text value.
24    Text,
25    /// Processing instruction
26    ProcessingInstruction,
27    /// Comment.
28    Comment,
29    /// Attribute
30    Attribute,
31    /// Namespace
32    Namespace,
33}
34
35#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
36pub(crate) enum ValueCategory {
37    Normal,
38    Attribute,
39    Namespace,
40}
41
42/// An XML value.
43///
44/// Access it using [`Xot::value`](crate::xotdata::Xot::value) or
45/// mutably using [`Xot::value_mut`](crate::xotdata::Xot::value_mut).
46#[derive(Debug, Clone, Eq, PartialEq, Hash)]
47pub enum Value {
48    /// Document that holds everything. Note that this not the same as the document
49    /// element.
50    Document,
51    /// Element; it has a name, attributes and namespace information.
52    Element(Element),
53    /// Text. You can get and set the text value.
54    Text(Text),
55    /// Processing instruction.
56    ProcessingInstruction(ProcessingInstruction),
57    /// Comment.
58    Comment(Comment),
59    /// Attribute
60    Attribute(Attribute),
61    /// Namespace
62    Namespace(Namespace),
63}
64
65impl Value {
66    /// Returns the type of the XML value.
67    pub fn value_type(&self) -> ValueType {
68        match self {
69            Value::Document => ValueType::Document,
70            Value::Element(_) => ValueType::Element,
71            Value::Text(_) => ValueType::Text,
72            Value::Comment(_) => ValueType::Comment,
73            Value::ProcessingInstruction(_) => ValueType::ProcessingInstruction,
74            Value::Attribute(_) => ValueType::Attribute,
75            Value::Namespace(_) => ValueType::Namespace,
76        }
77    }
78
79    pub(crate) fn value_category(&self) -> ValueCategory {
80        match self {
81            Value::Document
82            | Value::Element(_)
83            | Value::Text(_)
84            | Value::ProcessingInstruction(_)
85            | Value::Comment(_) => ValueCategory::Normal,
86            Value::Attribute(_) => ValueCategory::Attribute,
87            Value::Namespace(_) => ValueCategory::Namespace,
88        }
89    }
90
91    pub(crate) fn is_normal(&self) -> bool {
92        matches!(
93            self,
94            Value::Document
95                | Value::Element(_)
96                | Value::Text(_)
97                | Value::ProcessingInstruction(_)
98                | Value::Comment(_)
99        )
100    }
101}
102
103/// A map of PrefixId to NamespaceId for namespace tracking.
104///
105/// This is a real hash map, thus providing constant time access and does not
106/// preserve order information.
107///
108/// It is used to return namespace information from various APIs.
109pub type Prefixes = AHashMap<PrefixId, NamespaceId>;
110
111/// XML element name.
112///
113/// Does not include namespace or attribute information;
114/// this is kept in the tree.
115///
116/// Example: `<foo/>`.
117#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
118pub struct Element {
119    pub(crate) name_id: NameId,
120}
121
122impl Element {
123    pub(crate) fn new(name_id: NameId) -> Self {
124        Self { name_id }
125    }
126
127    /// The name of the element.
128    ///
129    /// ```rust
130    /// use xot::Xot;
131    ///
132    /// let mut xot = Xot::new();
133    /// let name_doc = xot.add_name("doc");
134    ///
135    /// let root = xot.parse("<doc/>")?;
136    /// let doc_el = xot.document_element(root).unwrap();
137    /// let element = xot.element(doc_el).unwrap();
138    /// assert_eq!(element.name(), name_doc);
139    ///
140    /// # Ok::<(), xot::Error>(())
141    /// ```
142    pub fn name(&self) -> NameId {
143        self.name_id
144    }
145
146    /// Set the name of an element
147    pub fn set_name(&mut self, name_id: NameId) {
148        self.name_id = name_id;
149    }
150}
151
152/// XML text value.
153///
154/// Example: `Bar` in `<foo>Bar</foo>`, or `hello` and `world` in `<greeting>hello<sep/>world</greeting>`.
155#[derive(Debug, Clone, Eq, PartialEq, Hash)]
156pub struct Text {
157    pub(crate) text: String,
158}
159
160impl Text {
161    pub(crate) fn new(text: String) -> Self {
162        Text { text }
163    }
164
165    /// Get the text value.
166    ///
167    /// See [`Xot::text_str`](`crate::Xot::text_str`) and [`Xot::text_content_str`](`crate::Xot::text_content_str`) for
168    /// more convenient ways to get text values.
169    pub fn get(&self) -> &str {
170        &self.text
171    }
172
173    /// Get the text value, as a mutable reference.
174    pub fn get_mut(&mut self) -> &mut String {
175        &mut self.text
176    }
177
178    /// Set the text value.
179    ///
180    /// ```rust
181    /// use xot::Xot;
182    ///
183    /// let mut xot = Xot::new();
184    /// let root = xot.parse(r#"<doc>Example</doc>"#)?;
185    /// let doc_el = xot.document_element(root).unwrap();
186    /// let text_node = xot.first_child(doc_el).unwrap();
187    ///
188    /// let text = xot.text_mut(text_node).unwrap();
189    /// text.set("New text");
190    ///
191    /// assert_eq!(xot.to_string(root).unwrap(), r#"<doc>New text</doc>"#);
192    /// # Ok::<(), xot::Error>(())
193    /// ```
194    pub fn set<S: Into<String>>(&mut self, text: S) {
195        self.text = text.into();
196    }
197}
198
199/// XML comment.
200///
201/// Example: `<!-- foo -->`.
202#[derive(Debug, Clone, Eq, PartialEq, Hash)]
203pub struct Comment {
204    pub(crate) text: String,
205}
206
207impl Comment {
208    pub(crate) fn new(text: String) -> Self {
209        Comment { text }
210    }
211
212    /// Get the comment text.
213    pub fn get(&self) -> &str {
214        &self.text
215    }
216
217    /// Set the comment text.
218    ///
219    /// Rejects comments that contain `--` as illegal.
220    pub fn set<S: Into<String>>(&mut self, text: S) -> Result<(), Error> {
221        let text = text.into();
222        if text.contains("--") {
223            return Err(Error::InvalidComment(text));
224        }
225        self.text = text;
226        Ok(())
227    }
228}
229
230/// XML processing instruction value.
231///
232/// Example: `<?foo?>` or `<?foo bar?>`.
233#[derive(Debug, Clone, Eq, PartialEq, Hash)]
234pub struct ProcessingInstruction {
235    pub(crate) target: NameId,
236    pub(crate) data: Option<String>,
237}
238
239impl ProcessingInstruction {
240    pub(crate) fn new(target: NameId, data: Option<String>) -> Self {
241        ProcessingInstruction { target, data }
242    }
243
244    /// Get processing instruction target.
245    pub fn target(&self) -> NameId {
246        self.target
247    }
248
249    /// Get processing instruction data.
250    pub fn data(&self) -> Option<&str> {
251        self.data.as_deref()
252    }
253
254    /// Set target
255    pub fn set_target<S: Into<String>>(&mut self, target: NameId) -> Result<(), Error> {
256        // XXX Ideally check that name follows XML spec
257        self.target = target;
258        Ok(())
259    }
260
261    /// Set data.
262    pub fn set_data<S: Into<String>>(&mut self, data: Option<S>) {
263        // XXX Ideally check that data follows XML spec, i.e. not contain
264        // "?>".
265        if let Some(data) = data {
266            let data = data.into();
267            if !data.is_empty() {
268                self.data = Some(data);
269                return;
270            }
271        }
272        self.data = None;
273    }
274}
275
276/// Represents a namespace node.
277#[derive(Debug, Clone, Eq, PartialEq, Hash)]
278pub struct Namespace {
279    pub(crate) prefix_id: PrefixId,
280    pub(crate) namespace_id: NamespaceId,
281}
282
283impl Namespace {
284    /// Get prefix
285    pub fn prefix(&self) -> PrefixId {
286        self.prefix_id
287    }
288
289    /// Get namespace
290    pub fn namespace(&self) -> NamespaceId {
291        self.namespace_id
292    }
293
294    /// Set namespace id
295    pub fn set_namespace(&mut self, namespace_id: NamespaceId) {
296        self.namespace_id = namespace_id;
297    }
298}
299
300/// Represents an attribute node.
301#[derive(Debug, Clone, Eq, PartialEq, Hash)]
302pub struct Attribute {
303    pub(crate) name_id: NameId,
304    pub(crate) value: String,
305}
306
307impl Attribute {
308    /// Get name
309    pub fn name(&self) -> NameId {
310        self.name_id
311    }
312
313    /// Get value
314    pub fn value(&self) -> &str {
315        &self.value
316    }
317
318    /// Set value
319    pub fn set_value<S: Into<String>>(&mut self, value: S) {
320        self.value = value.into();
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use crate::xotdata::Xot;
328
329    #[test]
330    fn test_element_hashable_name() {
331        let mut xot = Xot::new();
332        let a = xot.add_name("a");
333        let b = xot.add_name("b");
334
335        let alpha = Element { name_id: a };
336        let beta = Element { name_id: a };
337        let gamma = Element { name_id: b };
338
339        let hash_builder = ahash::RandomState::with_seed(42);
340        let alpha_hash = hash_builder.hash_one(alpha);
341        let beta_hash = hash_builder.hash_one(beta);
342        let gamma_hash = hash_builder.hash_one(gamma);
343        assert_eq!(alpha_hash, beta_hash);
344        assert_ne!(alpha_hash, gamma_hash);
345    }
346}