1use std::sync::OnceLock;
18
19use memchr::{memchr, memchr2, memchr3, memmem};
20
21fn xmlns_finder() -> &'static memmem::Finder<'static> {
22 static FINDER: OnceLock<memmem::Finder<'static>> = OnceLock::new();
23 FINDER.get_or_init(|| memmem::Finder::new(b"xmlns"))
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum Ns {
29 None,
31 Pml,
33 Dml,
35 Rel,
37 PkgRel,
39 ContentTypes,
41 Mce,
43 Cp,
45 Dc,
47 Dcterms,
49 Xsi,
51 Ep,
53 Vt,
55 Chart,
57 ChartEx,
59 Dgm,
61 Pic,
63 Xml,
65 P14,
67 P188,
69 Other(u16),
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum Conformance {
76 Transitional,
77 Strict,
78}
79
80const KNOWN: &[(&[u8], Ns, Conformance)] = &[
81 (
82 b"http://schemas.openxmlformats.org/presentationml/2006/main",
83 Ns::Pml,
84 Conformance::Transitional,
85 ),
86 (
87 b"http://purl.oclc.org/ooxml/presentationml/main",
88 Ns::Pml,
89 Conformance::Strict,
90 ),
91 (
92 b"http://schemas.openxmlformats.org/drawingml/2006/main",
93 Ns::Dml,
94 Conformance::Transitional,
95 ),
96 (
97 b"http://purl.oclc.org/ooxml/drawingml/main",
98 Ns::Dml,
99 Conformance::Strict,
100 ),
101 (
102 b"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
103 Ns::Rel,
104 Conformance::Transitional,
105 ),
106 (
107 b"http://purl.oclc.org/ooxml/officeDocument/relationships",
108 Ns::Rel,
109 Conformance::Strict,
110 ),
111 (
112 b"http://schemas.openxmlformats.org/package/2006/relationships",
113 Ns::PkgRel,
114 Conformance::Transitional,
115 ),
116 (
117 b"http://schemas.openxmlformats.org/package/2006/content-types",
118 Ns::ContentTypes,
119 Conformance::Transitional,
120 ),
121 (
122 b"http://schemas.openxmlformats.org/markup-compatibility/2006",
123 Ns::Mce,
124 Conformance::Transitional,
125 ),
126 (
127 b"http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
128 Ns::Cp,
129 Conformance::Transitional,
130 ),
131 (
132 b"http://purl.org/dc/elements/1.1/",
133 Ns::Dc,
134 Conformance::Transitional,
135 ),
136 (
137 b"http://purl.org/dc/terms/",
138 Ns::Dcterms,
139 Conformance::Transitional,
140 ),
141 (
142 b"http://www.w3.org/2001/XMLSchema-instance",
143 Ns::Xsi,
144 Conformance::Transitional,
145 ),
146 (
147 b"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
148 Ns::Ep,
149 Conformance::Transitional,
150 ),
151 (
152 b"http://purl.oclc.org/ooxml/officeDocument/extendedProperties",
153 Ns::Ep,
154 Conformance::Strict,
155 ),
156 (
157 b"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
158 Ns::Vt,
159 Conformance::Transitional,
160 ),
161 (
162 b"http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes",
163 Ns::Vt,
164 Conformance::Strict,
165 ),
166 (
167 b"http://schemas.openxmlformats.org/drawingml/2006/chart",
168 Ns::Chart,
169 Conformance::Transitional,
170 ),
171 (
172 b"http://purl.oclc.org/ooxml/drawingml/chart",
173 Ns::Chart,
174 Conformance::Strict,
175 ),
176 (
177 b"http://schemas.openxmlformats.org/drawingml/2006/diagram",
178 Ns::Dgm,
179 Conformance::Transitional,
180 ),
181 (
182 b"http://purl.oclc.org/ooxml/drawingml/diagram",
183 Ns::Dgm,
184 Conformance::Strict,
185 ),
186 (
187 b"http://schemas.openxmlformats.org/drawingml/2006/picture",
188 Ns::Pic,
189 Conformance::Transitional,
190 ),
191 (
192 b"http://purl.oclc.org/ooxml/drawingml/picture",
193 Ns::Pic,
194 Conformance::Strict,
195 ),
196 (
197 b"http://www.w3.org/XML/1998/namespace",
198 Ns::Xml,
199 Conformance::Transitional,
200 ),
201 (
202 b"http://schemas.microsoft.com/office/powerpoint/2010/main",
203 Ns::P14,
204 Conformance::Transitional,
205 ),
206 (
207 b"http://schemas.microsoft.com/office/drawing/2014/chartex",
208 Ns::ChartEx,
209 Conformance::Transitional,
210 ),
211 (
212 b"http://schemas.microsoft.com/office/powerpoint/2018/8/main",
213 Ns::P188,
214 Conformance::Transitional,
215 ),
216];
217
218pub fn transitional_uri(ns: Ns) -> Option<&'static str> {
220 KNOWN
221 .iter()
222 .find(|(_, known, conformance)| *known == ns && *conformance == Conformance::Transitional)
223 .and_then(|(uri, _, _)| std::str::from_utf8(uri).ok())
224}
225
226#[derive(Clone, Debug, PartialEq, Eq)]
228pub struct XmlError {
229 pub offset: usize,
230 pub msg: &'static str,
231}
232
233impl std::fmt::Display for XmlError {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 write!(f, "{} at byte {}", self.msg, self.offset)
236 }
237}
238
239impl std::error::Error for XmlError {}
240
241type XmlResult<T> = std::result::Result<T, XmlError>;
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub struct Name<'a> {
246 pub ns: Ns,
247 pub prefix: &'a [u8],
248 pub local: &'a [u8],
249}
250
251impl<'a> Name<'a> {
252 #[inline]
254 pub fn is(&self, ns: Ns, local: &[u8]) -> bool {
255 self.ns == ns && self.local == local
256 }
257
258 pub fn qualified(&self) -> String {
260 match self.prefix.is_empty() {
261 true => String::from_utf8_lossy(self.local).into_owned(),
262 false => format!(
263 "{}:{}",
264 String::from_utf8_lossy(self.prefix),
265 String::from_utf8_lossy(self.local)
266 ),
267 }
268 }
269}
270
271#[derive(Clone, Copy, Debug)]
273pub struct Start<'a> {
274 pub name: Name<'a>,
275 pub raw_attrs: &'a [u8],
277 pub self_closing: bool,
278 pub offset: usize,
280}
281
282#[derive(Clone, Copy, Debug, PartialEq, Eq)]
284pub struct Attr<'a> {
285 pub name: Name<'a>,
286 pub raw_value: &'a [u8],
287}
288
289#[derive(Clone, Copy, Debug)]
291pub enum Event<'a> {
292 Start(Start<'a>),
293 End(Name<'a>),
295 Text {
297 raw: &'a [u8],
298 cdata: bool,
299 },
300 Eof,
301}
302
303#[derive(Clone, Copy)]
304struct Scope<'a> {
305 depth: usize,
306 prefix: &'a [u8],
307 ns: Ns,
308}
309
310type Binding<'a> = Option<(&'a [u8], Ns)>;
312
313#[derive(Clone, Copy)]
315struct Open<'a> {
316 name: Name<'a>,
317 qname: &'a [u8],
318}
319
320pub struct Reader<'a> {
322 data: &'a [u8],
323 pos: usize,
324 open: Vec<Open<'a>>,
325 scopes: Vec<Scope<'a>>,
326 first_byte: Box<[Binding<'a>; 256]>,
329 default_ns: Ns,
330 pending_end: Option<Name<'a>>,
331 other: Vec<&'a [u8]>,
332 saw_transitional: bool,
333 saw_strict: bool,
334 strict: bool,
335 root_closed: bool,
336}
337
338impl<'a> Reader<'a> {
339 pub fn new(data: &'a [u8]) -> Self {
340 let pos = match data.starts_with(&[0xef, 0xbb, 0xbf]) {
341 true => 3,
342 false => 0,
343 };
344 Self {
345 data,
346 pos,
347 open: Vec::with_capacity(16),
348 scopes: Vec::with_capacity(8),
349 first_byte: Box::new([None; 256]),
350 default_ns: Ns::None,
351 pending_end: None,
352 other: Vec::new(),
353 saw_transitional: false,
354 saw_strict: false,
355 strict: false,
356 root_closed: false,
357 }
358 }
359
360 pub fn strict(mut self) -> Self {
362 self.strict = true;
363 self
364 }
365
366 #[inline]
368 pub fn depth(&self) -> usize {
369 self.open.len()
370 }
371
372 #[inline]
374 pub fn offset(&self) -> usize {
375 self.pos
376 }
377
378 pub fn saw_transitional(&self) -> bool {
380 self.saw_transitional
381 }
382
383 pub fn saw_strict(&self) -> bool {
385 self.saw_strict
386 }
387
388 pub fn other_uri(&self, index: u16) -> Option<&'a [u8]> {
390 self.other.get(usize::from(index)).copied()
391 }
392
393 #[inline]
395 pub fn resolve(&self, prefix: &[u8]) -> Ns {
396 let Some(&first) = prefix.first() else {
397 return self.default_ns;
398 };
399 if let Some((bound, ns)) = self.first_byte[usize::from(first)] {
400 if bound == prefix {
401 return ns;
402 }
403 }
404 self.resolve_slow(prefix)
405 }
406
407 fn resolve_slow(&self, prefix: &[u8]) -> Ns {
408 if let Some(scope) = self
409 .scopes
410 .iter()
411 .rev()
412 .find(|scope| scope.prefix == prefix)
413 {
414 return scope.ns;
415 }
416 match prefix {
417 b"xml" => Ns::Xml,
418 _ => Ns::None,
419 }
420 }
421
422 fn bind(&mut self, prefix: &'a [u8], ns: Ns) {
423 match prefix.first() {
424 None => self.default_ns = ns,
425 Some(&first) => self.first_byte[usize::from(first)] = Some((prefix, ns)),
426 }
427 }
428
429 fn rebind(&mut self, prefix: &[u8]) {
431 let remaining = self
432 .scopes
433 .iter()
434 .rev()
435 .find(|scope| match prefix.first() {
436 None => scope.prefix.is_empty(),
437 Some(&first) => scope.prefix.first() == Some(&first),
438 })
439 .map(|scope| (scope.prefix, scope.ns));
440 match prefix.first() {
441 None => self.default_ns = remaining.map_or(Ns::None, |(_, ns)| ns),
442 Some(&first) => self.first_byte[usize::from(first)] = remaining,
443 }
444 }
445
446 #[allow(clippy::should_implement_trait)]
449 pub fn next(&mut self) -> XmlResult<Event<'a>> {
450 if let Some(name) = self.pending_end.take() {
451 self.finish_open();
452 return Ok(Event::End(name));
453 }
454 loop {
455 if self.pos >= self.data.len() {
456 if !self.open.is_empty() {
457 return Err(XmlError {
458 offset: self.pos,
459 msg: "unexpected end of document inside an element",
460 });
461 }
462 return Ok(Event::Eof);
463 }
464 if self.data[self.pos] != b'<' {
465 if self.open.is_empty() {
466 self.skip_outer_whitespace()?;
467 continue;
468 }
469 return self.text();
470 }
471 let tag_start = self.pos;
472 let Some(&kind) = self.data.get(tag_start + 1) else {
473 return Err(XmlError {
474 offset: tag_start,
475 msg: "unterminated tag",
476 });
477 };
478 match kind {
479 b'/' => return self.end_tag(tag_start),
480 b'?' => {
481 self.skip_past(tag_start + 2, b"?>", "unterminated processing instruction")?
482 }
483 b'!' => {
484 if self.data[tag_start + 1..].starts_with(b"!--") {
485 self.skip_past(tag_start + 4, b"-->", "unterminated comment")?;
486 continue;
487 }
488 if self.data[tag_start + 1..].starts_with(b"![CDATA[") {
489 let body_start = tag_start + 9;
490 let end =
491 memmem::find(&self.data[body_start..], b"]]>").ok_or(XmlError {
492 offset: tag_start,
493 msg: "unterminated CDATA section",
494 })?;
495 self.pos = body_start + end + 3;
496 return Ok(Event::Text {
497 raw: &self.data[body_start..body_start + end],
498 cdata: true,
499 });
500 }
501 if self.data[tag_start + 1..].starts_with(b"!DOCTYPE") {
502 return Err(XmlError {
503 offset: tag_start,
504 msg: "DTD declarations are not allowed in package parts",
505 });
506 }
507 return Err(XmlError {
508 offset: tag_start,
509 msg: "unrecognized markup declaration",
510 });
511 }
512 _ => return self.start_tag(tag_start),
513 }
514 }
515 }
516
517 pub fn skip_element(&mut self) -> XmlResult<()> {
520 let target = self.open.len().saturating_sub(1);
521 if self.pending_end.take().is_some() {
522 if self.open.is_empty() {
523 return Err(XmlError {
524 offset: self.pos,
525 msg: "nothing to skip",
526 });
527 }
528 self.finish_open();
529 return Ok(());
530 }
531 loop {
532 let pos = self.pos;
533 let Some(rel) = memchr(b'<', &self.data[pos..]) else {
534 return Err(XmlError {
535 offset: pos,
536 msg: "unexpected end of document inside an element",
537 });
538 };
539 let tag_start = pos + rel;
540 let kind = *self.data.get(tag_start + 1).ok_or(XmlError {
541 offset: tag_start,
542 msg: "unterminated tag",
543 })?;
544 match kind {
545 b'/' => {
546 let fast = self.open.last().and_then(|open| {
547 let end = tag_start + 2 + open.qname.len();
548 let hit = self.data.get(tag_start + 2..end) == Some(open.qname)
549 && self.data.get(end) == Some(&b'>');
550 hit.then_some(end + 1)
551 });
552 match fast {
553 Some(after) => {
554 self.pos = after;
555 self.finish_open();
556 }
557 None => {
558 let close = memchr(b'>', &self.data[tag_start..]).ok_or(XmlError {
559 offset: tag_start,
560 msg: "unterminated end tag",
561 })?;
562 self.pos = tag_start + close + 1;
563 let qname = trim_ascii(&self.data[tag_start + 2..tag_start + close]);
564 self.close(qname, tag_start)?;
565 }
566 }
567 if self.open.len() == target {
568 return Ok(());
569 }
570 }
571 b'?' => {
572 self.skip_past(tag_start + 2, b"?>", "unterminated processing instruction")?
573 }
574 b'!' => {
575 if self.data[tag_start + 1..].starts_with(b"!--") {
576 self.skip_past(tag_start + 4, b"-->", "unterminated comment")?;
577 } else if self.data[tag_start + 1..].starts_with(b"![CDATA[") {
578 self.skip_past(tag_start + 9, b"]]>", "unterminated CDATA section")?;
579 } else {
580 return Err(XmlError {
581 offset: tag_start,
582 msg: "unrecognized markup declaration",
583 });
584 }
585 }
586 _ => {
587 let (name_end, _, self_closing, _) = self.scan_start(tag_start)?;
588 if !self_closing {
589 let qname = &self.data[tag_start + 1..name_end];
590 let name = Name {
591 ns: Ns::None,
592 prefix: &[],
593 local: qname,
594 };
595 self.open.push(Open { name, qname });
596 }
597 }
598 }
599 }
600 }
601
602 pub fn text_content(&mut self, out: &mut String) -> XmlResult<()> {
606 let target = self.open.len().saturating_sub(1);
607 loop {
608 match self.next()? {
609 Event::Text { raw, cdata } => match cdata {
610 true => out.push_str(&String::from_utf8_lossy(raw)),
611 false => unescape_into(raw, out),
612 },
613 Event::End(_) if self.open.len() == target => return Ok(()),
614 Event::Eof => return Ok(()),
615 _ => {}
616 }
617 }
618 }
619
620 pub fn attr(&self, start: &Start<'a>, ns: Ns, local: &[u8]) -> Option<&'a [u8]> {
622 self.attrs(start)
623 .find(|attr| attr.name.ns == ns && attr.name.local == local)
624 .map(|attr| attr.raw_value)
625 }
626
627 pub fn attrs(&self, start: &Start<'a>) -> Attrs<'_, 'a> {
629 Attrs {
630 reader: self,
631 raw: start.raw_attrs,
632 pos: 0,
633 }
634 }
635
636 fn text(&mut self) -> XmlResult<Event<'a>> {
637 let start = self.pos;
638 let end = memchr(b'<', &self.data[start..])
639 .map(|rel| start + rel)
640 .unwrap_or(self.data.len());
641 self.pos = end;
642 let raw = &self.data[start..end];
643 if self.strict {
644 if let Some(offset) = invalid_char_offset(raw) {
645 return Err(XmlError {
646 offset: start + offset,
647 msg: "character not allowed in XML",
648 });
649 }
650 if let Some(rel) = memmem::find(raw, b"]]>") {
651 return Err(XmlError {
652 offset: start + rel,
653 msg: "']]>' is not allowed in character data",
654 });
655 }
656 }
657 Ok(Event::Text { raw, cdata: false })
658 }
659
660 fn skip_outer_whitespace(&mut self) -> XmlResult<()> {
661 let start = self.pos;
662 let end = memchr(b'<', &self.data[start..])
663 .map(|rel| start + rel)
664 .unwrap_or(self.data.len());
665 if !self.data[start..end]
666 .iter()
667 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
668 {
669 return Err(XmlError {
670 offset: start,
671 msg: "text outside the root element",
672 });
673 }
674 self.pos = end;
675 Ok(())
676 }
677
678 fn skip_past(&mut self, from: usize, needle: &[u8], msg: &'static str) -> XmlResult<()> {
679 let rel =
680 memmem::find(&self.data[from.min(self.data.len())..], needle).ok_or(XmlError {
681 offset: self.pos,
682 msg,
683 })?;
684 self.pos = from + rel + needle.len();
685 Ok(())
686 }
687
688 fn scan_start(&mut self, tag_start: usize) -> XmlResult<(usize, usize, bool, Option<usize>)> {
692 let data = self.data;
693 let name_start = tag_start + 1;
694 let mut i = name_start;
695 let mut colon = None;
696 while i < data.len() {
697 let byte = data[i];
698 if NAME_END[usize::from(byte)] {
699 break;
700 }
701 if byte == b':' && colon.is_none() {
702 colon = Some(i);
703 }
704 i += 1;
705 }
706 if i == name_start {
707 return Err(XmlError {
708 offset: tag_start,
709 msg: "element name expected",
710 });
711 }
712 let name_end = i;
713 if !self.strict {
714 if let Some(rel) = first_unquoted_gt(&data[name_end..]) {
715 let gt = name_end + rel;
716 self.pos = gt + 1;
717 let self_closing = gt > name_end && data[gt - 1] == b'/';
718 let attrs_end = match self_closing {
719 true => gt - 1,
720 false => gt,
721 };
722 return Ok((name_end, attrs_end, self_closing, colon));
723 }
724 }
725 loop {
726 while i < data.len() && matches!(data[i], b' ' | b'\t' | b'\r' | b'\n') {
727 i += 1;
728 }
729 match data.get(i) {
730 None => {
731 return Err(XmlError {
732 offset: tag_start,
733 msg: "unterminated start tag",
734 })
735 }
736 Some(b'>') => {
737 self.pos = i + 1;
738 return Ok((name_end, i, false, colon));
739 }
740 Some(b'/') => {
741 if data.get(i + 1) != Some(&b'>') {
742 return Err(XmlError {
743 offset: i,
744 msg: "'/' must be followed by '>'",
745 });
746 }
747 self.pos = i + 2;
748 return Ok((name_end, i, true, colon));
749 }
750 Some(_) => {
751 let eq = memchr3(b'=', b'>', b'/', &data[i..])
752 .map(|rel| i + rel)
753 .ok_or(XmlError {
754 offset: i,
755 msg: "unterminated start tag",
756 })?;
757 if data[eq] != b'=' {
758 if self.strict {
759 return Err(XmlError {
760 offset: i,
761 msg: "attribute without a value",
762 });
763 }
764 i = eq;
765 continue;
766 }
767 let mut v = eq + 1;
768 while v < data.len() && matches!(data[v], b' ' | b'\t' | b'\r' | b'\n') {
769 v += 1;
770 }
771 let quote = *data.get(v).ok_or(XmlError {
772 offset: v,
773 msg: "unterminated start tag",
774 })?;
775 if quote != b'"' && quote != b'\'' {
776 return Err(XmlError {
777 offset: v,
778 msg: "attribute value must be quoted",
779 });
780 }
781 let close =
782 memchr(quote, &data[v + 1..])
783 .map(|rel| v + 1 + rel)
784 .ok_or(XmlError {
785 offset: v,
786 msg: "unterminated attribute value",
787 })?;
788 if self.strict && memchr(b'<', &data[v + 1..close]).is_some() {
789 return Err(XmlError {
790 offset: v,
791 msg: "'<' is not allowed in an attribute value",
792 });
793 }
794 i = close + 1;
795 }
796 }
797 }
798 }
799
800 fn start_tag(&mut self, tag_start: usize) -> XmlResult<Event<'a>> {
801 let (name_end, attrs_end, self_closing, colon) = self.scan_start(tag_start)?;
802 let raw_attrs = &self.data[name_end..attrs_end];
803 let qname = &self.data[tag_start + 1..name_end];
804 let (prefix, local): (&'a [u8], &'a [u8]) = match colon {
805 Some(colon) => (
806 &self.data[tag_start + 1..colon],
807 &self.data[colon + 1..name_end],
808 ),
809 None => (&[], qname),
810 };
811 if self.strict {
812 let valid = is_ncname(local) && (prefix.is_empty() || is_ncname(prefix));
813 if !valid {
814 return Err(XmlError {
815 offset: tag_start,
816 msg: "invalid element name",
817 });
818 }
819 if self.open.is_empty() && self.root_closed {
820 return Err(XmlError {
821 offset: tag_start,
822 msg: "more than one root element",
823 });
824 }
825 }
826 let depth = self.open.len() + 1;
827 if raw_attrs.len() >= 6 && xmlns_finder().find(raw_attrs).is_some() {
828 self.declare_namespaces(raw_attrs, depth, tag_start)?;
829 }
830 let ns = self.resolve(prefix);
831 if self.strict && ns == Ns::None && !prefix.is_empty() {
832 return Err(XmlError {
833 offset: tag_start,
834 msg: "undeclared namespace prefix",
835 });
836 }
837 let name = Name { ns, prefix, local };
838 self.open.push(Open { name, qname });
839 if self_closing {
840 self.pending_end = Some(name);
841 }
842 Ok(Event::Start(Start {
843 name,
844 raw_attrs,
845 self_closing,
846 offset: tag_start,
847 }))
848 }
849
850 fn declare_namespaces(
851 &mut self,
852 raw_attrs: &'a [u8],
853 depth: usize,
854 tag_start: usize,
855 ) -> XmlResult<()> {
856 let mut seen_default = false;
857 for attr in (RawAttrs {
858 raw: raw_attrs,
859 pos: 0,
860 }) {
861 let (prefix, local) = split_qname(attr.0);
862 let is_default = prefix.is_empty() && local == b"xmlns";
863 let is_prefixed = prefix == b"xmlns";
864 if !is_default && !is_prefixed {
865 continue;
866 }
867 let declared_prefix: &'a [u8] = match is_default {
868 true => b"",
869 false => local,
870 };
871 if self.strict {
872 if is_default && seen_default {
873 return Err(XmlError {
874 offset: tag_start,
875 msg: "duplicate default namespace declaration",
876 });
877 }
878 if self
879 .scopes
880 .iter()
881 .any(|scope| scope.depth == depth && scope.prefix == declared_prefix)
882 {
883 return Err(XmlError {
884 offset: tag_start,
885 msg: "duplicate namespace declaration",
886 });
887 }
888 }
889 seen_default |= is_default;
890 let ns = self.intern(attr.1);
891 self.scopes.push(Scope {
892 depth,
893 prefix: declared_prefix,
894 ns,
895 });
896 self.bind(declared_prefix, ns);
897 }
898 Ok(())
899 }
900
901 fn intern(&mut self, uri: &'a [u8]) -> Ns {
902 if uri.is_empty() {
903 return Ns::None;
904 }
905 if let Some((_, ns, conformance)) = KNOWN.iter().find(|(known, _, _)| *known == uri) {
906 let class_specific = matches!(
907 ns,
908 Ns::Pml | Ns::Dml | Ns::Rel | Ns::Ep | Ns::Vt | Ns::Chart | Ns::Dgm | Ns::Pic
909 );
910 match conformance {
911 Conformance::Transitional if class_specific => self.saw_transitional = true,
912 Conformance::Strict => self.saw_strict = true,
913 _ => {}
914 }
915 return *ns;
916 }
917 if let Some(index) = self.other.iter().position(|known| *known == uri) {
918 return Ns::Other(index as u16);
919 }
920 self.other.push(uri);
921 Ns::Other((self.other.len() - 1) as u16)
922 }
923
924 fn end_tag(&mut self, tag_start: usize) -> XmlResult<Event<'a>> {
925 if let Some(open) = self.open.last().copied() {
926 let end = tag_start + 2 + open.qname.len();
927 let matches_open = self.data.get(tag_start + 2..end) == Some(open.qname)
928 && self.data.get(end) == Some(&b'>');
929 if matches_open {
930 self.pos = end + 1;
931 self.finish_open();
932 return Ok(Event::End(open.name));
933 }
934 }
935 let close = memchr(b'>', &self.data[tag_start..])
936 .map(|rel| tag_start + rel)
937 .ok_or(XmlError {
938 offset: tag_start,
939 msg: "unterminated end tag",
940 })?;
941 let qname = trim_ascii(&self.data[tag_start + 2..close]);
942 self.pos = close + 1;
943 let name = self.close(qname, tag_start)?;
944 Ok(Event::End(name))
945 }
946
947 fn close(&mut self, qname: &[u8], offset: usize) -> XmlResult<Name<'a>> {
949 let Some(open) = self.open.last().copied() else {
950 return Err(XmlError {
951 offset,
952 msg: "end tag without a start tag",
953 });
954 };
955 if open.qname != qname {
956 return Err(XmlError {
957 offset,
958 msg: "end tag does not match the open element",
959 });
960 }
961 self.finish_open();
962 Ok(open.name)
963 }
964
965 #[inline]
967 fn finish_open(&mut self) {
968 self.open.pop();
969 self.pop_scopes();
970 self.root_closed |= self.open.is_empty();
971 }
972
973 #[inline]
974 fn pop_scopes(&mut self) {
975 let depth = self.open.len() + 1;
976 while self.scopes.last().is_some_and(|scope| scope.depth == depth) {
977 let Some(scope) = self.scopes.pop() else {
978 return;
979 };
980 self.rebind(scope.prefix);
981 }
982 }
983}
984
985pub struct Attrs<'r, 'a> {
988 reader: &'r Reader<'a>,
989 raw: &'a [u8],
990 pos: usize,
991}
992
993impl<'r, 'a> Iterator for Attrs<'r, 'a> {
994 type Item = Attr<'a>;
995
996 fn next(&mut self) -> Option<Attr<'a>> {
997 let (qname, raw_value, next) = next_raw_attr(self.raw, self.pos)?;
998 self.pos = next;
999 let (prefix, local) = split_qname(qname);
1000 let ns = match prefix.is_empty() {
1001 true => Ns::None,
1002 false => self.reader.resolve(prefix),
1003 };
1004 Some(Attr {
1005 name: Name { ns, prefix, local },
1006 raw_value,
1007 })
1008 }
1009}
1010
1011struct RawAttrs<'a> {
1012 raw: &'a [u8],
1013 pos: usize,
1014}
1015
1016impl<'a> Iterator for RawAttrs<'a> {
1017 type Item = (&'a [u8], &'a [u8]);
1018
1019 fn next(&mut self) -> Option<Self::Item> {
1020 let (qname, value, next) = next_raw_attr(self.raw, self.pos)?;
1021 self.pos = next;
1022 Some((qname, value))
1023 }
1024}
1025
1026fn next_raw_attr(raw: &[u8], mut pos: usize) -> Option<(&[u8], &[u8], usize)> {
1028 while pos < raw.len() && matches!(raw[pos], b' ' | b'\t' | b'\r' | b'\n') {
1029 pos += 1;
1030 }
1031 if pos >= raw.len() {
1032 return None;
1033 }
1034 let eq = memchr(b'=', &raw[pos..]).map(|rel| pos + rel)?;
1035 let name = trim_ascii(&raw[pos..eq]);
1036 let mut v = eq + 1;
1037 while v < raw.len() && matches!(raw[v], b' ' | b'\t' | b'\r' | b'\n') {
1038 v += 1;
1039 }
1040 let quote = *raw.get(v)?;
1041 let close = memchr(quote, &raw[v + 1..]).map(|rel| v + 1 + rel)?;
1042 Some((name, &raw[v + 1..close], close + 1))
1043}
1044
1045fn first_unquoted_gt(data: &[u8]) -> Option<usize> {
1048 let mut i = 0;
1049 loop {
1050 let at = i + memchr3(b'>', b'"', b'\'', &data[i..])?;
1051 let byte = data[at];
1052 if byte == b'>' {
1053 return Some(at);
1054 }
1055 let close = memchr(byte, &data[at + 1..])?;
1056 i = at + 1 + close + 1;
1057 }
1058}
1059
1060static NAME_END: [bool; 256] = {
1062 let mut table = [false; 256];
1063 table[b' ' as usize] = true;
1064 table[b'\t' as usize] = true;
1065 table[b'\r' as usize] = true;
1066 table[b'\n' as usize] = true;
1067 table[b'/' as usize] = true;
1068 table[b'>' as usize] = true;
1069 table
1070};
1071
1072#[inline]
1073fn split_qname(qname: &[u8]) -> (&[u8], &[u8]) {
1074 match memchr(b':', qname) {
1075 Some(colon) => (&qname[..colon], &qname[colon + 1..]),
1076 None => (&[], qname),
1077 }
1078}
1079
1080fn trim_ascii(bytes: &[u8]) -> &[u8] {
1081 let start = bytes
1082 .iter()
1083 .position(|byte| !byte.is_ascii_whitespace())
1084 .unwrap_or(bytes.len());
1085 let end = bytes
1086 .iter()
1087 .rposition(|byte| !byte.is_ascii_whitespace())
1088 .map_or(start, |i| i + 1);
1089 &bytes[start..end.max(start)]
1090}
1091
1092pub fn is_ncname(name: &[u8]) -> bool {
1094 let Some(&first) = name.first() else {
1095 return false;
1096 };
1097 let start_ok = first.is_ascii_alphabetic() || first == b'_' || first >= 0x80;
1098 start_ok
1099 && name.iter().all(|&byte| {
1100 byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.') || byte >= 0x80
1101 })
1102}
1103
1104pub fn invalid_char_offset(raw: &[u8]) -> Option<usize> {
1107 raw.iter()
1108 .position(|&byte| byte < 0x20 && !matches!(byte, b'\t' | b'\n' | b'\r'))
1109}
1110
1111pub fn unescape_into(raw: &[u8], out: &mut String) {
1115 let needs_work = memchr2(b'&', b'_', raw).is_some();
1116 if !needs_work {
1117 push_lossy(raw, out);
1118 return;
1119 }
1120 let mut rest = raw;
1121 while let Some(rel) = memchr2(b'&', b'_', rest) {
1122 push_lossy(&rest[..rel], out);
1123 rest = &rest[rel..];
1124 match rest[0] {
1125 b'&' => {
1126 let Some(semi) = memchr(b';', &rest[..rest.len().min(12)]) else {
1127 out.push('&');
1128 rest = &rest[1..];
1129 continue;
1130 };
1131 let entity = &rest[1..semi];
1132 match decode_entity(entity) {
1133 Some(ch) => {
1134 out.push(ch);
1135 rest = &rest[semi + 1..];
1136 }
1137 None => {
1138 out.push('&');
1139 rest = &rest[1..];
1140 }
1141 }
1142 }
1143 _ => match decode_x_escape(rest) {
1144 Some(ch) => {
1145 out.push(ch);
1146 rest = &rest[7..];
1147 }
1148 None => {
1149 out.push('_');
1150 rest = &rest[1..];
1151 }
1152 },
1153 }
1154 }
1155 push_lossy(rest, out);
1156}
1157
1158pub fn unescape_attr(raw: &[u8]) -> String {
1161 let mut out = String::with_capacity(raw.len());
1162 if memchr(b'&', raw).is_none() {
1163 push_lossy(raw, &mut out);
1164 return out;
1165 }
1166 let mut rest = raw;
1167 while let Some(rel) = memchr(b'&', rest) {
1168 push_lossy(&rest[..rel], &mut out);
1169 rest = &rest[rel..];
1170 let decoded = memchr(b';', &rest[..rest.len().min(12)])
1171 .and_then(|semi| decode_entity(&rest[1..semi]).map(|ch| (ch, semi)));
1172 match decoded {
1173 Some((ch, semi)) => {
1174 out.push(ch);
1175 rest = &rest[semi + 1..];
1176 }
1177 None => {
1178 out.push('&');
1179 rest = &rest[1..];
1180 }
1181 }
1182 }
1183 push_lossy(rest, &mut out);
1184 out
1185}
1186
1187fn decode_entity(entity: &[u8]) -> Option<char> {
1188 match entity {
1189 b"lt" => Some('<'),
1190 b"gt" => Some('>'),
1191 b"amp" => Some('&'),
1192 b"quot" => Some('"'),
1193 b"apos" => Some('\''),
1194 _ => {
1195 let digits = entity.strip_prefix(b"#")?;
1196 let code = match digits.strip_prefix(b"x") {
1197 Some(hex) => u32::from_str_radix(std::str::from_utf8(hex).ok()?, 16).ok()?,
1198 None => std::str::from_utf8(digits).ok()?.parse::<u32>().ok()?,
1199 };
1200 char::from_u32(code)
1201 }
1202 }
1203}
1204
1205fn decode_x_escape(rest: &[u8]) -> Option<char> {
1206 if rest.len() < 7 || rest[1] != b'x' || rest[6] != b'_' {
1207 return None;
1208 }
1209 let hex = std::str::from_utf8(&rest[2..6]).ok()?;
1210 let code = u32::from_str_radix(hex, 16).ok()?;
1211 char::from_u32(code)
1212}
1213
1214#[inline]
1215fn push_lossy(bytes: &[u8], out: &mut String) {
1216 match std::str::from_utf8(bytes) {
1217 Ok(text) => out.push_str(text),
1218 Err(_) => out.push_str(&String::from_utf8_lossy(bytes)),
1219 }
1220}
1221
1222pub fn well_formed(data: &[u8]) -> XmlResult<()> {
1224 let mut reader = Reader::new(data).strict();
1225 let mut saw_root = false;
1226 loop {
1227 match reader.next()? {
1228 Event::Start(_) => saw_root = true,
1229 Event::Eof => break,
1230 _ => {}
1231 }
1232 }
1233 match saw_root {
1234 true => Ok(()),
1235 false => Err(XmlError {
1236 offset: data.len(),
1237 msg: "no root element",
1238 }),
1239 }
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use super::*;
1245
1246 const SLIDE: &[u8] = br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1247<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:sp><p:nvSpPr><p:cNvPr id="2" name="Title 1"/><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:txBody><a:bodyPr/><a:p><a:r><a:rPr lang="en-US"><a:hlinkClick r:id="rId2"/></a:rPr><a:t>Hello & welcome</a:t></a:r><a:br/><a:r><a:t><![CDATA[a < b]]></a:t></a:r></a:p></p:txBody></p:sp></p:spTree></p:cSld></p:sld>"#;
1248
1249 fn collect(data: &[u8]) -> Vec<String> {
1250 let mut reader = Reader::new(data);
1251 let mut out = Vec::new();
1252 loop {
1253 match reader.next().unwrap() {
1254 Event::Start(start) => out.push(format!(
1255 "<{:?}:{}{}",
1256 start.name.ns,
1257 String::from_utf8_lossy(start.name.local),
1258 if start.self_closing { "/" } else { "" }
1259 )),
1260 Event::End(name) => out.push(format!("</{}", String::from_utf8_lossy(name.local))),
1261 Event::Text { raw, cdata } => {
1262 let mut text = String::new();
1263 match cdata {
1264 true => text.push_str(std::str::from_utf8(raw).unwrap()),
1265 false => unescape_into(raw, &mut text),
1266 }
1267 out.push(format!("'{text}'"));
1268 }
1269 Event::Eof => return out,
1270 }
1271 }
1272 }
1273
1274 #[test]
1275 fn resolves_prefixes_and_emits_ends_for_self_closing_tags() {
1276 let events = collect(SLIDE);
1277 assert_eq!(events[0], "<Pml:sld");
1278 assert!(events.contains(&"<Pml:cNvPr/".to_string()));
1279 assert!(events.contains(&"<Dml:bodyPr/".to_string()));
1280 assert!(events.contains(&"'Hello & welcome'".to_string()));
1281 assert!(events.contains(&"'a < b'".to_string()));
1282 let ends = events
1283 .iter()
1284 .filter(|event| event.starts_with("</"))
1285 .count();
1286 let starts = events
1287 .iter()
1288 .filter(|event| event.starts_with('<') && !event.starts_with("</"))
1289 .count();
1290 assert_eq!(starts, ends);
1291 assert_eq!(events.last().unwrap(), "</sld");
1292 }
1293
1294 #[test]
1295 fn attributes_resolve_namespaces_and_default_namespace_does_not_apply_to_them() {
1296 let mut reader = Reader::new(SLIDE);
1297 loop {
1298 if let Event::Start(start) = reader.next().unwrap() {
1299 if start.name.is(Ns::Dml, b"hlinkClick") {
1300 assert_eq!(reader.attr(&start, Ns::Rel, b"id"), Some(&b"rId2"[..]));
1301 assert_eq!(reader.attr(&start, Ns::None, b"id"), None);
1302 break;
1303 }
1304 }
1305 }
1306 let data = br#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="xml" ContentType="application/xml"/></Types>"#;
1307 let mut reader = Reader::new(data);
1308 let Event::Start(types) = reader.next().unwrap() else {
1309 panic!()
1310 };
1311 assert!(types.name.is(Ns::ContentTypes, b"Types"));
1312 let Event::Start(default) = reader.next().unwrap() else {
1313 panic!()
1314 };
1315 assert!(default.name.is(Ns::ContentTypes, b"Default"));
1316 let attrs: Vec<_> = reader.attrs(&default).collect();
1317 assert_eq!(attrs.len(), 2);
1318 assert_eq!(attrs[0].name.ns, Ns::None);
1319 assert_eq!(attrs[0].name.local, b"Extension");
1320 assert_eq!(attrs[1].raw_value, b"application/xml");
1321 }
1322
1323 #[test]
1324 fn lenient_start_tags_keep_mixed_quotes_and_quoted_angle_brackets() {
1325 let data = br#"<a x="it's" y=">" z='say "hi" > there'>text</a>"#;
1326 let mut reader = Reader::new(data);
1327 let Event::Start(start) = reader.next().unwrap() else {
1328 panic!()
1329 };
1330 assert!(!start.self_closing);
1331 let attrs: Vec<_> = reader.attrs(&start).collect();
1332 assert_eq!(attrs.len(), 3);
1333 assert_eq!(attrs[0].raw_value, b"it's");
1334 assert_eq!(attrs[1].raw_value, b">");
1335 assert_eq!(attrs[2].raw_value, br#"say "hi" > there"#);
1336 let Event::Text { raw, .. } = reader.next().unwrap() else {
1337 panic!()
1338 };
1339 assert_eq!(raw, b"text");
1340 assert!(matches!(reader.next().unwrap(), Event::End(_)));
1341 let mut reader = Reader::new(br#"<a x="it's" y=">"/>"#);
1342 let Event::Start(start) = reader.next().unwrap() else {
1343 panic!()
1344 };
1345 assert!(start.self_closing);
1346 assert_eq!(reader.attrs(&start).count(), 2);
1347 assert!(matches!(reader.next().unwrap(), Event::End(_)));
1348 assert!(matches!(reader.next().unwrap(), Event::Eof));
1349 assert_eq!(first_unquoted_gt(br#"x="unclosed>"#), None);
1350 }
1351
1352 #[test]
1353 fn strict_namespace_uris_map_to_the_same_ids() {
1354 let data = br#"<p:sld xmlns:p="http://purl.oclc.org/ooxml/presentationml/main" xmlns:a="http://purl.oclc.org/ooxml/drawingml/main"><a:t>x</a:t></p:sld>"#;
1355 let mut reader = Reader::new(data);
1356 let Event::Start(sld) = reader.next().unwrap() else {
1357 panic!()
1358 };
1359 assert_eq!(sld.name.ns, Ns::Pml);
1360 let Event::Start(t) = reader.next().unwrap() else {
1361 panic!()
1362 };
1363 assert_eq!(t.name.ns, Ns::Dml);
1364 assert!(reader.saw_strict());
1365 assert!(!reader.saw_transitional());
1366 }
1367
1368 #[test]
1369 fn unknown_namespaces_are_numbered_and_scoped() {
1370 let data =
1371 br#"<r xmlns="urn:a"><x xmlns="urn:b"><y/></x><z xmlns:q="urn:b"><q:w/></z></r>"#;
1372 let mut reader = Reader::new(data);
1373 let mut seen = Vec::new();
1374 loop {
1375 match reader.next().unwrap() {
1376 Event::Start(start) => seen.push((
1377 String::from_utf8_lossy(start.name.local).into_owned(),
1378 start.name.ns,
1379 )),
1380 Event::Eof => break,
1381 _ => {}
1382 }
1383 }
1384 assert_eq!(seen[0], ("r".into(), Ns::Other(0)));
1385 assert_eq!(seen[1], ("x".into(), Ns::Other(1)));
1386 assert_eq!(seen[2], ("y".into(), Ns::Other(1)));
1387 assert_eq!(seen[3], ("z".into(), Ns::Other(0)));
1388 assert_eq!(seen[4], ("w".into(), Ns::Other(1)));
1389 assert_eq!(reader.other_uri(1), Some(&b"urn:b"[..]));
1390 }
1391
1392 #[test]
1393 fn skip_element_consumes_the_subtree_and_keeps_depth_consistent() {
1394 let data = br#"<a><b x="1>2"><c/><!-- </b> --><d><![CDATA[</b>]]></d></b><e>after</e></a>"#;
1395 let mut reader = Reader::new(data);
1396 assert!(matches!(reader.next().unwrap(), Event::Start(_)));
1397 let Event::Start(b) = reader.next().unwrap() else {
1398 panic!()
1399 };
1400 assert_eq!(b.name.local, b"b");
1401 reader.skip_element().unwrap();
1402 assert_eq!(reader.depth(), 1);
1403 let Event::Start(e) = reader.next().unwrap() else {
1404 panic!()
1405 };
1406 assert_eq!(e.name.local, b"e");
1407 let mut text = String::new();
1408 reader.text_content(&mut text).unwrap();
1409 assert_eq!(text, "after");
1410 assert!(matches!(reader.next().unwrap(), Event::End(_)));
1411 assert!(matches!(reader.next().unwrap(), Event::Eof));
1412 }
1413
1414 #[test]
1415 fn skipping_a_self_closing_element_works() {
1416 let data = b"<a><b/><c>x</c></a>";
1417 let mut reader = Reader::new(data);
1418 reader.next().unwrap();
1419 reader.next().unwrap();
1420 reader.skip_element().unwrap();
1421 let Event::Start(c) = reader.next().unwrap() else {
1422 panic!()
1423 };
1424 assert_eq!(c.name.local, b"c");
1425 }
1426
1427 #[test]
1428 fn mismatched_and_unterminated_tags_are_errors() {
1429 assert!(Reader::new(b"<a></b>").next().is_ok());
1430 let mut reader = Reader::new(b"<a></b>");
1431 reader.next().unwrap();
1432 assert_eq!(
1433 reader.next().unwrap_err().msg,
1434 "end tag does not match the open element"
1435 );
1436 let mut reader = Reader::new(b"<a><b>");
1437 reader.next().unwrap();
1438 reader.next().unwrap();
1439 assert_eq!(
1440 reader.next().unwrap_err().msg,
1441 "unexpected end of document inside an element"
1442 );
1443 let mut reader = Reader::new(b"<a x='1'");
1444 assert_eq!(reader.next().unwrap_err().msg, "unterminated start tag");
1445 assert_eq!(
1446 Reader::new(b"<!DOCTYPE x><x/>").next().unwrap_err().msg,
1447 "DTD declarations are not allowed in package parts"
1448 );
1449 }
1450
1451 #[test]
1452 fn a_byte_order_mark_and_declaration_are_skipped() {
1453 let mut data = vec![0xef, 0xbb, 0xbf];
1454 data.extend_from_slice(b"<?xml version=\"1.0\"?>\n<x/>");
1455 let mut reader = Reader::new(&data);
1456 let Event::Start(x) = reader.next().unwrap() else {
1457 panic!()
1458 };
1459 assert_eq!(x.name.local, b"x");
1460 }
1461
1462 #[test]
1463 fn unescape_handles_entities_references_and_x_escapes() {
1464 let mut out = String::new();
1465 unescape_into(
1466 b"a <b> & "c' AB _x0009_tab &unknown; & _x00ZZ_ end",
1467 &mut out,
1468 );
1469 assert_eq!(out, "a <b> & \"c' AB \ttab &unknown; & _x00ZZ_ end");
1470 let mut out = String::new();
1471 unescape_into(b"plain text_with_underscores", &mut out);
1472 assert_eq!(out, "plain text_with_underscores");
1473 assert_eq!(
1474 unescape_attr(b"/ppt/a_x0041_.xml & b"),
1475 "/ppt/a_x0041_.xml & b"
1476 );
1477 let mut out = String::new();
1478 unescape_into(&[b'o', b'k', 0xff, b'!'], &mut out);
1479 assert_eq!(out, "ok\u{fffd}!");
1480 }
1481
1482 #[test]
1483 fn well_formed_reports_violations_the_lenient_reader_tolerates() {
1484 assert!(well_formed(b"<a><b x=\"1\"/>text</a>").is_ok());
1485 assert_eq!(
1486 well_formed(b"<a>bad\x01char</a>").unwrap_err().msg,
1487 "character not allowed in XML"
1488 );
1489 assert_eq!(
1490 well_formed(b"<a x=\"<\"/>").unwrap_err().msg,
1491 "'<' is not allowed in an attribute value"
1492 );
1493 assert_eq!(
1494 well_formed(b"<p:a/>").unwrap_err().msg,
1495 "undeclared namespace prefix"
1496 );
1497 assert_eq!(
1498 well_formed(b"<1a/>").unwrap_err().msg,
1499 "invalid element name"
1500 );
1501 assert_eq!(well_formed(b"").unwrap_err().msg, "no root element");
1502 assert_eq!(
1503 well_formed(b"<a/><b/>").unwrap_err().msg,
1504 "more than one root element"
1505 );
1506 assert!(Reader::new(b"<p:a/>").next().is_ok());
1507 let mut lenient = Reader::new(b"<a/><b/>");
1508 lenient.next().unwrap();
1509 lenient.next().unwrap();
1510 assert!(matches!(lenient.next().unwrap(), Event::Start(_)));
1511 }
1512
1513 #[test]
1514 fn text_outside_root_is_rejected_but_whitespace_is_fine() {
1515 let mut reader = Reader::new(b"\n<a/>\n");
1516 assert!(matches!(reader.next().unwrap(), Event::Start(_)));
1517 assert!(matches!(reader.next().unwrap(), Event::End(_)));
1518 assert!(matches!(reader.next().unwrap(), Event::Eof));
1519 let mut reader = Reader::new(b"junk<a/>");
1520 assert_eq!(
1521 reader.next().unwrap_err().msg,
1522 "text outside the root element"
1523 );
1524 }
1525
1526 #[test]
1527 fn transitional_uri_lookup() {
1528 assert_eq!(
1529 transitional_uri(Ns::Pml),
1530 Some("http://schemas.openxmlformats.org/presentationml/2006/main")
1531 );
1532 assert_eq!(transitional_uri(Ns::Other(3)), None);
1533 }
1534}