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
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ before_script:

script:
- if [[ $(rustup show active-toolchain) == stable* ]]; then cargo fmt -- --check; fi;
- cargo build
- cargo build --no-default-features
- cargo test
- cargo test --all-features
- cargo test --no-default-features
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "quick-xml"
version = "0.17.0"
version = "0.17.1"
authors = ["Johann Tuffe <[email protected]>"]
description = "High performance xml reader and writer"

Expand All @@ -17,11 +17,19 @@ travis-ci = { repository = "tafia/quick-xml" }

[dependencies]
encoding_rs = { version = "0.8.17", optional = true }
serde = { version = "1.0", optional = true }
memchr = "2.2.1"

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }

[lib]
bench = false

[features]
default = []
encoding = ["encoding_rs"]
serialize = ["serde"]

[package.metadata.docs.rs]
features = ["serialize"]
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
- test: Adding missing tests
- chore: Changes to the build process or auxiliary tools/libraries/documentation

## 0.17.1
- feat: add new `serialize` feature to support serde serialize/deserialize

## 0.17.0
- perf: speed up (un)escape a little
- feat: remove failure completely (breaking change) and implement `std::error::Error` for `Error`
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ assert_eq!(result, expected.as_bytes());
# Features

- `encoding`: support non utf8 xmls
- `serialize`: support serde `Serialize`/`Deserialize`

## Performance

Expand Down
89 changes: 89 additions & 0 deletions src/de/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::errors::Error;
use std::fmt;

/// Deserialization error
#[derive(Debug)]
pub enum DeError {
/// Serde custom error
Custom(String),
/// Cannot parse to integer
Int(std::num::ParseIntError),
/// Cannot parse to float
Float(std::num::ParseFloatError),
/// Xml parsing error
Xml(Error),
/// Unexpected end of attributes
EndOfAttributes,
/// Unexpected end of file
Eof,
/// Invalid value for a boolean
InvalidBoolean(String),
/// Invalid unit value
InvalidUnit(String),
/// Invalid event for Enum
InvalidEnum(crate::events::Event<'static>),
/// Expecting Text event
Text,
/// Expecting Start event
Start,
/// Expecting End event
End,
}

impl fmt::Display for DeError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
DeError::Custom(s) => write!(f, "{}", s),
DeError::Xml(e) => write!(f, "{}", e),
DeError::Int(e) => write!(f, "{}", e),
DeError::Float(e) => write!(f, "{}", e),
DeError::EndOfAttributes => write!(f, "Unexpected end of attributes"),
DeError::Eof => write!(f, "Unexpected `Event::Eof`"),
DeError::InvalidBoolean(v) => write!(f, "Invalid boolean value '{}'", v),
DeError::InvalidUnit(v) => {
write!(f, "Invalid unit value '{}', expected empty string", v)
}
DeError::InvalidEnum(e) => write!(
f,
"Invalid event for Enum, expecting Text or Start, got: {:?}",
e
),
DeError::Text => write!(f, "Expecting Text event"),
DeError::Start => write!(f, "Expecting Start event"),
DeError::End => write!(f, "Expecting End event"),
}
}
}

impl ::std::error::Error for DeError {
fn description(&self) -> &str {
"xml deserialize error"
}
fn cause(&self) -> Option<&dyn (::std::error::Error)> {
None
}
}

impl serde::de::Error for DeError {
fn custom<T: fmt::Display>(msg: T) -> Self {
DeError::Custom(msg.to_string())
}
}

impl From<Error> for DeError {
fn from(e: Error) -> Self {
DeError::Xml(e)
}
}

impl From<std::num::ParseIntError> for DeError {
fn from(e: std::num::ParseIntError) -> Self {
DeError::Int(e)
}
}

impl From<std::num::ParseFloatError> for DeError {
fn from(e: std::num::ParseFloatError) -> Self {
DeError::Float(e)
}
}
244 changes: 244 additions & 0 deletions src/de/escape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
//! Serde `Deserializer` module

use crate::{de::errors::DeError, errors::Error, escape::unescape, reader::Decoder};
use serde::de::{self, Visitor};
use serde::{self, forward_to_deserialize_any};
use std::borrow::Cow;

/// A deserializer for a xml escaped and encoded value
///
/// # Note
///
/// Escaping the value is actually not always necessary, for instance
/// when converting to float, we don't expect any escapable character
/// anyway
#[derive(Clone)]
pub(crate) struct EscapedDeserializer {
decoder: Decoder,
escaped_value: Vec<u8>,
escaped: bool,
}

impl EscapedDeserializer {
pub fn new(escaped_value: Vec<u8>, decoder: Decoder, escaped: bool) -> Self {
EscapedDeserializer {
decoder,
escaped_value,
escaped,
}
}
fn unescaped(&self) -> Result<Cow<[u8]>, DeError> {
if self.escaped {
unescape(&self.escaped_value).map_err(|e| DeError::Xml(Error::EscapeError(e)))
} else {
Ok(Cow::Borrowed(&self.escaped_value))
}
}
}

macro_rules! deserialize_num {
($method:ident, $visit:ident) => {
fn $method<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
#[cfg(not(feature = "encoding"))]
let value = self.decoder.decode(&self.escaped_value)?.parse()?;

#[cfg(feature = "encoding")]
let value = self.decoder.decode(&self.escaped_value).parse()?;

visitor.$visit(value)
}
}
}

impl<'de> serde::Deserializer<'de> for EscapedDeserializer {
type Error = DeError;

fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}

fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let unescaped = self.unescaped()?;
#[cfg(not(feature = "encoding"))]
let value = self.decoder.decode(&unescaped)?;

#[cfg(feature = "encoding")]
let value = self.decoder.decode(&unescaped);
visitor.visit_str(&value)
}

fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
let v = self.unescaped()?;
visitor.visit_bytes(&v)
}

fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_bytes(visitor)
}

fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}

fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
#[cfg(feature = "encoding")]
{
#[cfg(feature = "encoding")]
let value = self.decoder.decode(&self.escaped_value);

match value.as_ref() {
"true" | "1" | "True" | "TRUE" | "t" | "Yes" | "YES" | "yes" | "y" => {
visitor.visit_bool(true)
}
"false" | "0" | "False" | "FALSE" | "f" | "No" | "NO" | "no" | "n" => {
visitor.visit_bool(false)
}
_ => Err(DeError::InvalidBoolean(value.into())),
}
}

#[cfg(not(feature = "encoding"))]
{
match &*self.escaped_value {
b"true" | b"1" | b"True" | b"TRUE" | b"t" | b"Yes" | b"YES" | b"yes" | b"y" => {
visitor.visit_bool(true)
}
b"false" | b"0" | b"False" | b"FALSE" | b"f" | b"No" | b"NO" | b"no" | b"n" => {
visitor.visit_bool(false)
}
e => Err(DeError::InvalidBoolean(self.decoder.decode(e)?.into())),
}
}
}

fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_str(visitor)
}

fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.escaped_value.is_empty() {
visitor.visit_unit()
} else {
Err(DeError::InvalidUnit(
"Expecting unit, got non empty attribute".into(),
))
}
}

fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
if self.escaped_value.is_empty() {
visitor.visit_none()
} else {
visitor.visit_some(self)
}
}

fn deserialize_enum<V: de::Visitor<'de>>(
self,
_name: &str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
visitor.visit_enum(self)
}

fn deserialize_newtype_struct<V>(
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}

deserialize_num!(deserialize_i64, visit_i64);
deserialize_num!(deserialize_i32, visit_i32);
deserialize_num!(deserialize_i16, visit_i16);
deserialize_num!(deserialize_i8, visit_i8);
deserialize_num!(deserialize_u64, visit_u64);
deserialize_num!(deserialize_u32, visit_u32);
deserialize_num!(deserialize_u16, visit_u16);
deserialize_num!(deserialize_u8, visit_u8);
deserialize_num!(deserialize_f64, visit_f64);
deserialize_num!(deserialize_f32, visit_f32);

forward_to_deserialize_any! {
unit_struct seq tuple tuple_struct map struct identifier ignored_any
}
}

impl<'de> de::EnumAccess<'de> for EscapedDeserializer {
type Error = DeError;
type Variant = Self;

fn variant_seed<V: de::DeserializeSeed<'de>>(
self,
seed: V,
) -> Result<(V::Value, Self), DeError> {
let name = seed.deserialize(self.clone())?;
Ok((name, self))
}
}

impl<'de> de::VariantAccess<'de> for EscapedDeserializer {
type Error = DeError;

fn unit_variant(self) -> Result<(), DeError> {
Ok(())
}

fn newtype_variant_seed<T: de::DeserializeSeed<'de>>(
self,
seed: T,
) -> Result<T::Value, DeError> {
seed.deserialize(self)
}

fn tuple_variant<V: de::Visitor<'de>>(
self,
_len: usize,
_visitor: V,
) -> Result<V::Value, DeError> {
unimplemented!()
}

fn struct_variant<V: de::Visitor<'de>>(
self,
_fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, DeError> {
unimplemented!()
}
}
Loading