//! Getting started with the `quick-xml` pull reader.
//!
//! This is the best example to read *first*. It walks through the core loop that
//! every hand-written `quick-xml` reader is built on:
//!
//! 1. Create a [`Reader`].
//! 2. Repeatedly ask it for the next [`Event`] until you reach [`Event::Eof`].
//! 3. `match` on the event and pull out the data you care about.
//!
//! quick-xml is a *pull* parser: it never builds a document tree in memory.
//! Instead it hands you one small event at a time (`` start, some text,
//! `` end, ...) and you decide what to keep. That is what makes it fast
//! and memory-light, but it also means *you* are responsible for tracking where
//! you are in the document, and the parser will be as permissive or as strict
//! as you decide to make it.
//!
//! Run it with:
//!
//! ```console
//! cargo run --example getting_started
//! ```
//!
//! Once this makes sense, see:
//! - `reader_patterns.rs` for structuring larger readers (state machines vs. nested readers)
//! - `serde_roundtrip.rs` for skipping the manual loop entirely with `#[derive(Deserialize)]`
//! - `writer.rs` for producing XML
//! - `examples/README.md` for a guide on which approach to choose
use quick_xml::XmlVersion;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
const XML: &str = r#"
The Rust Programming Language
Steve Klabnik
Carol Nichols
39.95
Programming Rust
Jim Blandy
47.99
"#;
fn main() -> Result<(), quick_xml::Error> {
// `from_str` reads directly from a `&str`. Because the whole document is already in memory,
// the events can *borrow* from it and no extra read buffer is needed. (For streaming from
// a file or socket, see `read_buffered.rs`, which reuses a `Vec` to keep allocations low.)
let mut reader = Reader::from_str(XML);
let mut titles = Vec::new();
let mut book_count = 0u32;
// Track the XML version declared in the `` header. It affects attribute value
// normalization and other parsing behavior.
let mut xml_version = XmlVersion::Implicit1_0;
// The reader does not implement `Iterator` (its events borrow from an internal buffer),
// so we drive it with a plain loop.
loop {
match reader.read_event()? {
Event::Decl(e) => xml_version = e.xml_version()?,
// — an opening tag. `BytesStart` gives us the name and access to attributes.
Event::Start(e) if e.name().as_ref() == "book" => {
book_count += 1;
// Attributes are parsed lazily. Iterate them and decode the one
// we want. `normalized_value` unescapes entities (`&` -> `&`)
// and applies the whitespace normalization the XML spec requires
// for attribute values.
for attr in e.attributes() {
let attr = attr?;
if attr.key.as_ref() == "id" {
let id = attr.normalized_value(xml_version)?;
println!("found book id={id}");
}
}
}
// — rather than wait for the following Text and End
// events, `read_text` consumes everything up to `` and hands
// back the text in one step. It returns the raw (still-escaped)
// text; `xml_content` unescapes it into the logical string value.
Event::Start(e) if e.name().as_ref() == "title" => {
let text = reader.read_text(e.name())?;
titles.push(text.xml_content(xml_version).into_owned());
}
// `read_event` yields `Eof` exactly once, when the document ends.
Event::Eof => break,
// Start/End/Text/Comment/CData/PI/Decl/... — everything we don't
// care about in this example is simply ignored. Production-grade
// code might decide to perform more rigorous structural checks
// on the XML, see `reader_patterns.rs`
_ => {}
}
}
println!("read {book_count} books: {titles:?}");
assert_eq!(book_count, 2);
assert_eq!(
titles,
["The Rust Programming Language", "Programming Rust"]
);
Ok(())
}