Skip to content

Commit 310ff7e

Browse files
committed
Allow using tokio's AsyncBufRead
1 parent c1a4375 commit 310ff7e

16 files changed

+1740
-362
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "quick-xml"
33
version = "0.18.1"
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,12 +17,15 @@ 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"] }
28+
tokio = { version = "0.2.22", features = ["macros", "rt-threaded"] }
2529

2630
[lib]
2731
bench = false
@@ -30,6 +34,7 @@ bench = false
3034
default = []
3135
encoding = ["encoding_rs"]
3236
serialize = ["serde"]
37+
asynchronous = ["tokio", "async-recursion"]
3338

3439
[package.metadata.docs.rs]
3540
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

+14-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,19 @@ 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.block_on(async {
90+
reader.read_namespaced_event(&mut buf, &mut ns_buffer).await
91+
});
92+
93+
#[cfg(not(feature = "asynchronous"))]
94+
let event = reader.read_namespaced_event(&mut buf, &mut ns_buffer);
95+
96+
match event {
8697
Ok((namespace_value, Event::Start(e))) => {
8798
let namespace_value = namespace_value.unwrap_or_default();
8899
match (depth, state, namespace_value, e.local_name()) {

examples/nested_readers.rs

+33-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,29 @@ 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 = runtime.block_on(async {
27+
Reader::from_file("tests/documents/document.xml").await
28+
})?;
29+
30+
#[cfg(not(feature = "asynchronous"))]
1931
let mut reader = Reader::from_file("tests/documents/document.xml")?;
32+
2033
let mut found_tables = Vec::new();
2134
loop {
22-
match reader.read_event(&mut buf)? {
35+
#[cfg(feature = "asynchronous")]
36+
let event = runtime.block_on(async {
37+
reader.read_event(&mut buf).await
38+
})?;
39+
40+
#[cfg(not(feature = "asynchronous"))]
41+
let event = reader.read_event(&mut buf)?;
42+
43+
match event {
2344
Event::Start(element) => match element.name() {
2445
b"w:tbl" => {
2546
count += 1;
@@ -32,7 +53,16 @@ fn main() -> Result<(), quick_xml::Error> {
3253
let mut row_index = 0;
3354
loop {
3455
skip_buf.clear();
35-
match reader.read_event(&mut skip_buf)? {
56+
57+
#[cfg(feature = "asynchronous")]
58+
let event = runtime.block_on(async {
59+
reader.read_event(&mut skip_buf).await
60+
})?;
61+
62+
#[cfg(not(feature = "asynchronous"))]
63+
let event = reader.read_event(&mut skip_buf)?;
64+
65+
match event {
3666
Event::Start(element) => match element.name() {
3767
b"w:tr" => {
3868
stats.rows.push(vec![]);

examples/read_texts.rs

+26-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,34 @@ 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 {
23+
reader.read_event(&mut buf).await
24+
});
25+
26+
#[cfg(not(feature = "asynchronous"))]
27+
let event = reader.read_event(&mut buf);
28+
29+
match event {
1830
Ok(Event::Start(ref e)) if e.name() == b"tag2" => {
19-
txt.push(
31+
#[cfg(feature = "asynchronous")]
32+
let text = runtime.block_on(async {
2033
reader
2134
.read_text(b"tag2", &mut Vec::new())
22-
.expect("Cannot decode text value"),
23-
);
35+
.await
36+
.expect("Cannot decode text value")
37+
});
38+
39+
#[cfg(not(feature = "asynchronous"))]
40+
let text = reader
41+
.read_text(b"tag2", &mut Vec::new())
42+
.expect("Cannot decode text value");
43+
44+
txt.push(text);
2445
println!("{:?}", txt);
2546
}
2647
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

+9-10
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,9 +137,9 @@ 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>(
140+
pub fn unescape_and_decode_without_bom(
142141
&self,
143-
reader: &mut Reader<B>,
142+
reader: &impl Decode,
144143
) -> Result<String> {
145144
let decoded = reader.decode_without_bom(&*self.value);
146145
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
@@ -155,9 +154,9 @@ impl<'a> Attribute<'a> {
155154
/// 1. BytesText::unescaped()
156155
/// 2. Reader::decode(...)
157156
#[cfg(not(feature = "encoding"))]
158-
pub fn unescape_and_decode_without_bom<B: BufRead>(
157+
pub fn unescape_and_decode_without_bom(
159158
&self,
160-
reader: &Reader<B>,
159+
reader: &impl Decode,
161160
) -> Result<String> {
162161
let decoded = reader.decode_without_bom(&*self.value)?;
163162
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;

src/events/mod.rs

+28-14
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()))
@@ -488,9 +487,9 @@ impl<'a> BytesText<'a> {
488487
/// 1. BytesText::unescaped()
489488
/// 2. Reader::decode(...)
490489
#[cfg(feature = "encoding")]
491-
pub fn unescape_and_decode_without_bom<B: BufRead>(
490+
pub fn unescape_and_decode_without_bom(
492491
&self,
493-
reader: &mut Reader<B>,
492+
reader: &mut impl Decode,
494493
) -> Result<String> {
495494
let decoded = reader.decode_without_bom(&*self);
496495
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
@@ -505,9 +504,9 @@ impl<'a> BytesText<'a> {
505504
/// 1. BytesText::unescaped()
506505
/// 2. Reader::decode(...)
507506
#[cfg(not(feature = "encoding"))]
508-
pub fn unescape_and_decode_without_bom<B: BufRead>(
507+
pub fn unescape_and_decode_without_bom(
509508
&self,
510-
reader: &Reader<B>,
509+
reader: &impl Decode,
511510
) -> Result<String> {
512511
let decoded = reader.decode_without_bom(&*self)?;
513512
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
@@ -521,7 +520,7 @@ impl<'a> BytesText<'a> {
521520
/// 1. BytesText::unescaped()
522521
/// 2. Reader::decode(...)
523522
#[cfg(feature = "encoding")]
524-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
523+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
525524
let decoded = reader.decode(&*self);
526525
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
527526
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -534,7 +533,7 @@ impl<'a> BytesText<'a> {
534533
/// 1. BytesText::unescaped()
535534
/// 2. Reader::decode(...)
536535
#[cfg(not(feature = "encoding"))]
537-
pub fn unescape_and_decode<B: BufRead>(&self, reader: &Reader<B>) -> Result<String> {
536+
pub fn unescape_and_decode(&self, reader: &impl Decode) -> Result<String> {
538537
let decoded = reader.decode(&*self)?;
539538
let unescaped = unescape(decoded.as_bytes()).map_err(Error::EscapeError)?;
540539
String::from_utf8(unescaped.into_owned()).map_err(|e| Error::Utf8(e.utf8_error()))
@@ -656,6 +655,8 @@ impl<'a> AsRef<Event<'a>> for Event<'a> {
656655
#[cfg(test)]
657656
mod test {
658657
use super::*;
658+
#[cfg(feature = "asynchronous")]
659+
use tokio::runtime::Runtime;
659660

660661
#[test]
661662
fn local_name() {
@@ -666,11 +667,23 @@ mod test {
666667
<:foo attr='bar'>foobusbar</:foo>
667668
<foo:bus:baz attr='bar'>foobusbar</foo:bus:baz>
668669
"#;
669-
let mut rdr = Reader::from_str(xml);
670+
let mut rdr = crate::Reader::from_str(xml);
670671
let mut buf = Vec::new();
671672
let mut parsed_local_names = Vec::new();
673+
674+
#[cfg(feature = "asynchronous")]
675+
let mut runtime = Runtime::new().expect("Runtime cannot be initialized");
676+
672677
loop {
673-
match rdr.read_event(&mut buf).expect("unable to read xml event") {
678+
#[cfg(feature = "asynchronous")]
679+
let event = runtime.block_on(async {
680+
rdr.read_event(&mut buf).await.expect("unable to read xml event")
681+
});
682+
683+
#[cfg(not(feature = "asynchronous"))]
684+
let event = rdr.read_event(&mut buf).expect("unable to read xml event");
685+
686+
match event {
674687
Event::Start(ref e) => parsed_local_names.push(
675688
from_utf8(e.local_name())
676689
.expect("unable to build str from local_name")
@@ -685,6 +698,7 @@ mod test {
685698
_ => {}
686699
}
687700
}
701+
688702
assert_eq!(parsed_local_names[0], "bus".to_string());
689703
assert_eq!(parsed_local_names[1], "bus".to_string());
690704
assert_eq!(parsed_local_names[2], "".to_string());

0 commit comments

Comments
 (0)