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}