Skip to main content

rusty_xml_reader/
lib.rs

1//! `xmlTextReader` pull parser. M2 walks a tree built by the UTF-8 parser.
2
3#![forbid(unsafe_code)]
4
5use rusty_xml_parser::{xml_read_memory, XmlError};
6use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
7
8/// libxml2 `xmlReaderTypes`.
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10#[repr(i32)]
11pub enum ReaderType {
12    None = 0,
13    Element = 1,
14    Attribute = 2,
15    Text = 3,
16    CData = 4,
17    EntityReference = 5,
18    Entity = 6,
19    ProcessingInstruction = 7,
20    Comment = 8,
21    Document = 9,
22    DocumentType = 10,
23    DocumentFragment = 11,
24    Notation = 12,
25    Whitespace = 13,
26    SignificantWhitespace = 14,
27    EndElement = 15,
28    EndEntity = 16,
29    XmlDeclaration = 17,
30}
31
32#[derive(Clone, Copy)]
33struct Cursor {
34    id: NodeId,
35    end: bool,
36}
37
38/// `xmlTextReader`.
39pub struct XmlTextReader {
40    doc: XmlDoc,
41    cur: Option<Cursor>,
42    started: bool,
43    eof: bool,
44    attr: Option<NodeId>,
45}
46
47impl XmlTextReader {
48    fn kind_of(doc: &XmlDoc, id: NodeId, end: bool) -> ReaderType {
49        if end {
50            return ReaderType::EndElement;
51        }
52        match doc.kind(id) {
53            NodeKind::Element => ReaderType::Element,
54            NodeKind::Attribute => ReaderType::Attribute,
55            NodeKind::Text => {
56                if doc.xml_is_blank_node(id) {
57                    ReaderType::SignificantWhitespace
58                } else {
59                    ReaderType::Text
60                }
61            }
62            NodeKind::CData => ReaderType::CData,
63            NodeKind::Pi => ReaderType::ProcessingInstruction,
64            NodeKind::Comment => ReaderType::Comment,
65            NodeKind::Document => ReaderType::Document,
66            _ => ReaderType::None,
67        }
68    }
69
70    /// `xmlReaderForMemory`.
71    #[doc(alias = "xmlReaderForMemory")]
72    pub fn xml_reader_for_memory(
73        buffer: &[u8],
74        url: Option<&str>,
75        encoding: Option<&str>,
76        options: i32,
77    ) -> Result<Self, XmlError> {
78        let doc = xml_read_memory(buffer, url, encoding, options)?;
79        Ok(Self {
80            doc,
81            cur: None,
82            started: false,
83            eof: false,
84            attr: None,
85        })
86    }
87
88    /// `xmlReaderWalker`.
89    #[doc(alias = "xmlReaderWalker")]
90    pub fn xml_reader_walker(doc: XmlDoc) -> Self {
91        Self {
92            doc,
93            cur: None,
94            started: false,
95            eof: false,
96            attr: None,
97        }
98    }
99
100    /// `xmlTextReaderRead`. Returns 1 (node), 0 (eof), matching C.
101    #[doc(alias = "xmlTextReaderRead")]
102    pub fn read(&mut self) -> i32 {
103        self.attr = None;
104        if self.eof {
105            return 0;
106        }
107        if !self.started {
108            self.started = true;
109            if let Some(id) = self.doc.first_child(NodeId::DOCUMENT) {
110                self.cur = Some(Cursor { id, end: false });
111                return 1;
112            }
113            self.eof = true;
114            return 0;
115        }
116        let Cursor { id, end } = match self.cur {
117            Some(c) => c,
118            None => {
119                self.eof = true;
120                return 0;
121            }
122        };
123        if !end && self.doc.kind(id) == NodeKind::Element {
124            if let Some(ch) = self.doc.first_child(id) {
125                self.cur = Some(Cursor { id: ch, end: false });
126                return 1;
127            }
128            // empty element: no EndElement
129            return self.advance_after(id);
130        }
131        self.advance_after(id)
132    }
133
134    fn advance_after(&mut self, id: NodeId) -> i32 {
135        if let Some(n) = self.doc.next_sibling(id) {
136            self.cur = Some(Cursor { id: n, end: false });
137            return 1;
138        }
139        match self.doc.parent(id) {
140            Some(p) if p != NodeId::DOCUMENT && self.doc.kind(p) == NodeKind::Element => {
141                self.cur = Some(Cursor { id: p, end: true });
142                1
143            }
144            _ => {
145                self.eof = true;
146                self.cur = None;
147                0
148            }
149        }
150    }
151
152    pub fn node_type(&self) -> ReaderType {
153        match self.cur {
154            Some(Cursor { id, end }) => Self::kind_of(&self.doc, id, end),
155            None => ReaderType::None,
156        }
157    }
158
159    pub fn depth(&self) -> i32 {
160        let mut d = 0;
161        if let Some(Cursor { mut id, .. }) = self.cur {
162            while let Some(p) = self.doc.parent(id) {
163                if p == NodeId::DOCUMENT {
164                    break;
165                }
166                d += 1;
167                id = p;
168            }
169        }
170        d
171    }
172
173    pub fn is_empty_element(&self) -> bool {
174        match self.cur {
175            Some(Cursor { id, end: false }) if self.doc.kind(id) == NodeKind::Element => {
176                self.doc.first_child(id).is_none()
177            }
178            _ => false,
179        }
180    }
181
182    pub fn local_name(&self) -> Option<&str> {
183        self.cur.map(|c| self.doc.name(c.id))
184    }
185
186    pub fn prefix(&self) -> Option<&str> {
187        self.cur.and_then(|c| self.doc.prefix(c.id))
188    }
189
190    pub fn namespace_uri(&self) -> Option<&str> {
191        self.cur.and_then(|c| self.doc.ns_uri(c.id))
192    }
193
194    pub fn value(&self) -> Option<&str> {
195        if let Some(a) = self.attr {
196            return Some(self.doc.content(a));
197        }
198        match self.cur {
199            Some(Cursor { id, end: false }) => match self.doc.kind(id) {
200                NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi => {
201                    Some(self.doc.content(id))
202                }
203                _ => None,
204            },
205            _ => None,
206        }
207    }
208
209    pub fn name(&self) -> Option<String> {
210        self.cur.map(|c| self.doc.qname(c.id))
211    }
212
213    pub fn attribute_count(&self) -> i32 {
214        match self.cur {
215            Some(Cursor { id, end: false }) if self.doc.kind(id) == NodeKind::Element => {
216                self.doc.attrs(id).count() as i32
217            }
218            _ => 0,
219        }
220    }
221
222    pub fn has_attributes(&self) -> bool {
223        self.attribute_count() > 0
224    }
225
226    /// `xmlTextReaderGetAttribute`.
227    #[doc(alias = "xmlTextReaderGetAttribute")]
228    pub fn get_attribute(&self, name: &str) -> Option<String> {
229        let Cursor { id, end } = self.cur?;
230        if end {
231            return None;
232        }
233        self.doc.xml_get_prop(id, name)
234    }
235
236    /// `xmlTextReaderMoveToFirstAttribute`.
237    #[doc(alias = "xmlTextReaderMoveToFirstAttribute")]
238    pub fn move_to_first_attribute(&mut self) -> i32 {
239        let Cursor { id, end } = match self.cur {
240            Some(c) => c,
241            None => return 0,
242        };
243        if end {
244            return 0;
245        }
246        match self.doc.first_attr(id) {
247            Some(a) => {
248                self.attr = Some(a);
249                1
250            }
251            None => 0,
252        }
253    }
254
255    /// `xmlTextReaderMoveToNextAttribute`.
256    #[doc(alias = "xmlTextReaderMoveToNextAttribute")]
257    pub fn move_to_next_attribute(&mut self) -> i32 {
258        match self.attr {
259            Some(a) => match self.doc.next_sibling(a) {
260                Some(n) => {
261                    self.attr = Some(n);
262                    1
263                }
264                None => 0,
265            },
266            None => self.move_to_first_attribute(),
267        }
268    }
269
270    /// `xmlTextReaderMoveToElement`.
271    #[doc(alias = "xmlTextReaderMoveToElement")]
272    pub fn move_to_element(&mut self) -> i32 {
273        if self.attr.is_some() {
274            self.attr = None;
275            1
276        } else {
277            0
278        }
279    }
280
281    pub fn doc(&self) -> &XmlDoc {
282        &self.doc
283    }
284}
285
286/// `xmlReaderForMemory`.
287#[doc(alias = "xmlReaderForMemory")]
288pub fn xml_reader_for_memory(
289    buffer: &[u8],
290    url: Option<&str>,
291    encoding: Option<&str>,
292    options: i32,
293) -> Result<XmlTextReader, XmlError> {
294    XmlTextReader::xml_reader_for_memory(buffer, url, encoding, options)
295}
296
297/// `xmlReaderForDoc`.
298#[doc(alias = "xmlReaderForDoc")]
299pub fn xml_reader_for_doc(
300    cur: &str,
301    url: Option<&str>,
302    encoding: Option<&str>,
303    options: i32,
304) -> Result<XmlTextReader, XmlError> {
305    xml_reader_for_memory(cur.as_bytes(), url, encoding, options)
306}