Skip to content

Commit 81cd098

Browse files
committed
Allow using tokio's AsyncBufRead
1 parent 6c357da commit 81cd098

16 files changed

+2135
-429
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "quick-xml"
33
version = "0.19.0"
44
authors = ["Johann Tuffe <[email protected]>"]
55
description = "High performance xml reader and writer"
6+
edition = "2018"
67

78
documentation = "https://docs.rs/quick-xml"
89
repository = "https://github.com/tafia/quick-xml"
@@ -16,13 +17,16 @@ license = "MIT"
1617
travis-ci = { repository = "tafia/quick-xml" }
1718

1819
[dependencies]
20+
async-recursion = { version = "0.3.1", optional = true }
1921
encoding_rs = { version = "0.8.22", optional = true }
22+
tokio = { version = "0.2.22", features = ["fs", "io-util"], optional = true }
2023
serde = { version = "1.0", optional = true }
2124
memchr = "2.3.3"
2225

2326
[dev-dependencies]
2427
serde = { version = "1.0", features = ["derive"] }
2528
regex = "1"
29+
tokio = { version = "0.2.22", features = ["macros", "rt-threaded"] }
2630

2731
[lib]
2832
bench = false
@@ -31,6 +35,7 @@ bench = false
3135
default = []
3236
encoding = ["encoding_rs"]
3337
serialize = ["serde"]
38+
asynchronous = ["tokio", "async-recursion"]
3439

3540
[package.metadata.docs.rs]
3641
features = ["serialize"]

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ fn crates_io() -> Result<Html, DeError> {
210210

211211
### Credits
212212

213-
This has largely been inspired by [serde-xml-rs](https://github.com/RReverser/serde-xml-rs).
214-
quick-xml follows its convention for deserialization, including the
213+
This has largely been inspired by [serde-xml-rs](https://github.com/RReverser/serde-xml-rs).
214+
quick-xml follows its convention for deserialization, including the
215215
[`$value`](https://github.com/RReverser/serde-xml-rs#parsing-the-value-of-a-tag) special name.
216216

217217
### Parsing the "value" of a tag
@@ -234,6 +234,7 @@ Note that despite not focusing on performance (there are several unecessary copi
234234

235235
- `encoding`: support non utf8 xmls
236236
- `serialize`: support serde `Serialize`/`Deserialize`
237+
- `asynchronous`: support for `AsyncRead`s in `tokio`
237238

238239
## Performance
239240

examples/issue68.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#![allow(unused)]
22

3-
extern crate quick_xml;
4-
53
use quick_xml::events::Event;
64
use quick_xml::Reader;
75
use std::io::Read;
6+
#[cfg(feature = "asynchronous")]
7+
use tokio::runtime::Runtime;
88

99
struct Resource {
1010
etag: String,
@@ -81,8 +81,18 @@ fn parse_report(xml_data: &str) -> Vec<Resource> {
8181
let mut depth = 0;
8282
let mut state = State::MultiStatus;
8383

84+
#[cfg(feature = "asynchronous")]
85+
let mut runtime = Runtime::new().expect("Runtime cannot be initialized");
86+
8487
loop {
85-
match reader.read_namespaced_event(&mut buf, &mut ns_buffer) {
88+
#[cfg(feature = "asynchronous")]
89+
let event = runtime
90+
.block_on(async { reader.read_namespaced_event(&mut buf, &mut ns_buffer).await });
91+
92+
#[cfg(not(feature = "asynchronous"))]
93+
let event = reader.read_namespaced_event(&mut buf, &mut ns_buffer);
94+
95+
match event {
8696
Ok((namespace_value, Event::Start(e))) => {
8797
let namespace_value = namespace_value.unwrap_or_default();
8898
match (depth, state, namespace_value, e.local_name()) {

examples/nested_readers.rs

+29-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
extern crate quick_xml;
21
use quick_xml::events::Event;
32
use quick_xml::Reader;
3+
#[cfg(feature = "asynchronous")]
4+
use tokio::runtime::Runtime;
5+
46
// a structure to capture the rows we've extracted
57
// from a ECMA-376 table in document.xml
68
#[derive(Debug, Clone)]
@@ -16,10 +18,26 @@ fn main() -> Result<(), quick_xml::Error> {
1618
// buffer for nested reader
1719
let mut skip_buf = Vec::new();
1820
let mut count = 0;
21+
22+
#[cfg(feature = "asynchronous")]
23+
let mut runtime = Runtime::new().expect("Runtime cannot be initialized");
24+
25+
#[cfg(feature = "asynchronous")]
26+
let mut reader =
27+
runtime.block_on(async { Reader::from_file("tests/documents/document.xml").await })?;
28+
29+
#[cfg(not(feature = "asynchronous"))]
1930
let mut reader = Reader::from_file("tests/documents/document.xml")?;
31+
2032
let mut found_tables = Vec::new();
2133
loop {
22-
match reader.read_event(&mut buf)? {
34+
#[cfg(feature = "asynchronous")]
35+
let event = runtime.block_on(async { reader.read_event(&mut buf).await })?;
36+
37+
#[cfg(not(feature = "asynchronous"))]
38+
let event = reader.read_event(&mut buf)?;
39+
40+
match event {
2341
Event::Start(element) => match element.name() {
2442
b"w:tbl" => {
2543
count += 1;
@@ -32,7 +50,15 @@ fn main() -> Result<(), quick_xml::Error> {
3250
let mut row_index = 0;
3351
loop {
3452
skip_buf.clear();
35-
match reader.read_event(&mut skip_buf)? {
53+
54+
#[cfg(feature = "asynchronous")]
55+
let event =
56+
runtime.block_on(async { reader.read_event(&mut skip_buf).await })?;
57+
58+
#[cfg(not(feature = "asynchronous"))]
59+
let event = reader.read_event(&mut skip_buf)?;
60+
61+
match event {
3662
Event::Start(element) => match element.name() {
3763
b"w:tr" => {
3864
stats.rows.push(vec![]);

examples/read_texts.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
extern crate quick_xml;
1+
#[cfg(feature = "asynchronous")]
2+
use tokio::runtime::Runtime;
23

34
fn main() {
45
use quick_xml::events::Event;
@@ -13,14 +14,32 @@ fn main() {
1314
let mut txt = Vec::new();
1415
let mut buf = Vec::new();
1516

17+
#[cfg(feature = "asynchronous")]
18+
let mut runtime = Runtime::new().expect("Runtime cannot be initialized");
19+
1620
loop {
17-
match reader.read_event(&mut buf) {
21+
#[cfg(feature = "asynchronous")]
22+
let event = runtime.block_on(async { reader.read_event(&mut buf).await });
23+
24+
#[cfg(not(feature = "asynchronous"))]
25+
let event = reader.read_event(&mut buf);
26+
27+
match event {
1828
Ok(Event::Start(ref e)) if e.name() == b"tag2" => {
19-
txt.push(
29+
#[cfg(feature = "asynchronous")]
30+
let text = runtime.block_on(async {
2031
reader
2132
.read_text(b"tag2", &mut Vec::new())
22-
.expect("Cannot decode text value"),
23-
);
33+
.await
34+
.expect("Cannot decode text value")
35+
});
36+
37+
#[cfg(not(feature = "asynchronous"))]
38+
let text = reader
39+
.read_text(b"tag2", &mut Vec::new())
40+
.expect("Cannot decode text value");
41+
42+
txt.push(text);
2443
println!("{:?}", txt);
2544
}
2645
Ok(Event::Eof) => break, // exits the loop when reaching end of file

src/errors.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum Error {
3333
/// Duplicate attribute
3434
DuplicatedAttribute(usize, usize),
3535
/// Escape error
36-
EscapeError(::escape::EscapeError),
36+
EscapeError(crate::escape::EscapeError),
3737
}
3838

3939
impl From<::std::io::Error> for Error {

src/events/attributes.rs

+7-14
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
//!
33
//! Provides an iterator over attributes key/value pairs
44
5-
use errors::{Error, Result};
6-
use escape::{escape, unescape};
7-
use reader::{is_whitespace, Reader};
5+
use crate::errors::{Error, Result};
6+
use crate::escape::{escape, unescape};
7+
use crate::reader::{is_whitespace, Decode};
88
use std::borrow::Cow;
9-
use std::io::BufRead;
109
use std::ops::Range;
1110

1211
/// Iterator over XML attributes.
@@ -107,7 +106,7 @@ impl<'a> Attribute<'a> {
107106
/// [`unescaped_value()`]: #method.unescaped_value
108107
/// [`Reader::decode()`]: ../../reader/struct.Reader.html#method.decode
109108
#[cfg(feature = "encoding")]
110-
pub fn unescape_and_decode_value<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
109+
pub fn unescape_and_decode_value(&self, reader: &impl Decode) -> Result<String> {
111110
let decoded = reader.decode(&*self.value);
112111
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
113112
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -124,7 +123,7 @@ impl<'a> Attribute<'a> {
124123
/// [`unescaped_value()`]: #method.unescaped_value
125124
/// [`Reader::decode()`]: ../../reader/struct.Reader.html#method.decode
126125
#[cfg(not(feature = "encoding"))]
127-
pub fn unescape_and_decode_value<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
126+
pub fn unescape_and_decode_value(&self, reader: &impl Decode) -> Result<String> {
128127
let decoded = reader.decode(&*self.value)?;
129128
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
130129
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -138,10 +137,7 @@ impl<'a> Attribute<'a> {
138137
/// 1. BytesText::unescaped()
139138
/// 2. Reader::decode(...)
140139
#[cfg(feature = "encoding")]
141-
pub fn unescape_and_decode_without_bom<B: BufRead>(
142-
&self,
143-
reader: &mut Reader<B>,
144-
) -> Result<String> {
140+
pub fn unescape_and_decode_without_bom(&self, reader: &impl Decode) -> Result<String> {
145141
let decoded = reader.decode_without_bom(&*self.value);
146142
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
147143
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -155,10 +151,7 @@ impl<'a> Attribute<'a> {
155151
/// 1. BytesText::unescaped()
156152
/// 2. Reader::decode(...)
157153
#[cfg(not(feature = "encoding"))]
158-
pub fn unescape_and_decode_without_bom<B: BufRead>(
159-
&self,
160-
reader: &Reader<B>,
161-
) -> Result<String> {
154+
pub fn unescape_and_decode_without_bom(&self, reader: &impl Decode) -> Result<String> {
162155
let decoded = reader.decode_without_bom(&*self.value)?;
163156
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
164157
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))

src/events/mod.rs

+28-18
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ pub mod attributes;
55
#[cfg(feature = "encoding_rs")]
66
use encoding_rs::Encoding;
77
use std::borrow::Cow;
8-
use std::io::BufRead;
98
use std::ops::Deref;
109
use std::str::from_utf8;
1110

1211
use self::attributes::{Attribute, Attributes};
13-
use errors::{Error, Result};
14-
use escape::{escape, unescape};
15-
use reader::Reader;
12+
use crate::errors::{Error, Result};
13+
use crate::escape::{escape, unescape};
14+
use crate::reader::Decode;
1615

1716
use memchr;
1817

@@ -175,7 +174,7 @@ impl<'a> BytesStart<'a> {
175174
/// [`Reader::decode()`]: ../reader/struct.Reader.html#method.decode
176175
#[cfg(feature = "encoding")]
177176
#[inline]
178-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
177+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
179178
let decoded = reader.decode(&*self);
180179
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
181180
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -193,7 +192,7 @@ impl<'a> BytesStart<'a> {
193192
/// [`Reader::decode()`]: ../reader/struct.Reader.html#method.decode
194193
#[cfg(not(feature = "encoding"))]
195194
#[inline]
196-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
195+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
197196
let decoded = reader.decode(&*self)?;
198197
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
199198
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -495,10 +494,7 @@ impl<'a> BytesText<'a> {
495494
/// 1. BytesText::unescaped()
496495
/// 2. Reader::decode(...)
497496
#[cfg(feature = "encoding")]
498-
pub fn unescape_and_decode_without_bom<B: BufRead>(
499-
&self,
500-
reader: &mut Reader<B>,
501-
) -> Result<String> {
497+
pub fn unescape_and_decode_without_bom(&self, reader: &mut impl Decode) -> Result<String> {
502498
let decoded = reader.decode_without_bom(&*self);
503499
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
504500
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -512,10 +508,7 @@ impl<'a> BytesText<'a> {
512508
/// 1. BytesText::unescaped()
513509
/// 2. Reader::decode(...)
514510
#[cfg(not(feature = "encoding"))]
515-
pub fn unescape_and_decode_without_bom<B: BufRead>(
516-
&self,
517-
reader: &Reader<B>,
518-
) -> Result<String> {
511+
pub fn unescape_and_decode_without_bom(&self, reader: &impl Decode) -> Result<String> {
519512
let decoded = reader.decode_without_bom(&*self)?;
520513
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
521514
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -528,7 +521,7 @@ impl<'a> BytesText<'a> {
528521
/// 1. BytesText::unescaped()
529522
/// 2. Reader::decode(...)
530523
#[cfg(feature = "encoding")]
531-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
524+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
532525
let decoded = reader.decode(&*self);
533526
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
534527
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -541,7 +534,7 @@ impl<'a> BytesText<'a> {
541534
/// 1. BytesText::unescaped()
542535
/// 2. Reader::decode(...)
543536
#[cfg(not(feature = "encoding"))]
544-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
537+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
545538
let decoded = reader.decode(&*self)?;
546539
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
547540
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -663,6 +656,8 @@ impl<'a> AsRef<Event<'a>> for Event<'a> {
663656
#[cfg(test)]
664657
mod test {
665658
use super::*;
659+
#[cfg(feature = "asynchronous")]
660+
use tokio::runtime::Runtime;
666661

667662
#[test]
668663
fn local_name() {
@@ -673,11 +668,25 @@ mod test {
673668
<:foo attr='bar'>foobusbar</:foo>
674669
<foo:bus:baz attr='bar'>foobusbar</foo:bus:baz>
675670
"#;
676-
let mut rdr = Reader::from_str(xml);
671+
let mut rdr = crate::Reader::from_str(xml);
677672
let mut buf = Vec::new();
678673
let mut parsed_local_names = Vec::new();
674+
675+
#[cfg(feature = "asynchronous")]
676+
let mut runtime = Runtime::new().expect("Runtime cannot be initialized");
677+
679678
loop {
680-
match rdr.read_event(&mut buf).expect("unable to read xml event") {
679+
#[cfg(feature = "asynchronous")]
680+
let event = runtime.block_on(async {
681+
rdr.read_event(&mut buf)
682+
.await
683+
.expect("unable to read xml event")
684+
});
685+
686+
#[cfg(not(feature = "asynchronous"))]
687+
let event = rdr.read_event(&mut buf).expect("unable to read xml event");
688+
689+
match event {
681690
Event::Start(ref e) => parsed_local_names.push(
682691
from_utf8(e.local_name())
683692
.expect("unable to build str from local_name")
@@ -692,6 +701,7 @@ mod test {
692701
_ => {}
693702
}
694703
}
704+
695705
assert_eq!(parsed_local_names[0], "bus".to_string());
696706
assert_eq!(parsed_local_names[1], "bus".to_string());
697707
assert_eq!(parsed_local_names[2], "".to_string());

0 commit comments

Comments
 (0)