edit_xml/document/
node.rs

1use std::{borrow::Cow, fmt::Debug};
2#[cfg(feature = "document-breakdown")]
3mod breakdown;
4#[cfg(feature = "document-breakdown")]
5pub use breakdown::*;
6use tracing::instrument;
7
8use crate::{element::ElementDebug, Document, Element};
9
10/// Represents an XML node.
11#[derive(Debug, Clone, PartialEq)]
12pub enum Node {
13    /// XML Element
14    Element(Element),
15    /// XML Character Data ([specification](https://www.w3.org/TR/xml/#syntax))
16    Text(String),
17    /// Comments ([specification](https://www.w3.org/TR/xml/#sec-comments))
18    Comment(String),
19    /// CDATA ([specification](https://www.w3.org/TR/xml/#sec-cdata-sect))
20    CData(String),
21    /// Processing Instruction ([specification](https://www.w3.org/TR/xml/#sec-pi))
22    PI(String),
23    /// Document Type Declaration ([specification](https://www.w3.org/TR/xml/#sec-prolog-dtd))
24    DocType(String),
25}
26impl From<Element> for Node {
27    fn from(elem: Element) -> Self {
28        Node::Element(elem)
29    }
30}
31impl From<&Element> for Node {
32    fn from(elem: &Element) -> Self {
33        Node::Element(*elem)
34    }
35}
36macro_rules! enum_is {
37    [
38        $(
39            $(#[$docs:meta])*
40            $fn_name:ident => $name:ident
41        ),*
42     ] => {
43        $(
44            $(#[$docs])*
45            pub fn $fn_name(&self) -> bool {
46                matches!(self, Node::$name(_))
47            }
48        )*
49    };
50}
51impl Node {
52    /// Useful to use inside `filter_map`.
53    ///
54    /// ```
55    /// use edit_xml::{Document, Element};
56    ///
57    /// let mut doc = Document::parse_str(r#"<?xml version="1.0" encoding="UTF-8"?>
58    /// <config>
59    ///     Random Text
60    ///     <max>1</max>
61    /// </config>
62    /// "#).unwrap();
63    ///
64    /// let elems: Vec<Element> = doc
65    ///     .root_element()
66    ///     .unwrap()
67    ///     .children(&doc)
68    ///     .iter()
69    ///     .filter_map(|n| n.as_element())
70    ///     .collect();
71    /// ```
72    pub fn as_element(&self) -> Option<Element> {
73        match self {
74            Self::Element(elem) => Some(*elem),
75            _ => None,
76        }
77    }
78    #[instrument]
79    pub(crate) fn build_text_content<'a>(&self, doc: &'a Document, buf: &'a mut String) {
80        match self {
81            Node::Element(elem) => elem.build_text_content(doc, buf),
82            Node::Text(text) => buf.push_str(text),
83            Node::CData(text) => buf.push_str(text),
84            Node::PI(text) => buf.push_str(text),
85            _ => {}
86        }
87    }
88
89    /// Returns content if node is `Text`, `CData`, or `PI`.
90    /// If node is `Element`, return [Element::text_content()]
91    ///
92    /// Implementation of [Node.textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
93    pub fn text_content(&self, doc: &Document) -> String {
94        let mut buf = String::new();
95        self.build_text_content(doc, &mut buf);
96        buf
97    }
98    /// Returns content if node is `Text`, `CData`, or `PI`.
99    ///
100    /// If node is `Element` Cow will be owned.
101    /// Otherwise, Cow will be borrowed.
102    ///
103    /// If None is returned it is a comment or doctype
104    pub fn possible_borrowed_text(&self) -> Option<Cow<'_, str>> {
105        match self {
106            Node::Text(text) => Some(Cow::Borrowed(text)),
107            Node::CData(text) => Some(Cow::Borrowed(text)),
108            Node::PI(text) => Some(Cow::Borrowed(text)),
109            Node::Element(element) => Some(Cow::Owned(element.text_content(&Document::new()))),
110            _ => None,
111        }
112    }
113    /// Debug the node
114    pub fn debug<'node, 'doc>(&'node self, doc: &'doc Document) -> NodeDebug<'node, 'doc> {
115        NodeDebug::new(self, doc)
116    }
117    enum_is![
118        /// Returns true if the node is a text node
119        /// ```
120        /// use edit_xml::Node;
121        /// let node = Node::Text("Hello".to_string());
122        /// assert!(node.is_text());
123        /// ```
124        is_text => Text,
125        /// Returns true if the node is a comment node
126        /// ```
127        /// use edit_xml::Node;
128        /// let node = Node::Comment("Hello".to_string());
129        /// assert!(node.is_comment());
130        /// ```
131        is_comment => Comment,
132        /// Returns true if the node is a CDATA node
133        /// ```
134        /// use edit_xml::Node;
135        /// let node = Node::CData("Hello".to_string());
136        /// assert!(node.is_cdata());
137        /// ```
138        is_cdata => CData,
139        /// Returns true if the node is a Processing Instruction node
140        /// ```
141        /// use edit_xml::Node;
142        /// let node = Node::PI("Hello".to_string());
143        /// assert!(node.is_pi());
144        /// ```
145        is_pi => PI,
146        /// Returns true if the node is a Document Type Declaration
147        /// ```
148        /// use edit_xml::Node;
149        /// let node = Node::DocType("Hello".to_string());
150        /// assert!(node.is_doctype());
151        /// ```
152        is_doctype => DocType,
153        /// Returns true if the node is an element
154        is_element => Element
155    ];
156}
157pub enum NodeDebug<'node, 'doc> {
158    /// Uses ElementDebug to debug the element
159    Element(ElementDebug<'node, 'doc>),
160    /// Text node
161    Text(&'node str),
162    /// Comment node
163    Comment(&'node str),
164    /// CDATA node
165    CData(&'node str),
166    /// Processing Instruction node
167    PI(&'node str),
168    /// Document Type Declaration node
169    DocType(&'node str),
170}
171impl<'node, 'doc> NodeDebug<'node, 'doc> {
172    pub fn new(node: &'node Node, doc: &'doc Document) -> Self {
173        match node {
174            Node::Element(elem) => NodeDebug::Element(ElementDebug::new(elem, doc)),
175            Node::Text(text) => NodeDebug::Text(text),
176            Node::Comment(text) => NodeDebug::Comment(text),
177            Node::CData(text) => NodeDebug::CData(text),
178            Node::PI(text) => NodeDebug::PI(text),
179            Node::DocType(text) => NodeDebug::DocType(text),
180        }
181    }
182}
183
184impl Debug for NodeDebug<'_, '_> {
185    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
186        match self {
187            NodeDebug::Element(element) => element.fmt(f),
188            NodeDebug::Text(text_content) => Debug::fmt(text_content, f),
189            NodeDebug::Comment(f0) => f.debug_tuple("Comment").field(&f0).finish(),
190            NodeDebug::CData(f0) => f.debug_tuple("CData").field(&f0).finish(),
191            NodeDebug::PI(f0) => f.debug_tuple("PI").field(&f0).finish(),
192            NodeDebug::DocType(f0) => f.debug_tuple("DocType").field(&f0).finish(),
193        }
194    }
195}