1use alloc::{
9 borrow::{Cow, ToOwned},
10 format,
11 string::{String, ToString},
12 vec::Vec,
13};
14use core::ops::Range;
15
16#[cfg(feature = "std")]
17use std::{
18 collections::HashSet,
19 io::{Error as IoError, ErrorKind, Write},
20};
21
22#[derive(Debug, thiserror::Error)]
24#[error("XML lexical error: {message}")]
25pub struct Error {
26 message: String,
27}
28
29impl Error {
30 fn tokenizer(error: xmlparser::Error) -> Self {
31 Self {
32 message: error.to_string(),
33 }
34 }
35
36 fn malformed(message: impl Into<String>) -> Self {
37 Self {
38 message: message.into(),
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
45pub struct Name<'a> {
46 prefix: &'a str,
47 local: &'a str,
48}
49
50impl<'a> Name<'a> {
51 #[must_use]
53 pub fn prefix(self) -> Option<&'a str> {
54 (!self.prefix.is_empty()).then_some(self.prefix)
55 }
56
57 #[must_use]
59 pub const fn local(self) -> &'a str {
60 self.local
61 }
62
63 #[must_use]
65 pub fn is_qualified(self, value: &str) -> bool {
66 match self.prefix() {
67 Some(prefix) => {
68 value
69 .strip_prefix(prefix)
70 .and_then(|suffix| suffix.strip_prefix(':'))
71 == Some(self.local)
72 }
73 None => value == self.local,
74 }
75 }
76
77 #[must_use]
79 pub fn qualified(self) -> Cow<'a, str> {
80 self.prefix().map_or_else(
81 || Cow::Borrowed(self.local),
82 |prefix| Cow::Owned(format!("{prefix}:{}", self.local)),
83 )
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct Attribute<'a> {
90 pub name: Name<'a>,
92 pub value: &'a str,
94 pub range: Range<usize>,
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct StartTag<'a> {
101 pub name: Name<'a>,
103 pub attributes: Vec<Attribute<'a>>,
105 pub range: Range<usize>,
107}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
111pub enum Event<'a> {
112 Declaration {
114 version: &'a str,
116 standalone: Option<bool>,
118 range: Range<usize>,
120 },
121 ProcessingInstruction {
123 target: &'a str,
125 content: Option<&'a str>,
127 range: Range<usize>,
129 },
130 Comment {
132 text: &'a str,
134 range: Range<usize>,
136 },
137 DocType {
139 name: &'a str,
141 range: Range<usize>,
143 },
144 Start(StartTag<'a>),
146 Empty(StartTag<'a>),
148 End {
150 name: Name<'a>,
152 range: Range<usize>,
154 },
155 Text {
157 text: &'a str,
159 range: Range<usize>,
161 },
162 CData {
164 text: &'a str,
166 range: Range<usize>,
168 },
169 Reference {
171 name: &'a str,
173 range: Range<usize>,
175 },
176}
177
178pub struct Scanner<'a> {
180 input: &'a str,
181 tokenizer: xmlparser::Tokenizer<'a>,
182 pending_text: Option<PendingText<'a>>,
183 pending_start: Option<PendingStart<'a>>,
184 dtd_start: Option<(usize, &'a str)>,
185}
186
187struct PendingStart<'a> {
188 name: Name<'a>,
189 attributes: Vec<Attribute<'a>>,
190 start: usize,
191}
192
193struct PendingText<'a> {
194 text: &'a str,
195 range: Range<usize>,
196 offset: usize,
197}
198
199fn validate_unique_attributes(attributes: &mut [Attribute<'_>]) -> Result<(), Error> {
200 const SMALL_TAG_ATTRIBUTES: usize = 8;
201
202 if attributes.len() <= SMALL_TAG_ATTRIBUTES {
203 for index in 1..attributes.len() {
204 let name = attributes[index].name;
205 if attributes[..index]
206 .iter()
207 .any(|attribute| attribute.name == name)
208 {
209 return Err(Error::malformed(format!(
210 "duplicate attribute `{}`",
211 name.qualified()
212 )));
213 }
214 }
215 return Ok(());
216 }
217
218 attributes.sort_unstable_by_key(|attribute| attribute.name);
219 if let Some(name) = attributes
220 .windows(2)
221 .find_map(|pair| (pair[0].name == pair[1].name).then_some(pair[0].name))
222 {
223 return Err(Error::malformed(format!(
224 "duplicate attribute `{}`",
225 name.qualified()
226 )));
227 }
228 attributes.sort_unstable_by_key(|attribute| attribute.range.start);
229 Ok(())
230}
231
232impl<'a> Scanner<'a> {
233 #[must_use]
235 pub fn new(input: &'a str) -> Self {
236 Self {
237 input,
238 tokenizer: xmlparser::Tokenizer::from(input),
239 pending_text: None,
240 pending_start: None,
241 dtd_start: None,
242 }
243 }
244
245 pub fn next_event(&mut self) -> Result<Option<Event<'a>>, Error> {
247 if let Some(event) = self.next_text_event()? {
248 return Ok(Some(event));
249 }
250 loop {
251 let Some(token) = self.tokenizer.next() else {
252 if self.pending_start.is_some() {
253 return Err(Error::malformed("unterminated element start tag"));
254 }
255 return Ok(None);
256 };
257 let token = token.map_err(Error::tokenizer)?;
258 use xmlparser::{ElementEnd, Token};
259 match &token {
260 Token::DtdStart { name, span, .. } => {
261 self.dtd_start = Some((span.range().start, name.as_str()));
262 continue;
263 }
264 Token::DtdEnd { span } if self.dtd_start.is_some() => {
265 let (start, name) = self.dtd_start.take().expect("DTD start is present");
266 return Ok(Some(Event::DocType {
267 name,
268 range: start..span.range().end,
269 }));
270 }
271 _ if self.dtd_start.is_some() => {
272 continue;
275 }
276 _ => {}
277 }
278 match token {
279 Token::Declaration {
280 version,
281 standalone,
282 span,
283 ..
284 } => {
285 if version.as_str() != "1.0" {
291 return Err(Error::malformed("unsupported XML version"));
292 }
293 return Ok(Some(Event::Declaration {
294 version: version.as_str(),
295 standalone,
296 range: span.range(),
297 }));
298 }
299 Token::ProcessingInstruction {
300 target,
301 content,
302 span,
303 } => {
304 return Ok(Some(Event::ProcessingInstruction {
305 target: target.as_str(),
306 content: content.map(|value| value.as_str()),
307 range: span.range(),
308 }));
309 }
310 Token::Comment { text, span } => {
311 return Ok(Some(Event::Comment {
312 text: text.as_str(),
313 range: span.range(),
314 }));
315 }
316 Token::DtdStart { .. } => unreachable!("DTD start is handled above"),
317 Token::EmptyDtd { name, span, .. } => {
318 return Ok(Some(Event::DocType {
319 name: name.as_str(),
320 range: span.range(),
321 }));
322 }
323 Token::DtdEnd { .. } => {
324 return Err(Error::malformed("DOCTYPE end without a start"));
325 }
326 Token::EntityDeclaration { .. } => {}
327 Token::ElementStart {
328 prefix,
329 local,
330 span,
331 } => {
332 validate_qualified_lexeme(&self.input[span.range()])?;
333 if self.pending_start.is_some() {
334 return Err(Error::malformed("nested element start tokens"));
335 }
336 self.pending_start = Some(PendingStart {
337 name: Name {
338 prefix: prefix.as_str(),
339 local: local.as_str(),
340 },
341 attributes: Vec::new(),
342 start: span.range().start,
343 });
344 }
345 Token::Attribute {
346 prefix,
347 local,
348 value,
349 span,
350 } => {
351 validate_qualified_lexeme(&self.input[span.range()])?;
352 let start = self
353 .pending_start
354 .as_mut()
355 .ok_or_else(|| Error::malformed("attribute outside a start tag"))?;
356 let name = Name {
357 prefix: prefix.as_str(),
358 local: local.as_str(),
359 };
360 start.attributes.push(Attribute {
361 name,
362 value: value.as_str(),
363 range: span.range(),
364 });
365 }
366 Token::ElementEnd { end, span } => match end {
367 ElementEnd::Open | ElementEnd::Empty => {
368 let mut start = self
369 .pending_start
370 .take()
371 .ok_or_else(|| Error::malformed("element end without a start"))?;
372 validate_unique_attributes(&mut start.attributes)?;
373 let tag = StartTag {
374 name: start.name,
375 attributes: start.attributes,
376 range: start.start..span.range().end,
377 };
378 return Ok(Some(if end == ElementEnd::Open {
379 Event::Start(tag)
380 } else {
381 Event::Empty(tag)
382 }));
383 }
384 ElementEnd::Close(prefix, local) => {
385 validate_qualified_lexeme(&self.input[span.range()])?;
386 return Ok(Some(Event::End {
387 name: Name {
388 prefix: prefix.as_str(),
389 local: local.as_str(),
390 },
391 range: span.range(),
392 }));
393 }
394 },
395 Token::Text { text } => {
396 self.pending_text = Some(PendingText {
397 text: text.as_str(),
398 range: text.range(),
399 offset: 0,
400 });
401 if let Some(event) = self.next_text_event()? {
402 return Ok(Some(event));
403 }
404 }
405 Token::Cdata { text, span } => {
406 return Ok(Some(Event::CData {
407 text: text.as_str(),
408 range: span.range(),
409 }));
410 }
411 }
412 }
413 }
414
415 fn next_text_event(&mut self) -> Result<Option<Event<'a>>, Error> {
416 let Some(mut pending) = self.pending_text.take() else {
417 return Ok(None);
418 };
419 let tail = &pending.text[pending.offset..];
420 let Some(relative) = tail.find('&') else {
421 return Ok((!tail.is_empty()).then_some(Event::Text {
422 text: tail,
423 range: pending.range.start + pending.offset..pending.range.end,
424 }));
425 };
426 let start = pending.offset + relative;
427 if start > pending.offset {
428 let event = Event::Text {
429 text: &pending.text[pending.offset..start],
430 range: pending.range.start + pending.offset..pending.range.start + start,
431 };
432 pending.offset = start;
433 self.pending_text = Some(pending);
434 return Ok(Some(event));
435 }
436 let Some(relative_end) = pending.text[start + 1..].find(';') else {
437 return Err(Error::malformed(
438 "unterminated XML reference in character data",
439 ));
440 };
441 let end = start + 1 + relative_end;
442 let event = Event::Reference {
443 name: &pending.text[start + 1..end],
444 range: pending.range.start + start..pending.range.start + end + 1,
445 };
446 pending.offset = end + 1;
447 if pending.offset < pending.text.len() {
448 self.pending_text = Some(pending);
449 }
450 Ok(Some(event))
451 }
452
453 #[must_use]
455 pub const fn input(&self) -> &'a str {
456 self.input
457 }
458}
459
460fn validate_qualified_lexeme(source: &str) -> Result<(), Error> {
461 let source = source
465 .strip_prefix("</")
466 .or_else(|| source.strip_prefix('<'))
467 .unwrap_or(source);
468 let name = source
469 .split(|character: char| {
470 character.is_ascii_whitespace() || matches!(character, '=' | '/' | '>')
471 })
472 .next()
473 .unwrap_or_default();
474 let mut parts = name.split(':');
475 let first = parts.next().unwrap_or_default();
476 let second = parts.next();
477 if first.is_empty() || second.is_some_and(str::is_empty) || parts.next().is_some() {
478 return Err(Error::malformed("invalid qualified XML name"));
479 }
480 Ok(())
481}
482
483#[must_use]
485pub fn is_qname(value: &str) -> bool {
486 let mut parts = value.split(':');
487 let first = parts.next().unwrap_or_default();
488 !first.is_empty()
489 && is_ncname(first)
490 && parts.next().is_none_or(is_ncname)
491 && parts.next().is_none()
492}
493
494fn is_ncname(value: &str) -> bool {
495 let mut characters = value.chars();
496 let Some(first) = characters.next() else {
497 return false;
498 };
499 is_ncname_start(first) && characters.all(is_ncname_char)
500}
501
502fn is_ncname_start(character: char) -> bool {
503 matches!(
504 character,
505 'A'..='Z'
506 | '_'
507 | 'a'..='z'
508 | '\u{C0}'..='\u{D6}'
509 | '\u{D8}'..='\u{F6}'
510 | '\u{F8}'..='\u{2FF}'
511 | '\u{370}'..='\u{37D}'
512 | '\u{37F}'..='\u{1FFF}'
513 | '\u{200C}'..='\u{200D}'
514 | '\u{2070}'..='\u{218F}'
515 | '\u{2C00}'..='\u{2FEF}'
516 | '\u{3001}'..='\u{D7FF}'
517 | '\u{F900}'..='\u{FDCF}'
518 | '\u{FDF0}'..='\u{FFFD}'
519 | '\u{10000}'..='\u{EFFFF}'
520 )
521}
522
523fn is_ncname_char(character: char) -> bool {
524 is_ncname_start(character)
525 || matches!(
526 character,
527 '-' | '.' | '0'..='9' | '\u{B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}'
528 )
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
535pub struct DeclaredNamespacePrefixes(Vec<String>);
536
537impl DeclaredNamespacePrefixes {
538 #[must_use]
540 pub fn contains(&self, prefix: &str) -> bool {
541 self.0.iter().any(|candidate| candidate == prefix)
542 }
543}
544
545pub fn declared_namespace_prefixes(opening: &str) -> Result<DeclaredNamespacePrefixes, Error> {
547 let standalone = format!("{} />", opening.trim_end_matches('/'));
548 let mut scanner = Scanner::new(&standalone);
549 let Some(Event::Start(tag) | Event::Empty(tag)) = scanner.next_event()? else {
550 return Err(Error::malformed("expected one opening element tag"));
551 };
552 Ok(DeclaredNamespacePrefixes(
553 tag.attributes
554 .iter()
555 .filter_map(
556 |attribute| match (attribute.name.prefix(), attribute.name.local()) {
557 (None, "xmlns") => Some(String::new()),
558 (Some("xmlns"), prefix) => Some(prefix.to_owned()),
559 _ => None,
560 },
561 )
562 .collect(),
563 ))
564}
565
566pub fn decode_references(value: &str) -> Result<Cow<'_, str>, Error> {
568 if !value.contains('&') {
569 return Ok(Cow::Borrowed(value));
570 }
571 let mut output = String::with_capacity(value.len());
572 let mut offset = 0;
573 while let Some(relative) = value[offset..].find('&') {
574 let start = offset + relative;
575 output.push_str(&value[offset..start]);
576 let end = value[start + 1..]
577 .find(';')
578 .map(|relative| start + 1 + relative)
579 .ok_or_else(|| Error::malformed("unterminated XML reference"))?;
580 let name = &value[start + 1..end];
581 let character = match name {
582 "amp" => '&',
583 "apos" => '\'',
584 "gt" => '>',
585 "lt" => '<',
586 "quot" => '"',
587 value if value.starts_with('#') => decode_numeric_character_reference(&value[1..])?,
588 _ => {
589 return Err(Error::malformed(format!(
590 "unresolved entity reference &{name};"
591 )));
592 }
593 };
594 output.push(character);
595 offset = end + 1;
596 }
597 output.push_str(&value[offset..]);
598 Ok(Cow::Owned(output))
599}
600
601pub fn decode_numeric_character_reference(reference: &str) -> Result<char, Error> {
606 let (digits, radix) = reference
607 .strip_prefix('x')
608 .map_or((reference, 10), |digits| (digits, 16));
609 let valid_digits = if radix == 16 {
610 digits.bytes().all(|byte| byte.is_ascii_hexdigit())
611 } else {
612 digits.bytes().all(|byte| byte.is_ascii_digit())
613 };
614 if digits.is_empty() || !valid_digits {
615 return Err(Error::malformed("invalid XML character reference"));
616 }
617 u32::from_str_radix(digits, radix)
618 .ok()
619 .and_then(char::from_u32)
620 .filter(|character| is_xml_1_0_character(*character))
621 .ok_or_else(|| Error::malformed("invalid XML character reference"))
622}
623
624fn is_xml_1_0_character(character: char) -> bool {
625 matches!(character, '\u{9}' | '\u{A}' | '\u{D}' | '\u{20}'..='\u{D7FF}' | '\u{E000}'..='\u{FFFD}' | '\u{10000}'..='\u{10FFFF}')
626}
627
628#[must_use]
630pub fn escape_text(value: &str) -> Cow<'_, str> {
631 escape(value, false)
632}
633
634#[must_use]
636pub fn escape_attribute(value: &str) -> Cow<'_, str> {
637 escape(value, true)
638}
639
640fn escape(value: &str, attribute: bool) -> Cow<'_, str> {
641 if !value.bytes().any(|byte| {
642 matches!(byte, b'&' | b'<' | b'>' | b'\r')
643 || (attribute && matches!(byte, b'"' | b'\n' | b'\t'))
644 }) {
645 return Cow::Borrowed(value);
646 }
647 let mut output = String::with_capacity(value.len());
648 for character in value.chars() {
649 match character {
650 '&' => output.push_str("&"),
651 '<' => output.push_str("<"),
652 '>' => output.push_str(">"),
653 '"' if attribute => output.push_str("""),
654 '\t' if attribute => output.push_str("	"),
655 '\n' if attribute => output.push_str(" "),
656 '\r' => output.push_str(" "),
657 _ => output.push(character),
658 }
659 }
660 Cow::Owned(output)
661}
662
663#[cfg(feature = "std")]
665pub struct Writer<W> {
666 output: W,
667 frames: Vec<WriterFrame>,
668}
669
670#[cfg(feature = "std")]
671struct WriterFrame {
672 element_name: String,
673 namespaces: Vec<(String, String)>,
674}
675
676#[cfg(feature = "std")]
677impl<W: Write> Writer<W> {
678 #[must_use]
680 pub const fn new(output: W) -> Self {
681 Self {
682 output,
683 frames: Vec::new(),
684 }
685 }
686
687 pub fn start<'a>(
689 &mut self,
690 name: &str,
691 attributes: impl IntoIterator<Item = (&'a str, &'a str)>,
692 ) -> std::io::Result<()> {
693 self.tag(name, attributes, false)
694 }
695
696 pub fn empty<'a>(
698 &mut self,
699 name: &str,
700 attributes: impl IntoIterator<Item = (&'a str, &'a str)>,
701 ) -> std::io::Result<()> {
702 self.tag(name, attributes, true)
703 }
704
705 fn tag<'a>(
706 &mut self,
707 name: &str,
708 attributes: impl IntoIterator<Item = (&'a str, &'a str)>,
709 empty: bool,
710 ) -> std::io::Result<()> {
711 validate_writer_qname(name)?;
712 let attributes = attributes.into_iter().collect::<Vec<_>>();
713 validate_writer_element_namespace(name, &attributes, &self.frames)?;
714 validate_writer_attributes(&attributes, &self.frames)?;
715 write!(self.output, "<{name}")?;
716 for (attribute, value) in &attributes {
717 write!(self.output, " {attribute}=\"{}\"", escape_attribute(value))?;
718 }
719 self.output.write_all(if empty { b"/>" } else { b">" })?;
720 if !empty {
721 self.frames.push(WriterFrame {
722 element_name: name.to_owned(),
723 namespaces: attributes
724 .iter()
725 .filter_map(|(name, uri)| namespace_declaration(name).map(|name| (name, *uri)))
726 .map(|(name, uri)| (name.to_owned(), uri.to_owned()))
727 .collect(),
728 });
729 }
730 Ok(())
731 }
732
733 pub fn end(&mut self, name: &str) -> std::io::Result<()> {
735 validate_writer_qname(name)?;
736 let frame = self.frames.last().ok_or_else(|| {
739 IoError::new(ErrorKind::InvalidInput, "XML end tag has no open element")
740 })?;
741 if frame.element_name != name {
742 return Err(IoError::new(
743 ErrorKind::InvalidInput,
744 format!(
745 "XML end tag `{name}` does not match open element `{}`",
746 frame.element_name
747 ),
748 ));
749 }
750 write!(self.output, "</{name}>")?;
751 self.frames.pop();
752 Ok(())
753 }
754
755 pub fn text(&mut self, value: &str) -> std::io::Result<()> {
757 validate_writer_characters(value)?;
758 write!(self.output, "{}", escape_text(value))
759 }
760
761 pub fn raw(&mut self, value: &str) -> std::io::Result<()> {
763 self.output.write_all(value.as_bytes())
764 }
765
766 #[must_use]
768 pub fn into_inner(self) -> W {
769 self.output
770 }
771}
772
773#[cfg(feature = "std")]
774fn validate_writer_attributes(
775 attributes: &[(&str, &str)],
776 frames: &[WriterFrame],
777) -> std::io::Result<()> {
778 const SMALL_TAG_ATTRIBUTES: usize = 8;
779
780 for (name, value) in attributes {
781 validate_writer_qname(name)?;
782 validate_writer_characters(value)?;
783 validate_writer_namespace_declaration(name, value)?;
784 }
785 if attributes.len() <= SMALL_TAG_ATTRIBUTES {
786 for index in 1..attributes.len() {
787 if attributes[..index]
788 .iter()
789 .any(|(name, _)| *name == attributes[index].0)
790 {
791 return Err(IoError::new(
792 ErrorKind::InvalidInput,
793 format!("duplicate XML attribute `{}`", attributes[index].0),
794 ));
795 }
796 }
797 } else {
798 let mut names = HashSet::with_capacity(attributes.len());
799 if let Some((duplicate, _)) = attributes.iter().find(|(name, _)| !names.insert(*name)) {
800 return Err(IoError::new(
801 ErrorKind::InvalidInput,
802 format!("duplicate XML attribute `{duplicate}`"),
803 ));
804 }
805 }
806
807 let mut expanded = HashSet::with_capacity(attributes.len());
808 for (name, _) in attributes {
809 let Some((prefix, local)) = name.split_once(':') else {
810 if name != &"xmlns" && !expanded.insert((None, *name)) {
811 return duplicate_expanded_attribute(name);
812 }
813 continue;
814 };
815 if prefix == "xmlns" {
816 continue;
817 }
818 let namespace = resolve_writer_prefix(prefix, attributes, frames).ok_or_else(|| {
819 IoError::new(
820 ErrorKind::InvalidInput,
821 format!("unbound XML namespace prefix `{prefix}`"),
822 )
823 })?;
824 if namespace.is_empty() {
825 return Err(IoError::new(
826 ErrorKind::InvalidInput,
827 format!("unbound XML namespace prefix `{prefix}`"),
828 ));
829 }
830 if !expanded.insert((Some(namespace), local)) {
831 return duplicate_expanded_attribute(name);
832 }
833 }
834 Ok(())
835}
836
837#[cfg(feature = "std")]
838fn validate_writer_element_namespace(
839 name: &str,
840 attributes: &[(&str, &str)],
841 frames: &[WriterFrame],
842) -> std::io::Result<()> {
843 let Some((prefix, _)) = name.split_once(':') else {
844 return Ok(());
845 };
846 if prefix == "xmlns"
850 || resolve_writer_prefix(prefix, attributes, frames).is_none_or(str::is_empty)
851 {
852 return Err(IoError::new(
853 ErrorKind::InvalidInput,
854 format!("unbound XML namespace prefix `{prefix}`"),
855 ));
856 }
857 Ok(())
858}
859
860#[cfg(feature = "std")]
861fn validate_writer_namespace_declaration(name: &str, uri: &str) -> std::io::Result<()> {
862 const XML_NAMESPACE: &str = "http://www.w3.org/XML/1998/namespace";
863 const XMLNS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns/";
864
865 let Some(prefix) = namespace_declaration(name) else {
866 return Ok(());
867 };
868 let valid = if prefix == "xml" {
872 uri == XML_NAMESPACE
873 } else if prefix == "xmlns" {
874 false
875 } else {
876 uri != XML_NAMESPACE && uri != XMLNS_NAMESPACE && (prefix.is_empty() || !uri.is_empty())
877 };
878 if valid {
879 Ok(())
880 } else {
881 Err(IoError::new(
882 ErrorKind::InvalidInput,
883 format!("invalid XML namespace declaration `{name}={uri}`"),
884 ))
885 }
886}
887
888#[cfg(feature = "std")]
889fn namespace_declaration(name: &str) -> Option<&str> {
890 if name == "xmlns" {
891 Some("")
892 } else {
893 name.strip_prefix("xmlns:")
894 }
895}
896
897#[cfg(feature = "std")]
898fn resolve_writer_prefix<'a>(
899 prefix: &str,
900 attributes: &'a [(&str, &str)],
901 frames: &'a [WriterFrame],
902) -> Option<&'a str> {
903 if prefix == "xml" {
904 return Some("http://www.w3.org/XML/1998/namespace");
905 }
906 attributes
907 .iter()
908 .find_map(|(name, uri)| (namespace_declaration(name) == Some(prefix)).then_some(*uri))
909 .or_else(|| {
910 frames.iter().rev().find_map(|frame| {
911 frame
912 .namespaces
913 .iter()
914 .rev()
915 .find_map(|(name, uri)| (name == prefix).then_some(uri.as_str()))
916 })
917 })
918}
919
920#[cfg(feature = "std")]
921fn duplicate_expanded_attribute(name: &str) -> std::io::Result<()> {
922 Err(IoError::new(
923 ErrorKind::InvalidInput,
924 format!("duplicate expanded XML attribute `{name}`"),
925 ))
926}
927
928#[cfg(feature = "std")]
929fn validate_writer_qname(name: &str) -> std::io::Result<()> {
930 if is_qname(name) {
933 Ok(())
934 } else {
935 Err(IoError::new(ErrorKind::InvalidInput, "invalid XML QName"))
936 }
937}
938
939#[cfg(feature = "std")]
940fn validate_writer_characters(value: &str) -> std::io::Result<()> {
941 if let Some(character) = value
945 .chars()
946 .find(|character| !is_xml_1_0_character(*character))
947 {
948 Err(IoError::new(
949 ErrorKind::InvalidInput,
950 format!(
951 "character U+{:04X} is forbidden by XML 1.0",
952 u32::from(character)
953 ),
954 ))
955 } else {
956 Ok(())
957 }
958}
959
960#[cfg(all(test, feature = "std"))]
961#[allow(clippy::unwrap_used)]
962mod tests {
963 #[test]
964 fn xml_version_requires_digits_after_the_period() {
965 assert!(
967 super::Scanner::new("<?xml version='1.'?><r/>")
968 .next_event()
969 .is_err()
970 );
971 assert!(
972 super::Scanner::new("<?xml version='1.0'?><r/>")
973 .next_event()
974 .is_ok()
975 );
976 }
977
978 #[test]
979 fn scanner_rejects_xml_11_until_character_rules_are_version_aware() {
980 assert!(
982 super::Scanner::new("<?xml version='1.1'?><r></r>")
983 .next_event()
984 .is_err()
985 );
986 }
987 use super::*;
988
989 #[test]
990 fn scanner_groups_start_tags_and_splits_references() {
991 let xml = "<p xmlns='urn:test' a='x&y'>a&b<![CDATA[c]]></p>";
992 let mut scanner = Scanner::new(xml);
993 let events = std::iter::from_fn(|| scanner.next_event().transpose())
994 .collect::<Result<Vec<_>, _>>()
995 .expect("fixture must scan");
996 assert!(matches!(&events[0], Event::Start(tag) if tag.attributes.len() == 2));
997 assert!(matches!(&events[1], Event::Text { text: "a", .. }));
998 assert!(matches!(&events[2], Event::Reference { name: "amp", .. }));
999 assert!(matches!(&events[3], Event::Reference { name: "#x62", .. }));
1000 assert!(matches!(&events[4], Event::CData { text: "c", .. }));
1001 assert!(matches!(&events[5], Event::End { .. }));
1002 }
1003
1004 #[test]
1005 fn scanner_streams_dense_reference_runs_without_buffering_events() {
1006 let xml = format!("<root>{}</root>", "&".repeat(4096));
1009 let mut scanner = Scanner::new(&xml);
1010 assert!(matches!(
1011 scanner.next_event().expect("scan root start"),
1012 Some(Event::Start(_))
1013 ));
1014 assert!(matches!(
1015 scanner.next_event().expect("scan first reference"),
1016 Some(Event::Reference { name: "amp", .. })
1017 ));
1018 assert_eq!(
1019 scanner.pending_text.as_ref().map(|pending| pending.offset),
1020 Some("&".len()),
1021 "the scanner retains only a cursor into the source token"
1022 );
1023 assert_eq!(
1024 std::iter::from_fn(|| scanner.next_event().transpose())
1025 .collect::<Result<Vec<_>, _>>()
1026 .expect("remaining references scan")
1027 .len(),
1028 4096
1029 );
1030 }
1031
1032 #[test]
1033 fn scanner_rejects_unterminated_references_in_character_data() {
1034 for xml in ["<root>AT&T</root>", "<root>&bad</root>"] {
1037 let mut scanner = Scanner::new(xml);
1038 let result = std::iter::from_fn(|| scanner.next_event().transpose())
1039 .collect::<Result<Vec<_>, _>>();
1040 assert!(result.is_err(), "accepted {xml}");
1041 }
1042 }
1043
1044 #[test]
1045 fn scanner_keeps_the_internal_subset_as_one_doctype_event() {
1046 let xml = r#"<!DOCTYPE root [<!-- <fake/> --><!ENTITY value "ok">]><root/>"#;
1049 let mut scanner = Scanner::new(xml);
1050 assert!(matches!(
1051 scanner.next_event().expect("scan DTD"),
1052 Some(Event::DocType { .. })
1053 ));
1054 assert!(matches!(
1055 scanner.next_event().expect("scan root"),
1056 Some(Event::Empty(_))
1057 ));
1058 assert!(scanner.next_event().expect("scan EOF").is_none());
1059 }
1060
1061 #[test]
1062 fn scanner_rejects_empty_and_repeated_prefix_components() {
1063 for xml in ["<:root/>", "<root: />", "<a:b:c/>"] {
1066 assert!(Scanner::new(xml).next_event().is_err(), "accepted {xml}");
1067 }
1068 }
1069
1070 #[test]
1071 fn scanner_preserves_non_ascii_name_start_characters() {
1072 let Some(Event::Empty(tag)) = Scanner::new("<\u{1680}/>")
1075 .next_event()
1076 .expect("valid non-ASCII name scans")
1077 else {
1078 panic!("empty element must produce one empty-tag event");
1079 };
1080 assert_eq!(tag.name.local(), "\u{1680}");
1081 }
1082
1083 #[test]
1084 fn scanner_rejects_duplicate_lexical_attributes() {
1085 for xml in [
1088 "<root xmlns='urn:first' xmlns='urn:second'/>",
1089 "<root xmlns:p='urn:first' xmlns:p='urn:second'/>",
1090 "<root value='first' value='second'/>",
1091 ] {
1092 assert!(Scanner::new(xml).next_event().is_err(), "accepted {xml}");
1093 }
1094 }
1095
1096 #[test]
1097 fn scanner_validates_wide_attributes_without_changing_source_order() {
1098 let attributes = (0..4096)
1101 .rev()
1102 .map(|index| format!(" a{index}='{index}'"))
1103 .collect::<String>();
1104 let xml = format!("<root{attributes}/>");
1105 let Some(Event::Empty(tag)) = Scanner::new(&xml)
1106 .next_event()
1107 .expect("wide start tag scans")
1108 else {
1109 panic!("wide empty element must produce one empty-tag event");
1110 };
1111 assert_eq!(tag.attributes.len(), 4096);
1112 assert_eq!(tag.attributes[0].name.local(), "a4095");
1113 assert_eq!(tag.attributes[4095].name.local(), "a0");
1114
1115 let unique = (0..4096)
1116 .map(|index| format!(" a{index}='x'"))
1117 .collect::<String>();
1118 let duplicate = format!("<root{unique} a0='duplicate'/>");
1119 assert!(Scanner::new(&duplicate).next_event().is_err());
1120 }
1121
1122 #[test]
1123 fn writer_escapes_text_and_attributes_by_context() {
1124 let mut writer = Writer::new(Vec::new());
1125 writer
1126 .start("p", [("a", "<&\"")])
1127 .expect("start tag must serialize");
1128 writer.text("<&\"").expect("text must serialize");
1129 writer.end("p").expect("end tag must serialize");
1130 assert_eq!(
1131 String::from_utf8(writer.into_inner()).expect("writer emits UTF-8"),
1132 "<p a=\"<&"\"><&\"</p>"
1133 );
1134 }
1135
1136 #[test]
1137 fn writer_preserves_normalized_whitespace_across_reparse() {
1138 let mut writer = Writer::new(Vec::new());
1141 writer
1142 .empty("root", [("value", "tab\tline\nreturn\r")])
1143 .expect("empty tag must serialize");
1144 assert_eq!(
1145 String::from_utf8(writer.into_inner()).expect("writer emits UTF-8"),
1146 "<root value=\"tab	line return \"/>"
1147 );
1148
1149 let mut writer = Writer::new(Vec::new());
1150 writer.text("line\rbreak\n").expect("text must serialize");
1151 assert_eq!(
1152 String::from_utf8(writer.into_inner()).expect("writer emits UTF-8"),
1153 "line break\n"
1154 );
1155 }
1156
1157 #[test]
1158 fn writer_rejects_invalid_element_and_attribute_qnames() {
1159 for name in ["", ":root", "root:", "a:b:c", "root><injected"] {
1160 let mut writer = Writer::new(Vec::new());
1161 assert!(
1162 writer.empty(name, []).is_err(),
1163 "accepted element name {name:?}"
1164 );
1165 }
1166
1167 let mut writer = Writer::new(Vec::new());
1168 assert!(writer.empty("root", [("a:b:c", "value")]).is_err());
1169 let mut writer = Writer::new(Vec::new());
1170 assert!(writer.end("root><injected").is_err());
1171 }
1172
1173 #[test]
1174 fn writer_rejects_orphan_and_mismatched_end_tags_without_mutation() {
1175 let mut orphan = Writer::new(Vec::new());
1178 assert!(orphan.end("root").is_err());
1179 assert!(orphan.into_inner().is_empty());
1180
1181 let mut writer = Writer::new(Vec::new());
1182 writer.start("root", []).expect("root opens");
1183 writer.start("child", []).expect("child opens");
1184 assert!(writer.end("root").is_err());
1185 writer.end("child").expect("mismatch preserves child frame");
1186 writer.end("root").expect("root closes after child");
1187 assert_eq!(writer.into_inner(), b"<root><child></child></root>");
1188 }
1189
1190 #[test]
1191 fn numeric_character_references_follow_the_exact_xml_grammar() {
1192 for valid in ["65", "x41", "x10FFFF"] {
1193 let reference = format!("&#{valid};");
1194 assert!(decode_references(&reference).is_ok(), "rejected {valid}");
1195 }
1196 for malformed in ["", "x", "+65", "-1", "X41", "x+41", " 65"] {
1197 let reference = format!("&#{malformed};");
1198 assert!(
1199 decode_references(&reference).is_err(),
1200 "accepted {malformed}"
1201 );
1202 }
1203 }
1204
1205 #[test]
1206 fn writer_rejects_duplicate_attributes_before_emitting_markup() {
1207 let mut writer = Writer::new(Vec::new());
1210 assert!(
1211 writer
1212 .empty("root", [("id", "one"), ("id", "two")])
1213 .is_err()
1214 );
1215 assert!(writer.into_inner().is_empty());
1216 }
1217
1218 #[test]
1219 fn writer_rejects_duplicate_expanded_attribute_names() {
1220 let mut writer = Writer::new(Vec::new());
1223 assert!(
1224 writer
1225 .empty(
1226 "root",
1227 [
1228 ("xmlns:a", "urn:shared"),
1229 ("xmlns:b", "urn:shared"),
1230 ("a:id", "one"),
1231 ("b:id", "two"),
1232 ],
1233 )
1234 .is_err()
1235 );
1236 assert!(writer.into_inner().is_empty());
1237
1238 let mut writer = Writer::new(Vec::new());
1239 writer
1240 .empty(
1241 "root",
1242 [
1243 ("xmlns:a", "urn:first"),
1244 ("xmlns:b", "urn:second"),
1245 ("a:id", "one"),
1246 ("b:id", "two"),
1247 ],
1248 )
1249 .expect("different expanded names remain legal");
1250 }
1251
1252 #[test]
1253 fn writer_rejects_invalid_element_namespace_bindings_before_output() {
1254 for (name, attributes) in [
1258 ("p:root", Vec::new()),
1259 ("root", vec![("xmlns:xml", "urn:wrong")]),
1260 ("root", vec![("xmlns:xmlns", "urn:wrong")]),
1261 ] {
1262 let mut writer = Writer::new(Vec::new());
1263 assert!(writer.empty(name, attributes).is_err(), "accepted {name}");
1264 assert!(writer.into_inner().is_empty());
1265 }
1266
1267 let mut writer = Writer::new(Vec::new());
1268 writer
1269 .empty("p:root", [("xmlns:p", "urn:bound")])
1270 .expect("a same-tag namespace declaration binds the element prefix");
1271
1272 let mut writer = Writer::new(Vec::new());
1273 writer
1274 .start("root", [("xmlns:p", "urn:bound")])
1275 .expect("parent namespace declaration is valid");
1276 writer
1277 .empty("p:child", [])
1278 .expect("an inherited namespace declaration binds the child prefix");
1279 }
1280
1281 #[test]
1282 fn writer_rejects_characters_forbidden_by_xml_1_0() {
1283 let mut writer = Writer::new(Vec::new());
1284 assert!(writer.text("before\0after").is_err());
1285 assert!(writer.into_inner().is_empty());
1286
1287 let mut writer = Writer::new(Vec::new());
1288 assert!(
1289 writer
1290 .empty("root", [("value", "before\u{1}after")])
1291 .is_err()
1292 );
1293 }
1294}