Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@
of `NsReader`. Use `.resolver().<...>` methods instead.
- [#938]: Now `BytesText::xml_content`, `BytesCData::xml_content` and `BytesRef::xml_content`
accepts `XmlVersion` parameter to apply correct EOL normalization rules.
- [#944]: `read_text()` now returns `BytesText` which allows you to get the content with
properly normalized EOLs. To get the previous behavior use `.read_text().decode()?`.

[#371]: https://github.com/tafia/quick-xml/issues/371
[#914]: https://github.com/tafia/quick-xml/pull/914
[#938]: https://github.com/tafia/quick-xml/pull/938
[#944]: https://github.com/tafia/quick-xml/pull/944


## 0.39.2 -- 2026-02-20
Expand Down
9 changes: 8 additions & 1 deletion examples/read_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Note: for this specific data set using serde feature would simplify
// this simple data is purely to make it easier to understand the code

use quick_xml::encoding::EncodingError;
use quick_xml::events::attributes::AttrError;
use quick_xml::events::{BytesStart, Event};
use quick_xml::name::QName;
Expand Down Expand Up @@ -53,6 +54,12 @@ impl From<AttrError> for AppError {
}
}

impl From<EncodingError> for AppError {
fn from(error: EncodingError) -> Self {
Self::Xml(quick_xml::Error::Encoding(error))
}
}

#[derive(Debug)]
struct Translation {
tag: String,
Expand Down Expand Up @@ -91,7 +98,7 @@ impl Translation {
Ok(Translation {
tag: tag.into(),
lang: lang.into(),
text: text_content.into(),
text: text_content.decode()?.into(),
})
} else {
dbg!("Expected Event::Start for Text, got: {:?}", &event);
Expand Down
9 changes: 5 additions & 4 deletions src/reader/ns_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//! [qualified names]: https://www.w3.org/TR/xml-names11/#dt-qualname
//! [expanded names]: https://www.w3.org/TR/xml-names11/#dt-expname

use std::borrow::Cow;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::ops::Deref;
Expand Down Expand Up @@ -725,11 +724,13 @@ impl<'i> NsReader<&'i [u8]> {
/// // ...then, we could read text content until close tag.
/// // This call will correctly handle nested <html> elements.
/// let text = reader.read_text(end.name()).unwrap();
/// assert_eq!(text, Cow::Borrowed(r#"
/// let text = text.decode().unwrap();
/// assert_eq!(text, r#"
/// <title>This is a HTML text</title>
/// <p>Usual XML rules does not apply inside it
/// <p>For example, elements not needed to be &quot;closed&quot;
/// "#));
/// "#);
/// assert!(matches!(text, Cow::Borrowed(_)));
///
/// // Now we can enable checks again
/// reader.config_mut().check_end_names = true;
Expand All @@ -741,7 +742,7 @@ impl<'i> NsReader<&'i [u8]> {
/// [`Start`]: Event::Start
/// [`decoder()`]: Reader::decoder()
#[inline]
pub fn read_text(&mut self, end: QName) -> Result<Cow<'i, str>> {
pub fn read_text(&mut self, end: QName) -> Result<BytesText<'i>> {
// According to the https://www.w3.org/TR/xml11/#dt-etag, end name should
// match literally the start name. See `Self::check_end_names` documentation
let result = self.reader.read_text(end)?;
Expand Down
12 changes: 6 additions & 6 deletions src/reader/slice_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
//! underlying byte stream. This implementation supports not using an
//! intermediate buffer as the byte slice itself can be used to borrow from.

use std::borrow::Cow;
use std::io;

#[cfg(feature = "encoding")]
Expand All @@ -11,7 +10,7 @@ use crate::reader::EncodingRef;
use encoding_rs::{Encoding, UTF_8};

use crate::errors::{Error, Result};
use crate::events::Event;
use crate::events::{BytesText, Event};
use crate::name::QName;
use crate::parser::Parser;
use crate::reader::{BangType, ReadRefResult, ReadTextResult, Reader, Span, XmlSource};
Expand Down Expand Up @@ -209,11 +208,12 @@ impl<'a> Reader<&'a [u8]> {
/// // ...then, we could read text content until close tag.
/// // This call will correctly handle nested <html> elements.
/// let text = reader.read_text(end.name()).unwrap();
/// assert_eq!(text, Cow::Borrowed(r#"
/// let text = text.decode().unwrap();
/// assert_eq!(text, r#"
/// <title>This is a HTML text</title>
/// <p>Usual XML rules does not apply inside it
/// <p>For example, elements not needed to be &quot;closed&quot;
/// "#));
/// "#);
/// assert!(matches!(text, Cow::Borrowed(_)));
///
/// // Now we can enable checks again
Expand All @@ -225,15 +225,15 @@ impl<'a> Reader<&'a [u8]> {
///
/// [`Start`]: Event::Start
/// [`decoder()`]: Self::decoder()
pub fn read_text(&mut self, end: QName) -> Result<Cow<'a, str>> {
pub fn read_text(&mut self, end: QName) -> Result<BytesText<'a>> {
// self.reader will be changed, so store original reference
let buffer = self.reader;
let span = self.read_to_end(end)?;

let len = span.end - span.start;
// SAFETY: `span` can only contain indexes up to usize::MAX because it
// was created from offsets from a single &[u8] slice
Ok(self.decoder().decode(&buffer[0..len as usize])?)
Ok(BytesText::wrap(&buffer[0..len as usize], self.decoder()))
}
}

Expand Down
10 changes: 8 additions & 2 deletions tests/issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ mod issue514 {

reader.config_mut().check_end_names = false;

assert_eq!(reader.read_text(html_end.name()).unwrap(), "...");
assert_eq!(
reader.read_text(html_end.name()).unwrap(),
BytesText::from_escaped("...")
);

reader.config_mut().check_end_names = true;

Expand All @@ -153,7 +156,10 @@ mod issue514 {

reader.config_mut().check_end_names = false;

assert_eq!(reader.read_text(html_end.name()).unwrap(), "...");
assert_eq!(
reader.read_text(html_end.name()).unwrap(),
BytesText::from_escaped("...")
);

reader.config_mut().check_end_names = true;

Expand Down
20 changes: 10 additions & 10 deletions tests/reader-namespaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1267,7 +1267,7 @@ mod read_text {
assert_eq!(reader.read_event().unwrap(), DocType(BytesText::new("dtd")));
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1297,7 +1297,7 @@ mod read_text {
assert_eq!(reader.read_event().unwrap(), PI(BytesPI::new("pi")));
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1330,7 +1330,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1367,7 +1367,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
// NOTE: due to unbalanced XML namespace still not closed
assert_eq!(
Expand Down Expand Up @@ -1406,7 +1406,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1442,7 +1442,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1472,7 +1472,7 @@ mod read_text {
assert_eq!(reader.read_event().unwrap(), Text(BytesText::new("text")));
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1505,7 +1505,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down Expand Up @@ -1538,7 +1538,7 @@ mod read_text {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_resolved_event().unwrap(),
Expand Down
20 changes: 10 additions & 10 deletions tests/reader-read-text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -61,7 +61,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand All @@ -88,7 +88,7 @@ mod borrowed {
assert_eq!(reader.read_event().unwrap(), Event::PI(BytesPI::new("pi")));
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -118,7 +118,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -149,7 +149,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -181,7 +181,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -211,7 +211,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -241,7 +241,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -271,7 +271,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down Expand Up @@ -302,7 +302,7 @@ mod borrowed {
);
assert_eq!(
reader.read_text(QName(b"root")).unwrap(),
"<root/><root></root>"
BytesText::from_escaped("<root/><root></root>")
);
assert_eq!(
reader.read_event().unwrap(),
Expand Down
10 changes: 8 additions & 2 deletions tests/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,10 @@ mod read_text {
r.config_mut().trim_text(true);

assert_eq!(r.read_event().unwrap(), Start(BytesStart::new("tag")));
assert_eq!(r.read_text(QName(b"tag")).unwrap(), " text ");
assert_eq!(
r.read_text(QName(b"tag")).unwrap(),
BytesText::from_escaped(" text ")
);
assert_eq!(r.read_event().unwrap(), Eof);
}

Expand All @@ -359,7 +362,10 @@ mod read_text {
r.config_mut().trim_text(true);

assert_eq!(r.read_event().unwrap(), Start(BytesStart::new("tag")));
assert_eq!(r.read_text(QName(b"tag")).unwrap(), " <nested/> ");
assert_eq!(
r.read_text(QName(b"tag")).unwrap(),
BytesText::from_escaped(" <nested/> ")
);
assert_eq!(r.read_event().unwrap(), Eof);
}
}