tafia/quick-xml
 Watch   
 Star   
 Fork   
23 days ago
quick-xml

v0.42.0 - String Ergonomics

What's Changed

0.42.0 -- 2026-08-22

This is a large release. The primary change is an ergonomic improvement across the entire API - quick_xml now makes use of &str and String types where possible instead of &[u8] and Vec<u8>. This requires significant refactoring of downstream code, but should result in a net simplification as well as potential performance improvements, and opens up additional opportunities in future releases.

The MSRV has been raised to 1.86. We now use Rust 2024 Edition.

Breaking Changes

  • #963: Reader now validates that input is valid UTF-8 when constructing events. Non-UTF-8 input passed to Reader::from_reader() without DecodingReader will now produce Error::Encoding instead of silently passing through invalid bytes. Use DecodingReader to transcode non-UTF-8 sources.
  • #963: Name types (QName, LocalName, Prefix, Namespace, PrefixDeclaration) now wrap &str instead of &[u8]. into_inner() returns &str, and AsRef<str> is implemented (AsRef<[u8]> has been removed). ResolveResult::Unknown now contains String instead of Vec<u8>, and NamespaceError variants contain String instead of Vec<u8>.
  • #963: Removed the decoder: Decoder field from event types (BytesStart, BytesText, BytesCData, BytesRef) and Attributes. The decoder() method is no longer available on these types. Decode methods on events now always assume UTF-8 input. Error::missed_end() no longer takes a Decoder parameter.
  • #963: Event types (BytesStart, BytesEnd, BytesText, BytesCData, BytesPI, BytesRef) now store Cow<str> internally instead of Cow<[u8]>. into_inner() on BytesText, BytesCData, BytesPI, and BytesRef now returns Cow<str>. BytesStart::set_name() now takes &str instead of &[u8].
  • #963: All event types and the Event enum now implement Deref<Target = str> instead of Deref<Target = [u8]>. Explicit AsRef<str> impls are provided to avoid ambiguity.
  • #963: Removed decode() methods from BytesText, BytesCData, and BytesRef. Content is already available as &str via Deref. The xml10_content(), xml11_content(), xml_content(), and html_content() methods now return Cow<str> directly instead of Result<Cow<str>, EncodingError>.
  • #963: Attribute::value is now Cow<'a, str> instead of Cow<'a, [u8]>. The From<(&[u8], &[u8])> impl has been removed.
  • #963: BytesDecl::version(), encoding(), and standalone() now return Cow<'_, str> instead of Cow<'_, [u8]>.
  • #963: Removed Reader::decoder() method. Use Reader::encoding() instead (available with the encoding feature). Removed decoder() from the XmlRead serde trait. Removed all methods from Decoder (the struct is kept only for backward compatibility with deprecated Attribute methods).
  • #980: NamespaceError::TooManyDeclarations has been renamed to TooManyBindings, and NamespaceResolver::set_max_declarations_per_element has been renamed to NamespaceResolver::set_max_namespace_bindings, and the semantic behavior has changed slightly. The default maximum has also been reduced from 256 to 128.
  • #1000: DeError::UnexpectedStart renamed to DeError::MixedContent. That error is emitted when you try to deserialize boolean, number or string field from something like <field>text <tag/> another text</field>.

Bug Fixes

  • #670: Serde serializer now escapes \r, \n, and \t in attribute values as &#13;, &#10;, and &#9; respectively, preventing silent data loss from XML attribute-value normalization on round-trip. Likewise Attribute::from performs the same transformation.
  • #953: The serde Deserializer now correctly handles namespaces. Previously the namespace bindings might be applied or removed before the event actually was consumed which lead to a couple of bugs.
  • #989: Attributes::new and Attributes::html now return empty iterators when their starting position is past the end of the input instead of panicking.
  • #977: NamespaceResolver::push (and hence every NsReader Start/Empty event) now returns the new NamespaceError::TooDeeplyNested when a document nests elements deeper than u16::MAX, instead of overflowing the internal u16 depth counter. Previously the unguarded nesting_level += 1 panicked under overflow-checks builds and silently wrapped in release, corrupting namespace-scope bookkeeping on deeply nested untrusted input.
  • #980: NamespaceResolver now caps the total number of in-scope namespace bindings (default 128, configurable via set_max_namespace_bindings), replacing the previous per-element max_declarations_per_element limit.
  • #978: The serde Deserializer now enforces a configurable recursion-depth limit (default 128, matching serde_json). Deeply nested XML returns DeError::TooDeeplyNested instead of overflowing the native call stack. Use Deserializer::recursion_limit() to adjust.
  • #990: \r in text content is now escaped as &#13; by the serde serializer, BytesText::new(), escape(), partial_escape(), and minimal_escape(), preventing silent conversion to \n from XML end-of-line normalization on round-trip. Note that \r cannot be preserved through CDATA serialization because character references are not permitted inside CDATA sections.

Misc Changes

  • #269: Added getting-started examples (getting_started, writer, serde_roundtrip, reader_patterns, visitor) and an examples/README.md guide on choosing between the serde and pull-reader/writer APIs.
  • #331: Documentation about lifetimes of the events and attributes has been clarified.
  • #859: Added an example showing how to pretty-print serialized XML.
  • #983: Adopted an AI use and contribution policy for new upstream contributions.
  • #963: MSRV bumped to 1.86 (April 2025)
  • #963: Deprecated Attribute methods that take a Decoder parameter, since attribute values are now always valid UTF-8: decoded_and_normalized_value(), decoded_and_normalized_value_with(), decode_and_unescape_value(), and decode_and_unescape_value_with(). Use normalized_value() and normalized_value_with() instead.
  • #1002: Added NamespaceResolver::with that allows temporary applying namespace bindings from the start tag for the scope of a provided closure F, without making any persistent change to the resolver. It is useful to check a peeked event which is not yet consumed in custom implementations of peekable reader.
  • #1002: Added Deserializer::resolver and Deserializer::resolver_mut methods to get a namespace resolver used by this deserializer, because it no longer uses an NsReader internally.
  • #1005: Implement Hash, PartialOrd, and Ord for BytesText and BytesCData types.

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.41.0...v0.42.0

2026-06-30 01:01:12
quick-xml

v0.41.0 - Secuirity fixes

What's Changed

New Features

  • #970: Add NsReader::resolver_mut() and NamespaceResolver::{max_declarations_per_element, set_max_declarations_per_element}.

Bug Fixes

  • #969: Attributes (and anything that iterates BytesStart::attributes() with the default with_checks(true)) no longer takes O(N²) time on a start tag with a large number of attributes. Small tags keep the previous linear scan; larger ones switch to a 64-bit hash pre-filter, so the whole tag is O(N). The exact AttrError::Duplicated(new, prev) positions are unchanged.
  • #970: NamespaceResolver::push (and hence every NsReader Start/Empty event) now rejects a start tag that declares more than DEFAULT_MAX_DECLARATIONS_PER_ELEMENT (256) xmlns / xmlns:* namespace bindings, returning the new NamespaceError::TooManyDeclarations. Previously push allocated one NamespaceBinding per declaration with no upper bound, before the event was returned to the caller, so an NsReader consumer could not bound its memory exposure on untrusted input. The limit is configurable via NamespaceResolver::set_max_declarations_per_element (use usize::MAX to disable).

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.40.1...v0.41.0

2026-05-16 01:45:12
quick-xml

v0.40.1 - Fix rarely possible serde deserialization panic

What's Changed

  • #964: Fix unreachable!() panic in the serde deserializer when a DOCTYPE declaration appears between two text runs inside an element (e.g. <a>x<!DOCTYPE y>z</a>). The DOCTYPE used to break drain_text's consecutive-text merge, so two DeEvent::Text events reached read_text and tripped its "Cannot be two consequent Text events" invariant. DOCTYPE is now treated as transparent during text drain — it still goes through the entity resolver, but the surrounding text is merged into one run. Discovered via libFuzzer on a real-world SAML deserializer harness.

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.40.0...v0.40.1

2026-05-12 01:44:29
quick-xml

v0.40.0 - UTF-16 and ISO-2022-JP encodings supported

What's Changed

MSRV bumped to 1.79.

Now quick-xml supports the UTF-16 and ISO-2022-JP encoded documents. See the new DecodingReader type.

New Features

  • #956: Add DecodingReader, a BufRead adapter that auto-detects encoding from BOM or XML declaration and transcodes to UTF-8. Enabled by the encoding feature.

  • #938: Add new enumeration XmlVersion and typified getter BytesDecl::xml_version().

  • #938: Add new error variant IllFormedError::UnknownVersion.

  • #371: Add new error variant EscapeError::TooManyNestedEntities.

  • #371: Improved compliance with the XML attribute value normalization process by adding

    • Attribute::normalized_value()
    • Attribute::normalized_value_with()
    • Attribute::decoded_and_normalized_value()
    • Attribute::decoded_and_normalized_value_with()

    which ought to be used in place of deprecated

    • Attribute::unescape_value()
    • Attribute::unescape_value_with()
    • Attribute::decode_and_unescape_value()
    • Attribute::decode_and_unescape_value_with()

    Deprecated functions now behaves the same as newly added.

Bug Fixes

  • #938: Use correct rules for EOL normalization in Deserializer when parse XML 1.0 documents. Previously XML 1.1. rules was applied.

Misc Changes

  • #914: Remove deprecated .prefixes(), .resolve(), .resolve_attribute(), and .resolve_element() 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()?.
  • #956: Bumped MSRV from 1.59 (Feb 2022) to 1.79 (June 2024)

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.39.4...v0.40.0

2026-05-09 03:00:27
quick-xml

v0.39.4 - Fix another panics when parse malformed DTD

Bug Fixes

  • #957: Fix slice-index panic when reading malformed DTD whose unknown markup is split across BufReader chunks. As with #950, the returned Event::DocType may contain the malformed DTD; this fix only ensures that the parser does not panic.
  • #960: Fix sibling slice-index panic when a single chunk delivers < followed by 9+ bytes of unknown markup inside a DTD internal subset. Same disposition as #957 / #950: parser must not panic; DTD validity reporting is a future improvement.

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.39.3...v0.39.4

2026-05-05 01:08:05
quick-xml

v0.39.3 - Fix panic when parse malformed DTD

Bug Fixes

  • #950: Fix subtraction with overflow when parse malformed DTD in some cases. Note, that currently we do not check the validity of DTD, so the returned Event::DocType may contain the malformed DTD.

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.39.2...v0.39.3

2026-02-21 00:41:10
quick-xml

v0.39.2 - Fix regression and read_text_into

What's Changed

New Features

  • #483: Implement read_text_into() and read_text_into_async().

Bug Fixes

  • #939: Fix parsing error of the tag from buffered reader, when the first byte < is the last in the BufRead internal buffer. This is the regression from #936.

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.39.1...v0.39.2

2026-02-16 02:13:58
quick-xml

v0.39.1 - Fixes in read_to_end / read_text

What's Changed

New Features

  • #598: Add method NamespaceResolver::set_level which may be helpful in some circumstances.

Bug Fixes

  • #597: Fix incorrect processing of namespace scopes in NsReader::read_to_end, NsReader::read_to_end_into, NsReader::read_to_end_into_async and NsReader::read_text. The scope started by a start element was not ended after that call.
  • #936: Fix incorrect result of .read_text() when it is called after reading Text or GeneralRef event.
2026-01-12 01:10:04
quick-xml

v0.39.0 - Config for Writer

What's Changed

Added a way to configure Writer. Now all configuration is contained in the writer::Config struct and can be applied at once. When serde-types feature is enabled, configuration is serializable.

New Features

  • #846: Add methods config() and config_mut() to inspect and change the writer configuration.
  • #846: Add ability to write space before /> in self-closed tags for maximum compatibility with XHTML.
  • #846: Add method empty_element_handling() as a more powerful alternative to expand_empty_elements() in Serializer.
  • #929: Allow to pass list of field names to impl_deserialize_for_internally_tagged_enum! macro which is required if you enum variants contains $value fields.

Bug Fixes

  • #923: Implement correct skipping of well-formed DTD.

Misc Changes

  • #908: Increase minimal supported serde version from 1.0.139 to 1.0.180.
  • #913: Deprecate .prefixes(), .resolve(), .resolve_attribute(), and .resolve_element() of NsReader. Use .resolver().bindings() and .resolver().resolve() methods instead.
  • #913: Attributes::has_nil now accepts NamespaceResolver instead of Reader<R>.
  • #924: (breaking change) Split SyntaxError::UnclosedPIOrXmlDecl into UnclosedPI and UnclosedXmlDecl for more precise error reporting.
  • #924: (breaking change) Parser::eof_error now takes &self and content &[u8] parameters.
  • #926: (breaking change) Split SyntaxError::UnclosedTag into UnclosedTag, UnclosedSingleQuotedAttributeValue and UnclosedDoubleQuotedAttributeValue for more precise error reporting.

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.38.4...v0.39.0

2025-11-12 02:14:14
quick-xml

v0.38.4 - CDATA serialization in serde

What's Changed

New Features

  • #353: Add ability to serialize textual content as CDATA sections in Serializer. Everywhere where the text node may be created, a CDATA section(s) could be produced instead. See the new Serializer::text_format() method.

Bug Fixes

  • #912: Fix deserialization of numbers, booleans and characters that is space-wrapped, for example <int> 42 </int>. That space characters are usually indent added during serialization and other XML serialization libraries trims them

Misc Changes

  • #901: Fix running tests on 32-bit architecture
  • #909: Avoid some allocations in the Serializer

New Contributors

Full Changelog: https://github.com/tafia/quick-xml/compare/v0.38.3...v0.38.4