nxml_rs/
element.rs

1use std::{
2    borrow::Cow,
3    fmt::{self, Display},
4    ops::{Div, Rem},
5};
6
7#[cfg(feature = "compact_str")]
8use compact_str::{CompactString, ToCompactString};
9
10#[cfg(feature = "indexmap")]
11type Map<K, V> = indexmap::IndexMap<K, V>;
12#[cfg(not(feature = "indexmap"))]
13type Map<K, V> = std::collections::HashMap<K, V>;
14
15/// An XML element.
16///
17/// This is a result of zero-copy parsing, meaning you might run into lifetime
18/// issues.
19///
20/// If you need to own the element separately from the source XML, you can
21/// convert it to [`Element`] using
22/// [`into_owned`](struct.ElementRef.html#method.into_owned).
23#[derive(Debug, PartialEq, Eq, Clone)]
24pub struct ElementRef<'s> {
25    /// The name of the element, e.g. `LuaComponent` in `<LuaComponent />`.
26    pub name: &'s str,
27    /// The text content of the element, e.g. `hello` in
28    /// `<SomeComponent>hello</SomeComponent>`.
29    ///
30    /// If there are multiple text nodes, they are concatenated into a single
31    /// string with spaces between them. This is the only case where the
32    /// parsing is not zero-copy, as the text is discontinuous in the source
33    /// XML.
34    ///
35    /// If there is no text content, the value is `Cow::Borrowed("")`.
36    pub text_content: Cow<'s, str>,
37    /// A map of element attributes, e.g. `name="comp"` in `<SomeComponent
38    /// name="comp" />`, where the key is `name` and the value is `comp`.
39    pub attributes: Map<&'s str, &'s str>,
40    /// A list of child elements, e.g. [`<SomeComponent/>`,
41    /// `<SomeOtherComponent/>`] in
42    /// ```xml
43    /// <Entity>
44    ///     <SomeComponent/>
45    ///     <SomeOtherComponent/>
46    /// </Entity>
47    /// ```
48    pub children: Vec<ElementRef<'s>>,
49}
50
51impl<'s> ElementRef<'s> {
52    /// Create a new element with the given name.
53    pub fn new(name: &'s str) -> Self {
54        Self {
55            name,
56            attributes: Map::new(),
57            children: Vec::new(),
58            text_content: Cow::Borrowed(""),
59        }
60    }
61
62    /// Convert this element to an owned [`Element`] by cloning all the strings.
63    ///
64    /// # Example
65    /// ```rust
66    /// # use nxml_rs::*;
67    /// # fn assert_static<T: 'static>(_: T) {}
68    /// let nonstatic_prop = String::from("value");
69    /// let element = nxml_ref!(<Entity {&nonstatic_prop} />);
70    ///
71    /// let owned_element = element.to_owned();
72    ///
73    /// assert_static(owned_element);
74    /// ```
75    pub fn to_owned(&self) -> Element {
76        #[cfg(feature = "compact_str")]
77        return Element {
78            name: self.name.to_compact_string(),
79            attributes: self
80                .attributes
81                .iter()
82                .map(|(&k, &v)| (k.to_compact_string(), v.to_compact_string()))
83                .collect(),
84            children: self.children.iter().map(|c| c.to_owned()).collect(),
85            text_content: self.text_content.to_compact_string(),
86        };
87
88        #[cfg(not(feature = "compact_str"))]
89        Element {
90            name: self.name.to_owned(),
91            attributes: self
92                .attributes
93                .iter()
94                .map(|(&k, &v)| (k.to_owned(), v.to_owned()))
95                .collect(),
96            children: self.children.iter().map(|c| c.to_owned()).collect(),
97            text_content: self.text_content.clone().into_owned(),
98        }
99    }
100}
101
102#[cfg(feature = "compact_str")]
103type Str = compact_str::CompactString;
104#[cfg(not(feature = "compact_str"))]
105type Str = String;
106
107/// An owned XML element. Slightly easier to work with than [`ElementRef`].
108#[derive(Debug, PartialEq, Eq, Clone)]
109pub struct Element {
110    /// The name of the element, e.g. `LuaComponent` in `<LuaComponent />`.
111    pub name: Str,
112    /// The text content of the element, e.g. `hello` in
113    /// `<SomeComponent>hello</SomeComponent>`.
114    ///
115    /// If there are multiple text nodes, they are concatenated into a single
116    /// string with spaces between them.
117    pub text_content: Str,
118    /// A map of element attributes, e.g. `name="comp"` in `<SomeComponent
119    /// name="comp" />`, where the key is `name` and the value is `comp`.
120    pub attributes: Map<Str, Str>,
121    /// A list of child elements, e.g. [`<SomeComponent/>`,
122    /// `<SomeOtherComponent/>`] in
123    /// ```xml
124    /// <Entity>
125    ///     <SomeComponent/>
126    ///     <SomeOtherComponent/>
127    /// </Entity>
128    /// ```
129    pub children: Vec<Element>,
130}
131
132impl Element {
133    /// Create a new element with the given name.
134    #[cfg(feature = "compact_str")]
135    pub fn new(name: impl ToCompactString) -> Element {
136        Element {
137            name: name.to_compact_string(),
138            attributes: Map::new(),
139            children: Vec::new(),
140            text_content: CompactString::const_new(""),
141        }
142    }
143
144    /// Create a new element with the given name.
145    #[cfg(not(feature = "compact_str"))]
146    pub fn new(name: impl ToString) -> Element {
147        Element {
148            name: name.to_string(),
149            attributes: Map::new(),
150            children: Vec::new(),
151            text_content: String::new(),
152        }
153    }
154
155    /// Create an [`ElementRef`] view of this element.
156    /// # Example
157    /// ```rust
158    /// # use nxml_rs::*;
159    /// let element = nxml!(<root><thing/><thing/></root>);
160    ///
161    /// let element_ref: ElementRef = element.as_ref();
162    ///
163    /// assert_eq!(element_ref.to_string(), "<root><thing/><thing/></root>");
164    /// ```
165    pub fn as_ref(&self) -> ElementRef {
166        ElementRef {
167            name: &self.name,
168            attributes: self
169                .attributes
170                .iter()
171                .map(|(k, v)| (k.as_str(), v.as_str()))
172                .collect(),
173            children: self.children.iter().map(|c| c.as_ref()).collect(),
174            text_content: Cow::Borrowed(&self.text_content),
175        }
176    }
177}
178
179/// A text extractor, part of the DSL.
180///
181/// Only really needed to avoid writing &(&element / "child").text_content, to
182/// be able to write `&element / "child" % Text` instead.
183#[derive(Debug)]
184pub struct Text;
185
186macro_rules! dsl_impl {
187    (
188        #[$macro:ident]
189        impl $tpe:ident$(<$src:lifetime>)? {
190            attr($attr_str:ty) -> $attr_str_owned:ty,
191            text($text_str:ty)$(.$text_transform:ident())?,
192        }
193    ) => {
194        impl$(<$src>)? $tpe$(<$src>)? {
195
196            /// A shorthand for getting an attribute value.
197            /// # Example
198            /// ```rust
199            /// # use nxml_rs::*;
200            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity key=\"value\"/>);")]
201            ///
202            /// assert_eq!(element.attr("key"), Some("value"));
203            /// ```
204            pub fn attr(&self, key: &str) -> Option<&str> {
205                self.attributes.get(key).map(|s| s.as_ref())
206            }
207
208            /// Find the first child element with the given name.
209            /// # Example
210            /// ```rust
211            /// # use nxml_rs::*;
212            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity><Child>\"hello\"</Child></Entity>);")]
213            ///
214            /// assert_eq!(element.child("Child").unwrap().text_content, "hello");
215            /// ```
216            pub fn child(&self, name: &str) -> Option<&Self> {
217                self.children.iter().find(|c| c.name == name)
218            }
219
220            /// Find the first child element with the given name, mutable version.
221            /// # Example
222            /// ```rust
223            /// # use nxml_rs::*;
224            #[doc = concat!("let mut element = ", stringify!($macro),"!(<Entity><Child/></Entity>);")]
225            ///
226            /// element.child_mut("Child").unwrap().text_content = "world".into();
227            ///
228            /// assert_eq!(element.child("Child").unwrap().text_content, "world");
229            pub fn child_mut(&mut self, name: &str) -> Option<&mut Self> {
230                self.children.iter_mut().find(|c| c.name == name)
231            }
232
233            /// Iterate over all child elements with the given name.
234            /// # Example
235            /// ```rust
236            /// # use nxml_rs::*;
237            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity><Child/><Other/><Child/></Entity>);")]
238            ///
239            /// assert_eq!(element.children("Child").count(), 2);
240            /// ```
241            pub fn children<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Self> + 'a {
242                self.children.iter().filter(move |c| c.name == name)
243            }
244
245            /// Iterate over all child elements with the given name, mutable version.
246            /// # Example
247            /// ```rust
248            /// # use nxml_rs::*;
249            #[doc = concat!("let mut element = ", stringify!($macro),"!(<Entity><Child/><Other/><Child/></Entity>);")]
250            ///
251            /// for child in element.children_mut("Child") {
252            ///    child.text_content = "text".into();
253            /// }
254            ///
255            /// assert_eq!(element.to_string(), "<Entity><Child>text</Child><Other/><Child>text</Child></Entity>");
256            /// ```
257            pub fn children_mut<'a>(
258                &'a mut self,
259                name: &'a str,
260            ) -> impl Iterator<Item = &'a mut Self> + 'a {
261                self.children.iter_mut().filter(move |c| c.name == name)
262            }
263
264            /// A shorthand for setting an attribute value.
265            /// # Example
266            /// ```rust
267            /// # use nxml_rs::*;
268            #[doc = concat!("let mut element = ", stringify!($macro),"!(<Entity />);")]
269            ///
270            /// element.set_attr("key", "value");
271            ///
272            /// assert_eq!(element.to_string(), "<Entity key=\"value\"/>");
273            pub fn set_attr(&mut self, key: $attr_str, value: $attr_str) {
274                self.attributes.insert(key$(.$text_transform())?, value$(.$text_transform())?);
275            }
276
277            /// A shorthand for removing an attribute value.
278            /// # Example
279            /// ```rust
280            /// # use nxml_rs::*;
281            #[doc = concat!("let mut element = ", stringify!($macro),"!(<Entity key=\"value\" other=\"other\" />);")]
282            ///
283            /// element.remove_attr("key");
284            ///
285            /// assert_eq!(element.to_string(), "<Entity other=\"other\"/>");
286            pub fn remove_attr(&mut self, key: &str) -> Option<$attr_str_owned> {
287                #[cfg(feature = "indexmap")]
288                return self.attributes.shift_remove(key);
289
290                #[cfg(not(feature = "indexmap"))]
291                return self.attributes.remove(key);
292            }
293
294            /// Chained version of [`set_attr`](#method.set_attr).
295            /// # Example
296            /// ```rust
297            /// # use nxml_rs::*;
298            #[doc = concat!("let element = ", stringify!($tpe), "::new(\"Entity\")")]
299            ///     .with_attr("key", "value");
300            ///
301            /// assert_eq!(element.to_string(), "<Entity key=\"value\"/>");
302            /// ```
303            pub fn with_attr(mut self, key: $attr_str, value: $attr_str) -> Self {
304                self.set_attr(key, value);
305                self
306            }
307
308            /// Chained shorthand for setting the text content.
309            /// # Example
310            /// ```rust
311            /// # use nxml_rs::*;
312            #[doc = concat!("let element = ", stringify!($tpe), "::new(\"Entity\")")]
313            ///     .with_text("hello");
314            ///
315            /// assert_eq!(element.to_string(), "<Entity>hello</Entity>");
316            /// ```
317            pub fn with_text(mut self, text: $text_str) -> Self {
318                self.text_content = text$(.$text_transform())?;
319                self
320            }
321
322            /// Chained shorthand for adding a child element.
323            /// # Example
324            /// ```rust
325            /// # use nxml_rs::*;
326            #[doc = concat!("let element = ", stringify!($tpe), "::new(\"Entity\")")]
327            #[doc = concat!("     .with_child(", stringify!($tpe), "::new(\"Child\"));")]
328            ///
329            /// assert_eq!(element.to_string(), "<Entity><Child/></Entity>");
330            /// ```
331            pub fn with_child(mut self, element: Self) -> Self {
332                self.children.push(element);
333                self
334            }
335
336            /// A customizable [`Display`] impl that pretty-prints the element.
337            /// # Example
338            /// ```rust
339            /// # use nxml_rs::*;
340            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity><Child/></Entity>);")]
341            ///
342            /// assert_eq!(element.display().indent_width(0).to_string(), "<Entity>\n<Child/>\n</Entity>");
343            /// ```
344            pub fn display(&self) -> PrettyDisplay<'_, Self> {
345                PrettyDisplay {
346                    element: self,
347                    indent_width: 4,
348                    line_separator: "\n",
349                    autoclose: true,
350                }
351            }
352        }
353
354        impl<$($src,)? 'e> Div<&str> for &'e $tpe$(<$src>)? {
355            type Output = Self;
356
357            /// A chainable child element accessor
358            /// # Example
359            /// ```rust
360            /// # use nxml_rs::*;
361            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity><Child><Grandchild>\"hello\"</Grandchild></Child></Entity>);")]
362            ///
363            /// assert_eq!(&element / "Child" / "Grandchild" % Text, "hello");
364            /// ```
365            fn div(self, rhs: &str) -> Self::Output {
366                match self.child(rhs) {
367                    Some(child) => child,
368                    None => panic!("child element '{rhs}' not found"),
369                }
370            }
371        }
372
373        impl<$($src,)? 'e> Div<&str> for &'e mut $tpe$(<$src>)? {
374            type Output = Self;
375
376            /// A mutable version of the child accessor.
377            /// # Example
378            /// ```rust
379            /// # use nxml_rs::*;
380            #[doc = concat!("let mut element = ", stringify!($macro),"!(<Entity><Child><Grandchild>hello</Grandchild></Child></Entity>);")]
381            ///
382            /// (&mut element / "Child").children.clear();
383            ///
384            /// assert_eq!(element.to_string(), "<Entity><Child/></Entity>");
385            fn div(self, rhs: &str) -> Self::Output {
386                match self.child_mut(rhs) {
387                    Some(child) => child,
388                    None => panic!("child element '{rhs}' not found"),
389                }
390            }
391        }
392
393        impl<$($src,)? 'e> Rem<&str> for &'e $tpe$(<$src>)? {
394            type Output = &'e str;
395
396            /// A shorthand for getting an attribute value.
397            /// Not index because  `&element / "child" % "key"` is better
398            /// than `&(&element / "child")["key"]`.
399            /// # Example
400            /// ```rust
401            /// # use nxml_rs::*;
402            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity key=\"value\"/>);")]
403            ///
404            /// assert_eq!(&element % "key", "value");
405            fn rem(self, rhs: &str) -> Self::Output {
406                match self.attr(rhs) {
407                    Some(attr) => attr,
408                    None => panic!("attribute '{rhs}' not found"),
409                }
410            }
411        }
412
413        impl<$($src,)? 'e> Rem<Text> for &'e $tpe$(<$src>)? {
414            type Output = &'e str;
415
416            /// A shorthand for getting the text content.
417            /// # Example
418            /// ```rust
419            /// # use nxml_rs::*;
420            #[doc = concat!("let element = ", stringify!($macro),"!(<Entity>\"hello\"</Entity>);")]
421            ///
422            /// assert_eq!(&element % Text, "hello");
423            /// ```
424            fn rem(self, _: Text) -> Self::Output {
425                &self.text_content
426            }
427        }
428
429        impl<$($src)?> Display for $tpe$(<$src>)? {
430            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
431                self.display().compact().fmt(f)
432            }
433        }
434    };
435}
436
437dsl_impl! {
438    #[nxml_ref]
439    impl ElementRef<'s> {
440        attr(&'s str) -> &'s str,
441        text(&'s str).into(),
442    }
443}
444
445#[cfg(feature = "compact_str")]
446dsl_impl! {
447    #[nxml]
448    impl Element {
449        attr(impl ToCompactString) -> CompactString,
450        text(impl ToCompactString).to_compact_string(),
451    }
452}
453
454#[cfg(not(feature = "compact_str"))]
455dsl_impl! {
456    #[nxml]
457    impl Element {
458        attr(impl ToString) -> String,
459        text(impl ToString).to_string(),
460    }
461}
462
463// Instead of duplicating the Display impl, lets abstract over accessors in 3x
464// the code xd
465// But the algorith is not duplicated, so discrepancies are not possible
466trait ElementAccessor: Sized {
467    fn name(&self) -> &str;
468    fn attributes(&self) -> impl Iterator<Item = (&str, &str)>;
469    fn children(&self) -> &[Self];
470    fn text_content(&self) -> &str;
471}
472
473impl ElementAccessor for ElementRef<'_> {
474    fn name(&self) -> &str {
475        self.name
476    }
477    fn attributes(&self) -> impl Iterator<Item = (&str, &str)> {
478        self.attributes.iter().map(|(k, v)| (*k, *v))
479    }
480    fn children(&self) -> &[Self] {
481        &self.children
482    }
483    fn text_content(&self) -> &str {
484        &self.text_content
485    }
486}
487
488impl ElementAccessor for Element {
489    fn name(&self) -> &str {
490        &self.name
491    }
492    fn attributes(&self) -> impl Iterator<Item = (&str, &str)> {
493        self.attributes
494            .iter()
495            .map(|(k, v)| (k.as_str(), v.as_str()))
496    }
497    fn children(&self) -> &[Self] {
498        &self.children
499    }
500    fn text_content(&self) -> &str {
501        &self.text_content
502    }
503}
504
505/// A pretty-printer for XML elements.
506#[derive(Debug)]
507pub struct PrettyDisplay<'a, E> {
508    element: &'a E,
509    indent_width: usize,
510    line_separator: &'a str,
511    autoclose: bool,
512}
513
514impl<'a, E> PrettyDisplay<'a, E> {
515    /// Set the indentation width.
516    pub fn indent_width(mut self, indent_width: usize) -> Self {
517        self.indent_width = indent_width;
518        self
519    }
520
521    /// Set the line separator. Usually it's either `"\n"` or `""`.
522    pub fn line_separator(mut self, line_separator: &'a str) -> Self {
523        self.line_separator = line_separator;
524        self
525    }
526
527    /// A shorthand for `.line_separator("").indent_width(0)`.
528    pub fn compact(mut self) -> Self {
529        self.line_separator = "";
530        self.indent_width = 0;
531        self
532    }
533
534    /// Disable the `/>` syntax.
535    /// # Example
536    /// ```rust
537    /// # use nxml_rs::*;
538    /// let element = nxml!(<Entity><Child/></Entity>);
539    ///
540    /// assert_eq!(element.display().compact().no_autoclose().to_string(), "<Entity><Child></Child></Entity>");
541    pub fn no_autoclose(mut self) -> Self {
542        self.autoclose = false;
543        self
544    }
545
546    fn write(&self, w: &mut fmt::Formatter, element: &E, indent: usize) -> fmt::Result
547    where
548        E: ElementAccessor,
549    {
550        write!(w, "{:indent$}<{}", "", element.name())?;
551
552        for (key, value) in element.attributes() {
553            write!(w, " {key}=\"{value}\"")?;
554        }
555
556        let text_content = element.text_content();
557        if element.children().is_empty() && text_content.is_empty() {
558            if self.autoclose {
559                write!(w, "/>")?;
560            } else {
561                write!(w, "></{}>", element.name())?;
562            }
563            return Ok(());
564        }
565
566        write!(w, ">{}", self.line_separator)?;
567
568        if !text_content.is_empty() {
569            let indent = indent + self.indent_width;
570            write!(w, "{:indent$}{text_content}{}", "", self.line_separator)?;
571        }
572
573        for child in element.children() {
574            self.write(w, child, indent + self.indent_width)?;
575            write!(w, "{}", self.line_separator)?;
576        }
577
578        write!(w, "{:indent$}</{}>", "", element.name())
579    }
580}
581
582impl<'a, E: ElementAccessor> Display for PrettyDisplay<'a, E> {
583    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584        self.write(f, self.element, 0)
585    }
586}