1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Utilities which may eventually move upstream to the `rxml` crate.
8
9use std::borrow::Cow;
10
11use rxml::{parser::EventMetrics, AttrMap, Event, Namespace, NcName, NcNameStr, XmlVersion};
12
13/// An encodable item.
14///
15/// Unlike [`rxml::Item`], the contents of this item may either be owned or
16/// borrowed, individually. This enables the use in an [`crate::AsXml`] trait
17/// even if data needs to be generated during serialisation.
18#[derive(Debug)]
19pub enum Item<'x> {
20 /// XML declaration
21 XmlDeclaration(XmlVersion),
22
23 /// Start of an element header
24 ElementHeadStart(
25 /// Namespace name
26 Namespace,
27 /// Local name of the attribute
28 Cow<'x, NcNameStr>,
29 ),
30
31 /// An attribute key/value pair
32 Attribute(
33 /// Namespace name
34 Namespace,
35 /// Local name of the attribute
36 Cow<'x, NcNameStr>,
37 /// Value of the attribute
38 Cow<'x, str>,
39 ),
40
41 /// End of an element header
42 ElementHeadEnd,
43
44 /// A piece of text (in element content, not attributes)
45 Text(Cow<'x, str>),
46
47 /// Footer of an element
48 ///
49 /// This can be used either in places where [`Text`] could be used to
50 /// close the most recently opened unclosed element, or it can be used
51 /// instead of [`ElementHeadEnd`] to close the element using `/>`, without
52 /// any child content.
53 ///
54 /// [`Text`]: Self::Text
55 /// [`ElementHeadEnd`]: Self::ElementHeadEnd
56 ElementFoot,
57}
58
59impl Item<'_> {
60 /// Exchange all borrowed pieces inside this item for owned items, cloning
61 /// them if necessary.
62 pub fn into_owned(self) -> Item<'static> {
63 match self {
64 Self::XmlDeclaration(v) => Item::XmlDeclaration(v),
65 Self::ElementHeadStart(ns, name) => {
66 Item::ElementHeadStart(ns, Cow::Owned(name.into_owned()))
67 }
68 Self::Attribute(ns, name, value) => Item::Attribute(
69 ns,
70 Cow::Owned(name.into_owned()),
71 Cow::Owned(value.into_owned()),
72 ),
73 Self::ElementHeadEnd => Item::ElementHeadEnd,
74 Self::Text(value) => Item::Text(Cow::Owned(value.into_owned())),
75 Self::ElementFoot => Item::ElementFoot,
76 }
77 }
78
79 /// Return an [`rxml::Item`], which borrows data from this item.
80 pub fn as_rxml_item(&self) -> rxml::Item<'_> {
81 match self {
82 Self::XmlDeclaration(ref v) => rxml::Item::XmlDeclaration(*v),
83 Self::ElementHeadStart(ref ns, ref name) => rxml::Item::ElementHeadStart(ns, name),
84 Self::Attribute(ref ns, ref name, ref value) => rxml::Item::Attribute(ns, name, value),
85 Self::ElementHeadEnd => rxml::Item::ElementHeadEnd,
86 Self::Text(ref value) => rxml::Item::Text(value),
87 Self::ElementFoot => rxml::Item::ElementFoot,
88 }
89 }
90}
91
92/// Iterator adapter which converts an iterator over [`Event`][`rxml::Event`]
93/// to an iterator over [`Item<'static>`][`Item`].
94///
95/// This iterator consumes the events and returns items which contain the data
96/// in an owned fashion.
97#[cfg(feature = "minidom")]
98pub(crate) struct EventToItem<I> {
99 inner: I,
100 attributes: Option<rxml::xml_map::IntoIter<String>>,
101}
102
103#[cfg(feature = "minidom")]
104impl<I> EventToItem<I> {
105 pub(crate) fn new(inner: I) -> Self {
106 Self {
107 inner,
108 attributes: None,
109 }
110 }
111
112 fn drain(&mut self) -> Option<Item<'static>> {
113 match self.attributes {
114 Some(ref mut attrs) => {
115 if let Some(((ns, name), value)) = attrs.next() {
116 Some(Item::Attribute(ns, Cow::Owned(name), Cow::Owned(value)))
117 } else {
118 self.attributes = None;
119 Some(Item::ElementHeadEnd)
120 }
121 }
122 None => None,
123 }
124 }
125
126 fn update(&mut self, ev: Event) -> Item<'static> {
127 assert!(self.attributes.is_none());
128 match ev {
129 Event::XmlDeclaration(_, v) => Item::XmlDeclaration(v),
130 Event::StartElement(_, (ns, name), attrs) => {
131 self.attributes = Some(attrs.into_iter());
132 Item::ElementHeadStart(ns, Cow::Owned(name))
133 }
134 Event::Text(_, value) => Item::Text(Cow::Owned(value)),
135 Event::EndElement(_) => Item::ElementFoot,
136 }
137 }
138}
139
140#[cfg(feature = "minidom")]
141impl<I: Iterator<Item = Result<Event, crate::error::Error>>> Iterator for EventToItem<I> {
142 type Item = Result<Item<'static>, crate::error::Error>;
143
144 fn next(&mut self) -> Option<Self::Item> {
145 if let Some(item) = self.drain() {
146 return Some(Ok(item));
147 }
148 let next = match self.inner.next() {
149 Some(Ok(v)) => v,
150 Some(Err(e)) => return Some(Err(e)),
151 None => return None,
152 };
153 Some(Ok(self.update(next)))
154 }
155
156 fn size_hint(&self) -> (usize, Option<usize>) {
157 // we may create an indefinte amount of items for a single event,
158 // so we cannot provide a reasonable upper bound.
159 (self.inner.size_hint().0, None)
160 }
161}
162
163/// Iterator adapter which converts an iterator over [`Item`] to
164/// an iterator over [`Event`][`crate::Event`].
165///
166/// As `Event` does not support borrowing data, this iterator copies the data
167/// from the items on the fly.
168pub(crate) struct ItemToEvent<I> {
169 inner: I,
170 event_buffer: Option<Event>,
171 elem_buffer: Option<(Namespace, NcName, AttrMap)>,
172}
173
174impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> ItemToEvent<I> {
175 /// Create a new adapter with `inner` as the source iterator.
176 pub(crate) fn new(inner: I) -> Self {
177 Self {
178 inner,
179 event_buffer: None,
180 elem_buffer: None,
181 }
182 }
183}
184
185impl<I> ItemToEvent<I> {
186 fn update(&mut self, item: Item<'_>) -> Result<Option<Event>, crate::error::Error> {
187 assert!(self.event_buffer.is_none());
188 match item {
189 Item::XmlDeclaration(v) => {
190 assert!(self.elem_buffer.is_none());
191 Ok(Some(Event::XmlDeclaration(EventMetrics::zero(), v)))
192 }
193 Item::ElementHeadStart(ns, name) => {
194 if self.elem_buffer.is_some() {
195 // this is only used with AsXml implementations, so
196 // triggering this is always a coding failure instead of a
197 // runtime error.
198 panic!("got a second ElementHeadStart items without ElementHeadEnd inbetween: ns={:?} name={:?} (state={:?})", ns, name, self.elem_buffer);
199 }
200 self.elem_buffer = Some((ns.to_owned(), name.into_owned(), AttrMap::new()));
201 Ok(None)
202 }
203 Item::Attribute(ns, name, value) => {
204 let Some((_, _, attrs)) = self.elem_buffer.as_mut() else {
205 // this is only used with AsXml implementations, so
206 // triggering this is always a coding failure instead of a
207 // runtime error.
208 panic!(
209 "got a second Attribute item without ElementHeadStart: ns={:?}, name={:?}",
210 ns, name
211 );
212 };
213 attrs.insert(ns, name.into_owned(), value.into_owned());
214 Ok(None)
215 }
216 Item::ElementHeadEnd => {
217 let Some((ns, name, attrs)) = self.elem_buffer.take() else {
218 // this is only used with AsXml implementations, so
219 // triggering this is always a coding failure instead of a
220 // runtime error.
221 panic!(
222 "got ElementHeadEnd item without ElementHeadStart: {:?}",
223 item
224 );
225 };
226 Ok(Some(Event::StartElement(
227 EventMetrics::zero(),
228 (ns, name),
229 attrs,
230 )))
231 }
232 Item::Text(value) => {
233 if let Some(elem_buffer) = self.elem_buffer.as_ref() {
234 // this is only used with AsXml implementations, so
235 // triggering this is always a coding failure instead of a
236 // runtime error.
237 panic!("got Text after ElementHeadStart but before ElementHeadEnd: Text({:?}) (state = {:?})", value, elem_buffer);
238 }
239 Ok(Some(Event::Text(EventMetrics::zero(), value.into_owned())))
240 }
241 Item::ElementFoot => {
242 let end_ev = Event::EndElement(EventMetrics::zero());
243 let result = if let Some((ns, name, attrs)) = self.elem_buffer.take() {
244 // content-less element
245 self.event_buffer = Some(end_ev);
246 Event::StartElement(EventMetrics::zero(), (ns, name), attrs)
247 } else {
248 end_ev
249 };
250 Ok(Some(result))
251 }
252 }
253 }
254}
255
256impl<'x, I: Iterator<Item = Result<Item<'x>, crate::error::Error>>> Iterator for ItemToEvent<I> {
257 type Item = Result<Event, crate::error::Error>;
258
259 fn next(&mut self) -> Option<Self::Item> {
260 if let Some(event) = self.event_buffer.take() {
261 return Some(Ok(event));
262 }
263 loop {
264 let item = match self.inner.next() {
265 Some(Ok(v)) => v,
266 Some(Err(e)) => return Some(Err(e)),
267 None => return None,
268 };
269 if let Some(v) = self.update(item).transpose() {
270 return Some(v);
271 }
272 }
273 }
274
275 fn size_hint(&self) -> (usize, Option<usize>) {
276 // we may create an indefinte amount of items for a single event,
277 // so we cannot provide a reasonable upper bound.
278 (self.inner.size_hint().0, None)
279 }
280}
281
282#[cfg(all(test, feature = "minidom"))]
283mod tests_minidom {
284 use super::*;
285
286 fn events_to_items<I: Iterator<Item = Event>>(events: I) -> Vec<Item<'static>> {
287 let iter = EventToItem {
288 inner: events.map(|ev| Ok(ev)),
289 attributes: None,
290 };
291 let mut result = Vec::new();
292 for item in iter {
293 let item = item.unwrap();
294 result.push(item);
295 }
296 result
297 }
298
299 #[test]
300 fn event_to_item_xml_declaration() {
301 let events = vec![Event::XmlDeclaration(
302 EventMetrics::zero(),
303 XmlVersion::V1_0,
304 )];
305 let items = events_to_items(events.into_iter());
306 assert_eq!(items.len(), 1);
307 match items[0] {
308 Item::XmlDeclaration(XmlVersion::V1_0) => (),
309 ref other => panic!("unexected item in position 0: {:?}", other),
310 };
311 }
312
313 #[test]
314 fn event_to_item_empty_element() {
315 let events = vec![
316 Event::StartElement(
317 EventMetrics::zero(),
318 (Namespace::NONE, "elem".try_into().unwrap()),
319 AttrMap::new(),
320 ),
321 Event::EndElement(EventMetrics::zero()),
322 ];
323 let items = events_to_items(events.into_iter());
324 assert_eq!(items.len(), 3);
325 match items[0] {
326 Item::ElementHeadStart(ref ns, ref name) => {
327 assert_eq!(&**ns, Namespace::none());
328 assert_eq!(&**name, "elem");
329 }
330 ref other => panic!("unexected item in position 0: {:?}", other),
331 };
332 match items[1] {
333 Item::ElementHeadEnd => (),
334 ref other => panic!("unexected item in position 1: {:?}", other),
335 };
336 match items[2] {
337 Item::ElementFoot => (),
338 ref other => panic!("unexected item in position 2: {:?}", other),
339 };
340 }
341
342 #[test]
343 fn event_to_item_element_with_attributes() {
344 let mut attrs = AttrMap::new();
345 attrs.insert(
346 Namespace::NONE,
347 "attr".try_into().unwrap(),
348 "value".to_string(),
349 );
350 let events = vec![
351 Event::StartElement(
352 EventMetrics::zero(),
353 (Namespace::NONE, "elem".try_into().unwrap()),
354 attrs,
355 ),
356 Event::EndElement(EventMetrics::zero()),
357 ];
358 let items = events_to_items(events.into_iter());
359 assert_eq!(items.len(), 4);
360 match items[0] {
361 Item::ElementHeadStart(ref ns, ref name) => {
362 assert_eq!(&**ns, Namespace::none());
363 assert_eq!(&**name, "elem");
364 }
365 ref other => panic!("unexected item in position 0: {:?}", other),
366 };
367 match items[1] {
368 Item::Attribute(ref ns, ref name, ref value) => {
369 assert_eq!(&**ns, Namespace::none());
370 assert_eq!(&**name, "attr");
371 assert_eq!(&**value, "value");
372 }
373 ref other => panic!("unexected item in position 1: {:?}", other),
374 };
375 match items[2] {
376 Item::ElementHeadEnd => (),
377 ref other => panic!("unexected item in position 2: {:?}", other),
378 };
379 match items[3] {
380 Item::ElementFoot => (),
381 ref other => panic!("unexected item in position 3: {:?}", other),
382 };
383 }
384
385 #[test]
386 fn event_to_item_element_with_text() {
387 let events = vec![
388 Event::StartElement(
389 EventMetrics::zero(),
390 (Namespace::NONE, "elem".try_into().unwrap()),
391 AttrMap::new(),
392 ),
393 Event::Text(EventMetrics::zero(), "Hello World!".to_owned()),
394 Event::EndElement(EventMetrics::zero()),
395 ];
396 let items = events_to_items(events.into_iter());
397 assert_eq!(items.len(), 4);
398 match items[0] {
399 Item::ElementHeadStart(ref ns, ref name) => {
400 assert_eq!(&**ns, Namespace::none());
401 assert_eq!(&**name, "elem");
402 }
403 ref other => panic!("unexected item in position 0: {:?}", other),
404 };
405 match items[1] {
406 Item::ElementHeadEnd => (),
407 ref other => panic!("unexected item in position 1: {:?}", other),
408 };
409 match items[2] {
410 Item::Text(ref value) => {
411 assert_eq!(value, "Hello World!");
412 }
413 ref other => panic!("unexected item in position 2: {:?}", other),
414 };
415 match items[3] {
416 Item::ElementFoot => (),
417 ref other => panic!("unexected item in position 3: {:?}", other),
418 };
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 fn items_to_events<'x, I: IntoIterator<Item = Item<'x>>>(
427 items: I,
428 ) -> Result<Vec<Event>, crate::error::Error> {
429 let iter = ItemToEvent {
430 inner: items.into_iter().map(|x| Ok(x)),
431 event_buffer: None,
432 elem_buffer: None,
433 };
434 let mut result = Vec::new();
435 for ev in iter {
436 let ev = ev?;
437 result.push(ev);
438 }
439 Ok(result)
440 }
441
442 #[test]
443 fn item_to_event_xml_decl() {
444 let items = vec![Item::XmlDeclaration(XmlVersion::V1_0)];
445 let events = items_to_events(items).expect("item conversion");
446 assert_eq!(events.len(), 1);
447 match events[0] {
448 Event::XmlDeclaration(_, XmlVersion::V1_0) => (),
449 ref other => panic!("unexected event in position 0: {:?}", other),
450 };
451 }
452
453 #[test]
454 fn item_to_event_simple_empty_element() {
455 let items = vec![
456 Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
457 Item::ElementHeadEnd,
458 Item::ElementFoot,
459 ];
460 let events = items_to_events(items).expect("item conversion");
461 assert_eq!(events.len(), 2);
462 match events[0] {
463 Event::StartElement(_, (ref ns, ref name), ref attrs) => {
464 assert_eq!(attrs.len(), 0);
465 assert_eq!(ns, Namespace::none());
466 assert_eq!(name, "elem");
467 }
468 ref other => panic!("unexected event in position 0: {:?}", other),
469 };
470 match events[1] {
471 Event::EndElement(_) => (),
472 ref other => panic!("unexected event in position 1: {:?}", other),
473 };
474 }
475
476 #[test]
477 fn item_to_event_short_empty_element() {
478 let items = vec![
479 Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
480 Item::ElementFoot,
481 ];
482 let events = items_to_events(items).expect("item conversion");
483 assert_eq!(events.len(), 2);
484 match events[0] {
485 Event::StartElement(_, (ref ns, ref name), ref attrs) => {
486 assert_eq!(attrs.len(), 0);
487 assert_eq!(ns, Namespace::none());
488 assert_eq!(name, "elem");
489 }
490 ref other => panic!("unexected event in position 0: {:?}", other),
491 };
492 match events[1] {
493 Event::EndElement(_) => (),
494 ref other => panic!("unexected event in position 1: {:?}", other),
495 };
496 }
497
498 #[test]
499 fn item_to_event_element_with_text_content() {
500 let items = vec![
501 Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
502 Item::ElementHeadEnd,
503 Item::Text(Cow::Borrowed("Hello World!")),
504 Item::ElementFoot,
505 ];
506 let events = items_to_events(items).expect("item conversion");
507 assert_eq!(events.len(), 3);
508 match events[0] {
509 Event::StartElement(_, (ref ns, ref name), ref attrs) => {
510 assert_eq!(attrs.len(), 0);
511 assert_eq!(ns, Namespace::none());
512 assert_eq!(name, "elem");
513 }
514 ref other => panic!("unexected event in position 0: {:?}", other),
515 };
516 match events[1] {
517 Event::Text(_, ref value) => {
518 assert_eq!(value, "Hello World!");
519 }
520 ref other => panic!("unexected event in position 1: {:?}", other),
521 };
522 match events[2] {
523 Event::EndElement(_) => (),
524 ref other => panic!("unexected event in position 2: {:?}", other),
525 };
526 }
527
528 #[test]
529 fn item_to_event_element_with_attributes() {
530 let items = vec![
531 Item::ElementHeadStart(Namespace::NONE, Cow::Borrowed("elem".try_into().unwrap())),
532 Item::Attribute(
533 Namespace::NONE,
534 Cow::Borrowed("attr".try_into().unwrap()),
535 Cow::Borrowed("value"),
536 ),
537 Item::ElementHeadEnd,
538 Item::ElementFoot,
539 ];
540 let events = items_to_events(items).expect("item conversion");
541 assert_eq!(events.len(), 2);
542 match events[0] {
543 Event::StartElement(_, (ref ns, ref name), ref attrs) => {
544 assert_eq!(ns, Namespace::none());
545 assert_eq!(name, "elem");
546 assert_eq!(attrs.len(), 1);
547 assert_eq!(
548 attrs.get(Namespace::none(), "attr").map(|x| x.as_str()),
549 Some("value")
550 );
551 }
552 ref other => panic!("unexected event in position 0: {:?}", other),
553 };
554 match events[1] {
555 Event::EndElement(_) => (),
556 ref other => panic!("unexected event in position 2: {:?}", other),
557 };
558 }
559}