1use core::ops::Range;
2use core::str;
3use alloc::string::String;
4
5use memchr::memchr2;
6
7use crate::{Error, TextPos};
8
9type Result<T> = core::result::Result<T, Error>;
10
11trait XmlCharExt {
13 fn is_xml_name_start(&self) -> bool;
16
17 fn is_xml_name(&self) -> bool;
20
21 fn is_xml_char(&self) -> bool;
24}
25
26impl XmlCharExt for char {
27 #[inline]
28 fn is_xml_name_start(&self) -> bool {
29 if *self as u32 <= 128 {
31 return (*self as u8).is_xml_name_start();
32 }
33
34 matches!(*self as u32,
35 0x0000C0..=0x0000D6
36 | 0x0000D8..=0x0000F6
37 | 0x0000F8..=0x0002FF
38 | 0x000370..=0x00037D
39 | 0x00037F..=0x001FFF
40 | 0x00200C..=0x00200D
41 | 0x002070..=0x00218F
42 | 0x002C00..=0x002FEF
43 | 0x003001..=0x00D7FF
44 | 0x00F900..=0x00FDCF
45 | 0x00FDF0..=0x00FFFD
46 | 0x010000..=0x0EFFFF)
47 }
48
49 #[inline]
50 fn is_xml_name(&self) -> bool {
51 if *self as u32 <= 128 {
53 return (*self as u8).is_xml_name();
54 }
55
56 matches!(*self as u32, 0x0000B7
57 | 0x0000C0..=0x0000D6
58 | 0x0000D8..=0x0000F6
59 | 0x0000F8..=0x0002FF
60 | 0x000300..=0x00036F
61 | 0x000370..=0x00037D
62 | 0x00037F..=0x001FFF
63 | 0x00200C..=0x00200D
64 | 0x00203F..=0x002040
65 | 0x002070..=0x00218F
66 | 0x002C00..=0x002FEF
67 | 0x003001..=0x00D7FF
68 | 0x00F900..=0x00FDCF
69 | 0x00FDF0..=0x00FFFD
70 | 0x010000..=0x0EFFFF)
71 }
72
73 #[inline]
74 fn is_xml_char(&self) -> bool {
75 if (*self as u32) < 0x20 {
78 return (*self as u8).is_xml_space();
79 }
80
81 !matches!(*self as u32, 0xFFFF | 0xFFFE)
82 }
83}
84
85trait XmlByteExt {
86 fn is_xml_space(&self) -> bool;
90
91 fn is_xml_name_start(&self) -> bool;
94
95 fn is_xml_name(&self) -> bool;
98
99 fn is_xml_char(&self) -> bool;
102}
103
104impl XmlByteExt for u8 {
105 #[inline]
106 fn is_xml_space(&self) -> bool {
107 matches!(*self, b' ' | b'\t' | b'\n' | b'\r')
108 }
109
110 #[inline]
111 fn is_xml_name_start(&self) -> bool {
112 matches!(*self, b'A'..=b'Z' | b'a'..=b'z' | b':' | b'_')
113 }
114
115 #[inline]
116 fn is_xml_name(&self) -> bool {
117 matches!(*self, b'A'..=b'Z' | b'a'..=b'z'| b'0'..=b'9'| b':' | b'_' | b'-' | b'.')
118 }
119
120 #[inline]
121 fn is_xml_char(&self) -> bool {
122 *self > 0x20 || self.is_xml_space()
123 }
124}
125
126#[inline]
127fn is_xml_str(s: &str, value_start: usize, stream: &mut Stream<'_>) -> Result<()> {
128 if s.as_bytes().is_ascii() {
129 for (i, b) in s.as_bytes().iter().enumerate() {
130 if !b.is_xml_char() {
131 return Err(Error::NonXmlChar(*b as char, stream.gen_text_pos_from(value_start + i)));
132 }
133 }
134
135 Ok(())
136 } else {
137 is_xml_str_unicode(s, value_start, stream)
138 }
139}
140
141
142#[cold]
143#[inline(never)]
144fn is_xml_str_unicode(s: &str, value_start: usize, stream: &mut Stream<'_>) -> Result<()> {
145 for (i, ch) in s.char_indices() {
146 if !ch.is_xml_char() {
147 return Err(Error::NonXmlChar(ch, stream.gen_text_pos_from(value_start + i)));
148 }
149 }
150
151 Ok(())
152}
153
154#[must_use]
159#[derive(Clone, Copy)]
160pub struct StrSpan<'input> {
161 text: &'input str,
162 start: usize,
163}
164
165impl<'input> From<&'input str> for StrSpan<'input> {
166 #[inline]
167 fn from(text: &'input str) -> Self {
168 StrSpan { text, start: 0 }
169 }
170}
171
172impl<'input> StrSpan<'input> {
173 #[inline]
174 pub fn from_substr(text: &'input str, start: usize, end: usize) -> Self {
175 debug_assert!(start <= end);
176 StrSpan {
177 text: &text[start..end],
178 start,
179 }
180 }
181
182 #[inline]
183 pub fn range(&self) -> Range<usize> {
184 self.start..(self.start + self.text.len())
185 }
186
187 #[inline]
188 pub fn as_str(&self) -> &'input str {
189 self.text
190 }
191
192 #[inline]
193 fn slice_region(&self, start: usize, end: usize) -> &'input str {
194 &self.text[start..end]
195 }
196}
197
198pub enum Token<'input> {
199 ProcessingInstruction(&'input str, Option<&'input str>, Range<usize>),
201
202 Comment(&'input str, Range<usize>),
204
205 EntityDeclaration(&'input str, StrSpan<'input>),
207
208 ElementStart(&'input str, &'input str, usize),
210
211 Attribute(Range<usize>, u16, u8, &'input str, &'input str, StrSpan<'input>),
213
214 ElementEnd(ElementEnd<'input>, Range<usize>),
215
216 Text(&'input str, Range<usize>),
220
221 Cdata(&'input str, Range<usize>),
223}
224
225#[derive(Clone, Copy)]
227pub enum ElementEnd<'input> {
228 Open,
230 Close(&'input str, &'input str),
232 Empty,
234}
235
236pub trait XmlEvents<'input> {
237 fn token(&mut self, token: Token<'input>) -> Result<()>;
238
239 fn resolve_entity(&mut self, _pub_id: Option<&str>, _uri: &str) -> core::result::Result<Option<&'input str>, String> { Ok(None) }
240}
241
242pub fn parse<'input>(
244 text: &'input str,
245 allow_dtd: bool,
246 events: &mut impl XmlEvents<'input>,
247) -> Result<()> {
248 let s = &mut Stream::new(text);
249
250 parse_declaration(s)?;
251
252 parse_misc(s, events)?;
253
254 s.skip_spaces();
255 if s.starts_with(b"<!DOCTYPE") {
256 if !allow_dtd {
257 return Err(Error::DtdDetected);
258 }
259
260 parse_doctype(s, events)?;
261 parse_misc(s, events)?;
262 }
263
264 s.skip_spaces();
265 if s.curr_byte().ok() == Some(b'<') && parse_start_tag(s, events)? {
266 parse_content(s, events)?;
267 }
268
269 parse_misc(s, events)?;
270
271 if !s.at_end() {
272 return Err(Error::UnknownToken(s.gen_text_pos()));
273 }
274
275 Ok(())
276}
277
278fn parse_misc<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
280 while !s.at_end() {
281 s.skip_spaces();
282 if s.starts_with(b"<!--") {
283 parse_comment(s, events)?;
284 } else if s.starts_with(b"<?") {
285 parse_pi(s, events)?;
286 } else {
287 break;
288 }
289 }
290
291 Ok(())
292}
293
294fn parse_declaration(s: &mut Stream) -> Result<()> {
298 if s.starts_with(&[0xEF, 0xBB, 0xBF]) {
300 s.advance(3);
301 }
302
303 if !s.starts_with(b"<?xml ") {
304 return Ok(())
305 }
306
307 fn consume_spaces(s: &mut Stream) -> Result<()> {
308 if s.starts_with_space() {
309 s.skip_spaces();
310 } else if !s.starts_with(b"?>") && !s.at_end() {
311 return Err(Error::InvalidChar2(
312 "a whitespace",
313 s.curr_byte_unchecked(),
314 s.gen_text_pos(),
315 ));
316 }
317
318 Ok(())
319 }
320
321 s.advance(5); consume_spaces(s)?;
323
324 if !s.starts_with(b"version") {
326 return s.skip_string(b"version");
328 }
329 let _ = parse_attribute(s)?;
330 consume_spaces(s)?;
331
332 if s.starts_with(b"encoding") {
333 let _ = parse_attribute(s)?;
334 consume_spaces(s)?;
335 }
336
337 if s.starts_with(b"standalone") {
338 let _ = parse_attribute(s)?;
339 }
340
341 s.skip_spaces();
342 s.skip_string(b"?>")?;
343
344 Ok(())
345}
346
347fn parse_comment<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
349 let start = s.pos();
350 s.advance(4);
351 let text = s.consume_chars(|s, c| !(c == '-' && s.starts_with(b"-->")))?;
352 s.skip_string(b"-->")?;
353
354 if text.contains("--") {
355 return Err(Error::InvalidComment(s.gen_text_pos_from(start)));
356 }
357
358 if text.ends_with('-') {
359 return Err(Error::InvalidComment(s.gen_text_pos_from(start)));
360 }
361
362 let range = s.range_from(start);
363 events.token(Token::Comment(text, range))?;
364
365 Ok(())
366}
367
368fn parse_pi<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
371 if s.starts_with(b"<?xml ") {
372 return Err(Error::UnexpectedDeclaration(s.gen_text_pos()));
373 }
374
375 let start = s.pos();
376 s.advance(2);
377 let target = s.consume_name()?;
378 s.skip_spaces();
379 let content = s.consume_chars(|s, c| !(c == '?' && s.starts_with(b"?>")))?;
380 let content = if !content.is_empty() {
381 Some(content)
382 } else {
383 None
384 };
385
386 s.skip_string(b"?>")?;
387
388 let range = s.range_from(start);
389 events.token(Token::ProcessingInstruction(target, content, range))?;
390 Ok(())
391}
392
393fn parse_doctype<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
394 let start = s.pos();
395 parse_doctype_start(s)?;
396 s.skip_spaces();
397
398 if s.curr_byte() == Ok(b'>') {
399 s.advance(1);
400 return Ok(());
401 }
402
403 s.advance(1); while !s.at_end() {
405 s.skip_spaces();
406 if s.starts_with(b"<!ENTITY") {
407 parse_entity_decl(s, events)?;
408 } else if s.starts_with(b"<!--") {
409 parse_comment(s, events)?;
410 } else if s.starts_with(b"<?") {
411 parse_pi(s, events)?;
412 } else if s.starts_with(b"]") {
413 s.advance(1);
415 s.skip_spaces();
416 match s.curr_byte() {
417 Ok(b'>') => {
418 s.advance(1);
419 break;
420 }
421 Ok(c) => {
422 return Err(Error::InvalidChar2("'>'", c, s.gen_text_pos()));
423 }
424 Err(_) => {
425 return Err(Error::UnexpectedEndOfStream);
426 }
427 }
428 } else if s.starts_with(b"<!ELEMENT")
429 || s.starts_with(b"<!ATTLIST")
430 || s.starts_with(b"<!NOTATION")
431 {
432 if consume_decl(s).is_err() {
433 let pos = s.gen_text_pos_from(start);
434 return Err(Error::UnknownToken(pos));
435 }
436 } else {
437 return Err(Error::UnknownToken(s.gen_text_pos()));
438 }
439 }
440
441 Ok(())
442}
443
444fn parse_doctype_start(s: &mut Stream) -> Result<()> {
446 s.advance(9);
447
448 s.consume_spaces()?;
449 s.skip_name()?;
450 s.skip_spaces();
451
452 let _ = parse_external_id(s)?;
453 s.skip_spaces();
454
455 let c = s.curr_byte()?;
456 if c != b'[' && c != b'>' {
457 return Err(Error::InvalidChar2("'[' or '>'", c, s.gen_text_pos()));
458 }
459
460 Ok(())
461}
462
463fn parse_external_id<'input>(s: &mut Stream<'input>) -> Result<Option<(Option<&'input str>, &'input str)>> {
465 let v = if s.starts_with(b"SYSTEM") || s.starts_with(b"PUBLIC") {
466 let start = s.pos();
467 s.advance(6);
468 let id = s.slice_back(start);
469
470 s.consume_spaces()?;
471 let quote = s.consume_quote()?;
472 let first = s.consume_bytes(|c| c != quote);
473 s.consume_byte(quote)?;
474
475 if id == "SYSTEM" {
476 Some((None, first))
477 } else {
478 s.consume_spaces()?;
479 let quote = s.consume_quote()?;
480 let second = s.consume_bytes(|c| c != quote);
481 s.consume_byte(quote)?;
482
483 Some((Some(first), second))
484 }
485 } else {
486 None
487 };
488
489 Ok(v)
490}
491
492fn parse_entity_decl<'input>(
496 s: &mut Stream<'input>,
497 events: &mut impl XmlEvents<'input>,
498) -> Result<()> {
499 s.advance(8);
500 s.consume_spaces()?;
501
502 let is_ge = if s.try_consume_byte(b'%') {
503 s.consume_spaces()?;
504 false
505 } else {
506 true
507 };
508
509 let name = s.consume_name()?;
510 s.consume_spaces()?;
511 if let Some(definition) = parse_entity_def(s, events, is_ge)? {
512 events.token(Token::EntityDeclaration(name, definition))?;
513 }
514 s.skip_spaces();
515 s.consume_byte(b'>')?;
516
517 Ok(())
518}
519
520fn parse_entity_def<'input>(
527 s: &mut Stream<'input>,
528 events: &mut impl XmlEvents<'input>,
529 is_ge: bool,
530) -> Result<Option<StrSpan<'input>>> {
531 let c = s.curr_byte()?;
532 match c {
533 b'"' | b'\'' => {
534 let quote = s.consume_quote()?;
535 let start = s.pos();
536 s.skip_bytes(|c| c != quote);
537 let value = s.slice_back_span(start);
538 s.consume_byte(quote)?;
539 Ok(Some(value))
540 }
541 b'S' | b'P' => {
542 if let Some((pub_id, uri)) = parse_external_id(s)? {
543 if is_ge {
544 s.skip_spaces();
545 if s.starts_with(b"NDATA") {
546 s.advance(5);
547 s.consume_spaces()?;
548 s.skip_name()?;
549 }
551 }
552
553 let value = events.resolve_entity(pub_id, uri).map_err(|msg| Error::EntityResolver(s.gen_text_pos(), msg))?;
554
555 match value {
556 Some(value) => {
557 let mut stream = Stream::new(value);
558 parse_declaration(&mut stream)?;
559 let value = StrSpan::from(&value[stream.pos..]);
560
561 Ok(Some(value))
562 }
563 None => Ok(None),
564 }
565 } else {
566 Err(Error::InvalidExternalID(s.gen_text_pos()))
567 }
568 }
569 _ => {
570 let pos = s.gen_text_pos();
571 Err(Error::InvalidChar2("a quote, SYSTEM or PUBLIC", c, pos))
572 }
573 }
574}
575
576fn consume_decl(s: &mut Stream) -> Result<()> {
577 s.skip_bytes(|c| c != b'>');
578 s.consume_byte(b'>')?;
579 Ok(())
580}
581
582fn parse_start_tag<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<bool> {
587 let start = s.pos();
588 s.advance(1); let (prefix, local) = s.consume_qname()?;
590 events.token(Token::ElementStart(prefix, local, start))?;
591
592 let mut open = false;
593 while !s.at_end() {
594 let has_space = s.starts_with_space();
595 s.skip_spaces();
596 let start = s.pos();
597 match s.curr_byte()? {
598 b'/' => {
599 s.advance(1);
600 s.consume_byte(b'>')?;
601 let range = s.range_from(start);
602 events.token(Token::ElementEnd(ElementEnd::Empty, range))?;
603 break;
604 }
605 b'>' => {
606 s.advance(1);
607 let range = s.range_from(start);
608 events.token(Token::ElementEnd(ElementEnd::Open, range))?;
609 open = true;
610 break;
611 }
612 _ => {
613 if !has_space {
615 s.consume_spaces()?;
617 }
618
619 let (prefix, local) = s.consume_qname()?;
623 let qname_end = s.pos();
624 let qname_len = u16::try_from(qname_end - start).unwrap_or(u16::MAX);
625 s.consume_eq()?;
626 let eq_len = u8::try_from(s.pos() - qname_end).unwrap_or(u8::MAX);
627 let quote = s.consume_quote()?;
628 let value_start = s.pos();
630 s.advance_until2(quote, b'<')?;
631 let value = s.slice_back_span(value_start);
632 is_xml_str(value.as_str(), value_start, s)?;
633 s.consume_byte(quote)?;
634 let end = s.pos();
635 events.token(Token::Attribute(start..end, qname_len, eq_len, prefix, local, value))?;
636 }
637 }
638 }
639
640 Ok(open)
641}
642
643fn parse_attribute<'input>(
645 s: &mut Stream<'input>,
646) -> Result<(&'input str, &'input str, StrSpan<'input>)> {
647 let (prefix, local) = s.consume_qname()?;
648 s.consume_eq()?;
649 let quote = s.consume_quote()?;
650 let quote_c = quote as char;
651 let value_start = s.pos();
653 s.skip_chars(|_, c| c != quote_c && c != '<')?;
654 let value = s.slice_back_span(value_start);
655 s.consume_byte(quote)?;
656 Ok((prefix, local, value))
657}
658
659pub fn parse_content<'input>(
666 s: &mut Stream<'input>,
667 events: &mut impl XmlEvents<'input>,
668) -> Result<()> {
669 let mut depth: u32 = 0;
670
671 while !s.at_end() {
672 match s.curr_byte() {
673 Ok(b'<') => match s.next_byte() {
674 Ok(b'!') => {
675 if s.starts_with(b"<!--") {
676 parse_comment(s, events)?;
677 } else if s.starts_with(b"<![CDATA[") {
678 parse_cdata(s, events)?;
679 } else {
680 return Err(Error::UnknownToken(s.gen_text_pos()));
681 }
682 }
683 Ok(b'?') => parse_pi(s, events)?,
684 Ok(b'/') => {
685 parse_close_element(s, events)?;
686 if depth == 0 {
687 break;
688 }
689 depth -= 1;
690 }
691 Ok(_) => {
692 if parse_start_tag(s, events)? {
693 depth = depth.checked_add(1).ok_or(Error::NodesLimitReached)?;
694 }
695 }
696 Err(_) => return Err(Error::UnknownToken(s.gen_text_pos())),
697 },
698 Ok(_) => parse_text(s, events)?,
699 Err(_) => return Err(Error::UnknownToken(s.gen_text_pos())),
700 }
701 }
702
703 Ok(())
704}
705
706fn parse_cdata<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
711 let start = s.pos();
712 s.advance(9); let text = s.consume_chars(|s, c| !(c == ']' && s.starts_with(b"]]>")))?;
714 s.skip_string(b"]]>")?;
715 let range = s.range_from(start);
716 events.token(Token::Cdata(text, range))?;
717 Ok(())
718}
719
720fn parse_close_element<'input>(
722 s: &mut Stream<'input>,
723 events: &mut impl XmlEvents<'input>,
724) -> Result<()> {
725 let start = s.pos();
726 s.advance(2); let (prefix, tag_name) = s.consume_qname()?;
729 s.skip_spaces();
730 s.consume_byte(b'>')?;
731
732 let range = s.range_from(start);
733 events.token(Token::ElementEnd(
734 ElementEnd::Close(prefix, tag_name),
735 range,
736 ))?;
737 Ok(())
738}
739
740fn parse_text<'input>(s: &mut Stream<'input>, events: &mut impl XmlEvents<'input>) -> Result<()> {
741 let start = s.pos();
742 let text = s.consume_chars(|_, c| c != '<')?;
743
744 if text.contains('>') && text.contains("]]>") {
749 return Err(Error::InvalidCharacterData(s.gen_text_pos()));
750 }
751
752 let range = s.range_from(start);
753 events.token(Token::Text(text, range))?;
754 Ok(())
755}
756
757#[derive(Clone, Copy)]
759pub enum Reference<'input> {
760 Entity(&'input str),
764
765 Char(char),
769}
770
771#[derive(Clone)]
772pub struct Stream<'input> {
773 pos: usize,
774 end: usize,
775 span: StrSpan<'input>,
776}
777
778impl<'input> Stream<'input> {
779 #[inline]
780 pub fn new(text: &'input str) -> Self {
781 Stream {
782 pos: 0,
783 end: text.len(),
784 span: text.into(),
785 }
786 }
787
788 #[inline]
789 pub fn from_substr(text: &'input str, fragment: Range<usize>) -> Self {
790 Stream {
791 pos: fragment.start,
792 end: fragment.end,
793 span: text.into(),
794 }
795 }
796
797 #[inline]
798 pub fn pos(&self) -> usize {
799 self.pos
800 }
801
802 #[inline]
803 pub fn at_end(&self) -> bool {
804 self.pos >= self.end
805 }
806
807 #[inline]
808 pub fn curr_byte(&self) -> Result<u8> {
809 if self.at_end() {
810 return Err(Error::UnexpectedEndOfStream);
811 }
812
813 Ok(self.curr_byte_unchecked())
814 }
815
816 #[inline]
817 pub fn curr_byte_unchecked(&self) -> u8 {
818 self.span.text.as_bytes()[self.pos]
819 }
820
821 #[inline]
822 fn next_byte(&self) -> Result<u8> {
823 if self.pos + 1 >= self.end {
824 return Err(Error::UnexpectedEndOfStream);
825 }
826
827 Ok(self.span.as_str().as_bytes()[self.pos + 1])
828 }
829
830 #[inline]
831 fn as_bytes(&self) -> &[u8] {
832 &self.span.text.as_bytes()[self.pos..self.end]
833 }
834
835 #[inline]
836 pub fn advance(&mut self, n: usize) {
837 debug_assert!(self.pos + n <= self.end);
838 self.pos += n;
839 }
840
841 #[inline]
842 fn starts_with(&self, text: &[u8]) -> bool {
843 self.span.text.as_bytes()[self.pos..self.end].starts_with(text)
844 }
845
846 fn consume_byte(&mut self, c: u8) -> Result<()> {
847 let curr = self.curr_byte()?;
848 if curr != c {
849 return Err(Error::InvalidChar(c, curr, self.gen_text_pos()));
850 }
851
852 self.advance(1);
853 Ok(())
854 }
855
856 fn try_consume_byte(&mut self, c: u8) -> bool {
858 match self.curr_byte() {
859 Ok(b) if b == c => {
860 self.advance(1);
861 true
862 }
863 _ => false,
864 }
865 }
866
867 fn skip_string(&mut self, text: &'static [u8]) -> Result<()> {
868 if !self.starts_with(text) {
869 let pos = self.gen_text_pos();
870
871 let expected = str::from_utf8(text).unwrap();
873
874 return Err(Error::InvalidString(expected, pos));
875 }
876
877 self.advance(text.len());
878 Ok(())
879 }
880
881 #[inline]
882 fn consume_bytes<F: Fn(u8) -> bool>(&mut self, f: F) -> &'input str {
883 let start = self.pos;
884 self.skip_bytes(f);
885 self.slice_back(start)
886 }
887
888 fn skip_bytes<F: Fn(u8) -> bool>(&mut self, f: F) {
889 while !self.at_end() && f(self.curr_byte_unchecked()) {
890 self.advance(1);
891 }
892 }
893
894 #[inline]
895 fn consume_chars<F>(&mut self, f: F) -> Result<&'input str>
896 where
897 F: Fn(&Stream, char) -> bool,
898 {
899 let start = self.pos;
900 self.skip_chars(f)?;
901 Ok(self.slice_back(start))
902 }
903
904 #[inline]
905 fn skip_chars<F>(&mut self, f: F) -> Result<()>
906 where
907 F: Fn(&Stream, char) -> bool,
908 {
909 for c in self.chars() {
910 if !c.is_xml_char() {
911 return Err(Error::NonXmlChar(c, self.gen_text_pos()));
912 } else if f(self, c) {
913 self.advance(c.len_utf8());
914 } else {
915 break;
916 }
917 }
918
919 Ok(())
920 }
921
922 #[inline]
923 fn advance_until2(&mut self, needle1: u8, needle2: u8) -> Result<()> {
924 match memchr2(needle1, needle2, self.as_bytes()) {
925 Some(pos) => {
926 self.advance(pos);
927 Ok(())
928 }
929 None => Err(Error::UnexpectedEndOfStream),
930 }
931 }
932
933 #[inline]
934 fn chars(&self) -> str::Chars<'input> {
935 self.span.as_str()[self.pos..self.end].chars()
936 }
937
938 #[inline]
939 fn slice_back(&self, pos: usize) -> &'input str {
940 self.span.slice_region(pos, self.pos)
941 }
942
943 #[inline]
944 fn slice_back_span(&self, pos: usize) -> StrSpan<'input> {
945 StrSpan::from_substr(self.span.text, pos, self.pos)
946 }
947
948 #[inline]
949 fn range_from(&self, start: usize) -> Range<usize> {
950 start..self.pos
951 }
952
953 #[inline]
954 fn skip_spaces(&mut self) {
955 while self.starts_with_space() {
956 self.advance(1);
957 }
958 }
959
960 #[inline]
961 fn starts_with_space(&self) -> bool {
962 !self.at_end() && self.curr_byte_unchecked().is_xml_space()
963 }
964
965 fn consume_spaces(&mut self) -> Result<()> {
967 if self.at_end() {
968 return Err(Error::UnexpectedEndOfStream);
969 }
970
971 if !self.starts_with_space() {
972 return Err(Error::InvalidChar2(
973 "a whitespace",
974 self.curr_byte_unchecked(),
975 self.gen_text_pos(),
976 ));
977 }
978
979 self.skip_spaces();
980 Ok(())
981 }
982
983 #[inline(never)]
985 pub fn consume_reference(&mut self) -> Option<Reference<'input>> {
986 if !self.try_consume_byte(b'&') {
987 return None;
988 }
989
990 let reference = if self.try_consume_byte(b'#') {
991 let (value, radix) = if self.try_consume_byte(b'x') {
992 let value =
993 self.consume_bytes(|c| c.is_ascii_hexdigit());
994 (value, 16)
995 } else {
996 let value = self.consume_bytes(|c| c.is_ascii_digit());
997 (value, 10)
998 };
999
1000 let n = u32::from_str_radix(value, radix).ok()?;
1001
1002 let c = char::from_u32(n).unwrap_or('\u{FFFD}');
1003 if !c.is_xml_char() {
1004 return None;
1005 }
1006
1007 Reference::Char(c)
1008 } else {
1009 let name = self.consume_name().ok()?;
1010 match name {
1011 "quot" => Reference::Char('"'),
1012 "amp" => Reference::Char('&'),
1013 "apos" => Reference::Char('\''),
1014 "lt" => Reference::Char('<'),
1015 "gt" => Reference::Char('>'),
1016 _ => Reference::Entity(name),
1017 }
1018 };
1019
1020 self.consume_byte(b';').ok()?;
1021
1022 Some(reference)
1023 }
1024
1025 fn consume_name(&mut self) -> Result<&'input str> {
1027 let start = self.pos();
1028 self.skip_name()?;
1029
1030 let name = self.slice_back(start);
1031 if name.is_empty() {
1032 return Err(Error::InvalidName(self.gen_text_pos_from(start)));
1033 }
1034
1035 Ok(name)
1036 }
1037
1038 fn skip_name(&mut self) -> Result<()> {
1040 let start = self.pos();
1041 let mut iter = self.chars();
1042 if let Some(c) = iter.next() {
1043 if c.is_xml_name_start() {
1044 self.advance(c.len_utf8());
1045 } else {
1046 return Err(Error::InvalidName(self.gen_text_pos_from(start)));
1047 }
1048 }
1049
1050 for c in iter {
1051 if c.is_xml_name() {
1052 self.advance(c.len_utf8());
1053 } else {
1054 break;
1055 }
1056 }
1057
1058 Ok(())
1059 }
1060
1061 #[inline(never)]
1065 fn consume_qname(&mut self) -> Result<(&'input str, &'input str)> {
1066 let start = self.pos();
1067
1068 let mut splitter = None;
1069
1070 while !self.at_end() {
1071 let b = self.curr_byte_unchecked();
1073 if b < 128 {
1074 if b == b':' {
1075 if splitter.is_none() {
1076 splitter = Some(self.pos());
1077 self.advance(1);
1078 } else {
1079 return Err(Error::InvalidName(self.gen_text_pos_from(start)));
1081 }
1082 } else if b.is_xml_name() {
1083 self.advance(1);
1084 } else {
1085 break;
1086 }
1087 } else {
1088 match self.chars().next() {
1090 Some(c) if c.is_xml_name() => {
1091 self.advance(c.len_utf8());
1092 }
1093 _ => break,
1094 }
1095 }
1096 }
1097
1098 let (prefix, local) = if let Some(splitter) = splitter {
1099 let prefix = self.span.slice_region(start, splitter);
1100 let local = self.slice_back(splitter + 1);
1101 (prefix, local)
1102 } else {
1103 let local = self.slice_back(start);
1104 (self.span.slice_region(start, start), local)
1106 };
1107
1108 fn is_xml_name_start(name: &str) -> bool {
1109 if let Some(b) = name.as_bytes().first() {
1110 if *b < 128 {
1111 return b.is_xml_name_start();
1112 } else if let Some(c) = name.chars().next() {
1113 return c.is_xml_name_start();
1114 }
1115 }
1116
1117 false
1118 }
1119
1120 if !prefix.is_empty() && !is_xml_name_start(prefix) {
1122 return Err(Error::InvalidName(self.gen_text_pos_from(start)));
1123 }
1124
1125 if !is_xml_name_start(local) {
1127 return Err(Error::InvalidName(self.gen_text_pos_from(start)));
1128 }
1129
1130 Ok((prefix, local))
1131 }
1132
1133 fn consume_eq(&mut self) -> Result<()> {
1134 self.skip_spaces();
1135 self.consume_byte(b'=')?;
1136 self.skip_spaces();
1137
1138 Ok(())
1139 }
1140
1141 fn consume_quote(&mut self) -> Result<u8> {
1142 let c = self.curr_byte()?;
1143 if c == b'\'' || c == b'"' {
1144 self.advance(1);
1145 Ok(c)
1146 } else {
1147 Err(Error::InvalidChar2("a quote", c, self.gen_text_pos()))
1148 }
1149 }
1150
1151 #[inline(never)]
1155 pub fn gen_text_pos(&self) -> TextPos {
1156 let text = self.span.as_str();
1157 let end = self.pos;
1158
1159 let row = Self::calc_curr_row(text, end);
1160 let col = Self::calc_curr_col(text, end);
1161 TextPos::new(row, col)
1162 }
1163
1164 #[inline(never)]
1168 pub fn gen_text_pos_from(&self, pos: usize) -> TextPos {
1169 let mut s = self.clone();
1170 s.pos = core::cmp::min(pos, s.span.as_str().len());
1171 s.gen_text_pos()
1172 }
1173
1174 fn calc_curr_row(text: &str, end: usize) -> u32 {
1175 let mut row = 1;
1176 for c in &text.as_bytes()[..end] {
1177 if *c == b'\n' {
1178 row += 1;
1179 }
1180 }
1181
1182 row
1183 }
1184
1185 fn calc_curr_col(text: &str, end: usize) -> u32 {
1186 let mut col = 1;
1187 for c in text[..end].chars().rev() {
1188 if c == '\n' {
1189 break;
1190 } else {
1191 col += 1;
1192 }
1193 }
1194
1195 col
1196 }
1197}