diff --git a/Changelog.md b/Changelog.md index 3f4ae2ec..2fd133ec 100644 --- a/Changelog.md +++ b/Changelog.md @@ -18,8 +18,12 @@ ### Bug Fixes +- [#936]: Fix incorrect result of `.read_text()` when it is called after reading `Text` or `GeneralRef` event. + ### Misc Changes +[#936]: https://github.com/tafia/quick-xml/pull/936 + ## 0.39.0 -- 2026-01-11 diff --git a/src/parser/pi.rs b/src/parser/pi.rs index 77088aaf..18c24804 100644 --- a/src/parser/pi.rs +++ b/src/parser/pi.rs @@ -74,12 +74,12 @@ impl Parser for PiParser { #[inline] fn eof_error(self, content: &[u8]) -> SyntaxError { - // Check if content starts with "?xml" followed by whitespace, '?' or end. + // Check if content starts with " { - self $(.$reader)? .consume(1); - *position += 1; - return ReadTextResult::Markup(buf); - } + Some(0) if read == 0 && available[0] == b'<' => return ReadTextResult::Markup(buf), // Do not consume `&` because it may be lone and we would be need to // return it as part of Text event Some(0) if read == 0 => return ReadTextResult::Ref(buf), Some(i) if available[i] == b'<' => { buf.extend_from_slice(&available[..i]); - // +1 to skip `<` - let used = i + 1; - self $(.$reader)? .consume(used); - read += used as u64; + self $(.$reader)? .consume(i); + read += i as u64; *position += read; return ReadTextResult::UpToMarkup(&buf[start..]); @@ -137,10 +131,10 @@ macro_rules! impl_buffered_source { // should explicitly skip it at first iteration lest we confuse // it with the end if read == 0 { - debug_assert_eq!( - available.first(), - Some(&b'&'), - "`read_ref` must be called at `&`" + debug_assert!( + available.starts_with(b"&"), + "`read_ref` must be called at `&`:\n{:?}", + crate::utils::Bytes(available) ); // If that ampersand is lone, then it will be part of text // and we should keep it @@ -151,31 +145,31 @@ macro_rules! impl_buffered_source { } match memchr::memchr3(b';', b'&', b'<', available) { - // Do not consume `&` because it may be lone and we would be need to - // return it as part of Text event - Some(i) if available[i] == b'&' => { + Some(i) if available[i] == b';' => { buf.extend_from_slice(&available[..i]); - self $(.$reader)? .consume(i); - read += i as u64; + // +1 -- skip the end `;` + let used = i + 1; + self $(.$reader)? .consume(used); + read += used as u64; *position += read; - return ReadRefResult::UpToRef(&buf[start..]); + return ReadRefResult::Ref(&buf[start..]); } + // Do not consume `&` because it may be lone and we would be need to + // return it as part of Text event Some(i) => { - let is_end = available[i] == b';'; + let is_amp = available[i] == b'&'; buf.extend_from_slice(&available[..i]); - // +1 -- skip the end `;` or `<` - let used = i + 1; - self $(.$reader)? .consume(used); - read += used as u64; + self $(.$reader)? .consume(i); + read += i as u64; *position += read; - return if is_end { - ReadRefResult::Ref(&buf[start..]) + return if is_amp { + ReadRefResult::UpToRef(&buf[start..]) } else { ReadRefResult::UpToMarkup(&buf[start..]) }; @@ -243,14 +237,20 @@ macro_rules! impl_buffered_source { buf: &'b mut Vec, position: &mut u64, ) -> Result<(BangType, &'b [u8])> { - // Peeked one bang ('!') before being called, so it's guaranteed to - // start with it. + // Peeked ' BangType::new(n.first().cloned())?, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(Error::Io(e.into())), + }; + }; loop { match self $(.$reader)? .fill_buf() $(.$await)? { @@ -310,13 +310,19 @@ macro_rules! impl_buffered_source { #[inline] $($async)? fn peek_one(&mut self) -> io::Result> { - loop { + let available = loop { break match self $(.$reader)? .fill_buf() $(.$await)? { - Ok(n) => Ok(n.first().cloned()), + Ok(n) => n, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => Err(e), + Err(e) => return Err(e), }; - } + }; + debug_assert!( + available.starts_with(b"<"), + "markup must start from '<':\n{:?}", + crate::utils::Bytes(available) + ); + Ok(available.get(1).cloned()) } }; } diff --git a/src/reader/mod.rs b/src/reader/mod.rs index e227e189..2cc828bd 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -416,9 +416,8 @@ macro_rules! read_until_close { { Ok((bang_type, bytes)) => $self.state.emit_bang(bang_type, bytes), Err(e) => { - // We want to report error at `<`, but offset was increased, - // so return it back (-1 for `<`) - $self.state.last_error_offset = start - 1; + // We want to report error at `<` + $self.state.last_error_offset = start; Err(e) } }, @@ -438,9 +437,8 @@ macro_rules! read_until_close { { Ok(bytes) => $self.state.emit_end(bytes), Err(e) => { - // We want to report error at `<`, but offset was increased, - // so return it back (-1 for `<`) - $self.state.last_error_offset = start - 1; + // We want to report error at `<` + $self.state.last_error_offset = start; Err(e) } }, @@ -451,9 +449,8 @@ macro_rules! read_until_close { { Ok(bytes) => $self.state.emit_question_mark(bytes), Err(e) => { - // We want to report error at `<`, but offset was increased, - // so return it back (-1 for `<`) - $self.state.last_error_offset = start - 1; + // We want to report error at `<` + $self.state.last_error_offset = start; Err(e) } }, @@ -464,17 +461,15 @@ macro_rules! read_until_close { { Ok(bytes) => Ok($self.state.emit_start(bytes)), Err(e) => { - // We want to report error at `<`, but offset was increased, - // so return it back (-1 for `<`) - $self.state.last_error_offset = start - 1; + // We want to report error at `<` + $self.state.last_error_offset = start; Err(e) } }, // `<` - syntax error, tag not closed Ok(None) => { - // We want to report error at `<`, but offset was increased, - // so return it back (-1 for `<`) - $self.state.last_error_offset = start - 1; + // We want to report error at `<` + $self.state.last_error_offset = start; Err(Error::Syntax(SyntaxError::UnclosedTag)) } Err(e) => Err(Error::Io(e.into())), @@ -879,13 +874,7 @@ impl Reader { /// (` `, `\t`, `\r`, and `\n`) if [`Config::trim_text_end`] is set this is position /// before trim, not the position of the last byte of the [`Event::Text`] content. pub const fn buffer_position(&self) -> u64 { - // when internal state is InsideMarkup, we have actually read until '<', - // which we don't want to show - if let ParseState::InsideMarkup = self.state.state { - self.state.offset - 1 - } else { - self.state.offset - } + self.state.offset } /// Gets the last error byte position in the input data. If there is no errors @@ -1183,8 +1172,8 @@ impl BangType { for i in memchr::memchr_iter(b'>', chunk) { // Need to read at least 6 symbols (`!---->`) for properly finished comment // - XML comment - // 012345 - i - if buf.len() + i > 4 { + // 0123456 - i + if buf.len() + i > 5 { if chunk[..i].ends_with(b"--") { // We cannot strip last `--` from the buffer because we need it in case of // check_comments enabled option. XML standard requires that comment @@ -1270,7 +1259,7 @@ mod test { $($async)? fn not_properly_start() { let buf = $buf; let mut position = 1; - let mut input = b"![]]>other content".as_ref(); + let mut input = b"other content".as_ref(); // ^= 1 match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? { @@ -1288,9 +1277,9 @@ mod test { #[$test] $($async)? fn not_closed() { let buf = $buf; - let mut position = 1; - let mut input = b"![CDATA[other content".as_ref(); - // ^= 1 ^= 22 + let mut position = 0; + let mut input = b" assert_eq!(cause, SyntaxError::UnclosedCData), @@ -1306,9 +1295,9 @@ mod test { #[$test] $($async)? fn empty() { let buf = $buf; - let mut position = 1; - let mut input = b"![CDATA[]]>other content".as_ref(); - // ^= 1 ^= 12 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 12 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1316,7 +1305,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::CData, Bytes(b"![CDATA[]]")) + (BangType::CData, Bytes(b"content]]>other content]]>".as_ref(); - // ^= 1 ^= 29 + let mut position = 0; + let mut input = b"content]]>other content]]>".as_ref(); + // ^= 0 ^= 29 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1337,7 +1326,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::CData, Bytes(b"![CDATA[cdata]] ]>content]]")) + (BangType::CData, Bytes(b"content]]")) ); assert_eq!(position, 29); } @@ -1368,7 +1357,7 @@ mod test { $($async)? fn not_properly_start() { let buf = $buf; let mut position = 1; - let mut input = b"!- -->other content".as_ref(); + let mut input = b"other content".as_ref(); // ^= 1 match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? { @@ -1384,9 +1373,9 @@ mod test { #[$test] $($async)? fn not_properly_end() { let buf = $buf; - let mut position = 1; - let mut input = b"!->other content".as_ref(); - // ^= 1 ^= 17 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 17 match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? { Err(Error::Syntax(cause)) => assert_eq!(cause, SyntaxError::UnclosedComment), @@ -1401,9 +1390,9 @@ mod test { #[$test] $($async)? fn not_closed1() { let buf = $buf; - let mut position = 1; - let mut input = b"!--other content".as_ref(); - // ^= 1 ^= 17 + let mut position = 0; + let mut input = b"other content".as_ref(); - // ^= 1 ^= 18 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 18 match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? { Err(Error::Syntax(cause)) => assert_eq!(cause, SyntaxError::UnclosedComment), @@ -1435,9 +1424,9 @@ mod test { #[$test] $($async)? fn not_closed3() { let buf = $buf; - let mut position = 1; - let mut input = b"!--->other content".as_ref(); - // ^= 1 ^= 19 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 19 match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? { Err(Error::Syntax(cause)) => assert_eq!(cause, SyntaxError::UnclosedComment), @@ -1452,9 +1441,9 @@ mod test { #[$test] $($async)? fn empty() { let buf = $buf; - let mut position = 1; - let mut input = b"!---->other content".as_ref(); - // ^= 1 ^= 7 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 7 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1462,7 +1451,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::Comment, Bytes(b"!----")) + (BangType::Comment, Bytes(b"comment<--->other content".as_ref(); - // ^= 1 ^= 18 + let mut position = 0; + let mut input = b"comment<--->other content".as_ref(); + // ^= 0 ^= 18 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1480,7 +1469,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::Comment, Bytes(b"!--->comment<---")) + (BangType::Comment, Bytes(b"comment<---")) ); assert_eq!(position, 18); } @@ -1497,9 +1486,9 @@ mod test { #[$test] $($async)? fn not_properly_start() { let buf = $buf; - let mut position = 1; - let mut input = b"!D other content".as_ref(); - // ^= 1 ^= 17 + let mut position = 0; + let mut input = b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1514,9 +1503,9 @@ mod test { #[$test] $($async)? fn without_space() { let buf = $buf; - let mut position = 1; - let mut input = b"!DOCTYPEother content".as_ref(); - // ^= 1 ^= 22 + let mut position = 0; + let mut input = b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1531,9 +1520,9 @@ mod test { #[$test] $($async)? fn empty() { let buf = $buf; - let mut position = 1; - let mut input = b"!DOCTYPE>other content".as_ref(); - // ^= 1 ^= 10 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 10 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1541,7 +1530,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::DocType(DtdParser::Finished), Bytes(b"!DOCTYPE")) + (BangType::DocType(DtdParser::Finished), Bytes(b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1571,9 +1560,9 @@ mod test { #[$test] $($async)? fn not_properly_start() { let buf = $buf; - let mut position = 1; - let mut input = b"!d other content".as_ref(); - // ^= 1 ^= 17 + let mut position = 0; + let mut input = b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1588,9 +1577,9 @@ mod test { #[$test] $($async)? fn without_space() { let buf = $buf; - let mut position = 1; - let mut input = b"!doctypeother content".as_ref(); - // ^= 1 ^= 22 + let mut position = 0; + let mut input = b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1605,9 +1594,9 @@ mod test { #[$test] $($async)? fn empty() { let buf = $buf; - let mut position = 1; - let mut input = b"!doctype>other content".as_ref(); - // ^= 1 ^= 10 + let mut position = 0; + let mut input = b"other content".as_ref(); + // ^= 0 ^= 10 let (ty, bytes) = $source(&mut input) .read_bang_element(buf, &mut position) @@ -1615,7 +1604,7 @@ mod test { .unwrap(); assert_eq!( (ty, Bytes(bytes)), - (BangType::DocType(DtdParser::Finished), Bytes(b"!doctype")) + (BangType::DocType(DtdParser::Finished), Bytes(b" assert_eq!(cause, SyntaxError::UnclosedDoctype), @@ -1665,13 +1654,13 @@ mod test { let buf = $buf; let mut position = 1; let mut input = b"<".as_ref(); - // ^= 2 + // ^= 1 match $source(&mut input).read_text(buf, &mut position) $(.$await)? { ReadTextResult::Markup(b) => assert_eq!(b, $buf), x => panic!("Expected `Markup(_)`, but got `{:?}`", x), } - assert_eq!(position, 2); + assert_eq!(position, 1); } #[$test] @@ -1693,13 +1682,13 @@ mod test { let buf = $buf; let mut position = 1; let mut input = b"a<".as_ref(); - // 1 ^= 3 + // ^= 2 match $source(&mut input).read_text(buf, &mut position) $(.$await)? { ReadTextResult::UpToMarkup(bytes) => assert_eq!(Bytes(bytes), Bytes(b"a")), x => panic!("Expected `UpToMarkup(_)`, but got `{:?}`", x), } - assert_eq!(position, 3); + assert_eq!(position, 2); } #[$test] @@ -1774,13 +1763,13 @@ mod test { let buf = $buf; let mut position = 1; let mut input = b"&<".as_ref(); - // ^= 3 + // ^= 2 match $source(&mut input).read_ref(buf, &mut position) $(.$await)? { ReadRefResult::UpToMarkup(bytes) => assert_eq!(Bytes(bytes), Bytes(b"&")), x => panic!("Expected `UpToMarkup(_)`, but got `{:?}`", x), } - assert_eq!(position, 3); + assert_eq!(position, 2); } #[$test] diff --git a/src/reader/slice_reader.rs b/src/reader/slice_reader.rs index 45ec32b7..1cbd3f5a 100644 --- a/src/reader/slice_reader.rs +++ b/src/reader/slice_reader.rs @@ -265,18 +265,14 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] { fn read_text(&mut self, _buf: (), position: &mut u64) -> ReadTextResult<'a, ()> { // Search for start of markup or an entity or character reference match memchr::memchr2(b'<', b'&', self) { - Some(0) if self[0] == b'<' => { - *self = &self[1..]; - *position += 1; - ReadTextResult::Markup(()) - } + Some(0) if self[0] == b'<' => ReadTextResult::Markup(()), // Do not consume `&` because it may be lone and we would be need to // return it as part of Text event Some(0) => ReadTextResult::Ref(()), Some(i) if self[i] == b'<' => { - let bytes = &self[..i]; - *self = &self[i + 1..]; - *position += i as u64 + 1; + let (bytes, rest) = self.split_at(i); + *self = rest; + *position += i as u64; ReadTextResult::UpToMarkup(bytes) } Some(i) => { @@ -296,32 +292,32 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] { #[inline] fn read_ref(&mut self, _buf: (), position: &mut u64) -> ReadRefResult<'a> { - debug_assert_eq!( - self.first(), - Some(&b'&'), - "`read_ref` must be called at `&`" + debug_assert!( + self.starts_with(b"&"), + "`read_ref` must be called at `&`:\n{:?}", + crate::utils::Bytes(self) ); // Search for the end of reference or a start of another reference or a markup match memchr::memchr3(b';', b'&', b'<', &self[1..]) { + Some(i) if self[i + 1] == b';' => { + let end = i + 1; + let bytes = &self[..end]; + // +1 -- skip the end `;` + *self = &self[end + 1..]; + *position += end as u64 + 1; + + ReadRefResult::Ref(bytes) + } // Do not consume `&` because it may be lone and we would be need to // return it as part of Text event - Some(i) if self[i + 1] == b'&' => { + Some(i) => { + let is_amp = self[i + 1] == b'&'; let (bytes, rest) = self.split_at(i + 1); *self = rest; *position += i as u64 + 1; - ReadRefResult::UpToRef(bytes) - } - Some(i) => { - let end = i + 1; - let is_end = self[end] == b';'; - let bytes = &self[..end]; - // +1 -- skip the end `;` or `<` - *self = &self[end + 1..]; - *position += end as u64 + 1; - - if is_end { - ReadRefResult::Ref(bytes) + if is_amp { + ReadRefResult::UpToRef(bytes) } else { ReadRefResult::UpToMarkup(bytes) } @@ -357,9 +353,13 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] { fn read_bang_element(&mut self, _buf: (), position: &mut u64) -> Result<(BangType, &'a [u8])> { // Peeked one bang ('!') before being called, so it's guaranteed to // start with it. - debug_assert_eq!(self[0], b'!'); + debug_assert!( + self.starts_with(b" XmlSource<'a, ()> for &'a [u8] { #[inline] fn peek_one(&mut self) -> io::Result> { - Ok(self.first().copied()) + debug_assert!( + self.starts_with(b"<"), + "markup must start from '<':\n{:?}", + crate::utils::Bytes(self) + ); + Ok(self.get(1).copied()) } } diff --git a/src/reader/state.rs b/src/reader/state.rs index ff08272d..9d708529 100644 --- a/src/reader/state.rs +++ b/src/reader/state.rs @@ -75,15 +75,15 @@ impl ReaderState { /// Returns `Comment`, `CData` or `DocType` event. /// /// `buf` contains data between `<` and `>`: - /// - CDATA: `![CDATA[...]]` - /// - Comment: `!--...--` - /// - Doctype (uppercase): `!D...` - /// - Doctype (lowercase): `!d...` + /// - CDATA: `(&mut self, bang_type: BangType, buf: &'b [u8]) -> Result> { - debug_assert_eq!( - buf.first(), - Some(&b'!'), - "CDATA, comment or DOCTYPE should start from '!'" + debug_assert!( + buf.starts_with(b" { - debug_assert!(buf.ends_with(b"--")); + BangType::Comment if buf.starts_with(b": - // ~~~~~~~~~~~~~~~~ : - buf - // : =========== : - zone of search (possible values of `p`) - // : |---p : - p is counted from | (| is 0) - // : : : ^ - self.offset - // ^ : : - self.offset - len - // ^ : - self.offset - len + 2 - // ^ - self.offset - len + 2 + p - self.last_error_offset = self.offset - len as u64 + 2 + p as u64; + // ~~~~~~~~~~~~~~~~~ : - buf + // : =========== : - zone of search (possible values of `p`) + // : |---p : - p is counted from | (| is 0) + // : : : ^ - self.offset + // ^ : : - self.offset - len + // ^ : - self.offset - len + 3 + // ^ - self.offset - len + 3 + p + self.last_error_offset = self.offset - len as u64 + 3 + p as u64; return Err(Error::IllFormed(IllFormedError::DoubleHyphenInComment)); } // Continue search after single `-` (+1 to skip it) @@ -124,8 +128,8 @@ impl ReaderState { } } Ok(Event::Comment(BytesText::wrap( - // Cut of `!--` and `--` from start and end - &buf[3..len - 2], + // Cut of `\ + \ + \ + \ + ", + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Comment(BytesText::new("comment")) + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn start() { + let mut reader = Reader::from_str( + "\ + \ + \ + \ + \ + \ + ", + ); + reader.config_mut().check_end_names = false; + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::new("tag")), + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn end() { + let mut reader = Reader::from_str( + "\ + \ + \ + \ + \ + \ + ", + ); + reader.config_mut().check_end_names = false; + reader.config_mut().allow_unmatched_ends = true; + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::End(BytesEnd::new("tag")), + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn empty() { + let mut reader = Reader::from_str( + "\ + \ + \ + \ + \ + \ + ", + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("tag")), + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn text() { + let mut reader = Reader::from_str( + "\ + \ + text\ + \ + \ + \ + ", + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Text(BytesText::new("text")) + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn cdata() { + let mut reader = Reader::from_str( + "\ + \ + \ + \ + \ + \ + ", + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::CData(BytesCData::new("cdata")) + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn dangling_amp() { + let mut reader = Reader::from_str( + "\ + \ + &\ + \ + \ + \ + ", + ); + reader.config_mut().allow_dangling_amp = true; + assert_eq!( + reader.read_event().unwrap(), + Event::Start(BytesStart::from_content("root", 4)), + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Text(BytesText::from_escaped("&")) + ); + assert_eq!( + reader.read_text(QName(b"root")).unwrap(), + "" + ); + assert_eq!( + reader.read_event().unwrap(), + Event::Empty(BytesStart::new("element")) + ); + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } +}