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
3 changes: 0 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]


name: Lint

Expand Down
13 changes: 5 additions & 8 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@ name: Rust

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
109 changes: 108 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,109 @@
target
# Created by https://www.toptal.com/developers/gitignore/api/git,rust,linux,macos,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=git,rust,linux,macos,windows

### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig

# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/git,rust,linux,macos,windows
16 changes: 0 additions & 16 deletions .travis.yml

This file was deleted.

17 changes: 11 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
[package]
authors = ["Ingvar Stepanyan <me@rreverser.com>"]
authors = [
"Ingvar Stepanyan <me@rreverser.com>",
"William Bartlett <bartlettstarman@gmail.com>",
]
description = "xml-rs based deserializer for Serde (compatible with 1.0)"
license = "MIT"
name = "serde-xml-rs"
repository = "https://github.com/RReverser/serde-xml-rs"
version = "0.7.1"
edition = "2018"
version = "0.8.0"
edition = "2021"

[dependencies]
log = "0.4"
Expand All @@ -15,7 +18,9 @@ thiserror = "1.0"

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
simple_logger = "2.1"
docmatic = "0.1"
rstest = "0.12"
rstest = "0.25"
indoc = "2.0.6"
test-log = "0.2.17"
serde_bytes = "0.11.17"
serde-query = "0.2.0"
chrono = { version = "0.4.40", default-features = false, features = ["serde"] }
14 changes: 14 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Migration across breaking changes

## From 0.6.x or 0.7.x to 0.8.0

Breaking changes:
- Element content is now deserialized to a field named `#content` instead of `$value`.
- Attributes must now be deserialized to fields named `@...`, mirroring what was introduced in the serializer.
- Tuples become string only. This will be addressed in a future release.
- Namespace support means that namespace prefixes are now added to element names and attributes. You might have to rename some of your struct fields and enum variants to match.

Tips for migrating:
- Replace `$value` with `#content`.
- Rename any fields that must be deserialized from attributes to include the `@` prefix. For example, field `foo` becomes `@foo`.
- If complex tuples are important, please file an issue with details on the XML format you want to (de)serialize and the Rust types you would prefer to (de)serialize to/from.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# serde-xml-rs

[![Build Status](https://travis-ci.org/RReverser/serde-xml-rs.svg?branch=master)](https://travis-ci.org/RReverser/serde-xml-rs)
[![Rust](https://github.com/RReverser/serde-xml-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/RReverser/serde-xml-rs/actions/workflows/rust.yml)

`xml-rs` based deserializer for Serde (compatible with 1.0)
`xml-rs` based serializer and deserializer for Serde (compatible with 1.0)

## Example usage

Expand Down Expand Up @@ -30,3 +30,11 @@ fn main() {
assert_eq!(src, reserialized_item);
}
```

## Breaking changes in version 0.8.0

Notably:
- The `$value` name has been changed to `#content` (could become configurable in the future).
- Fields that are deserialized from attributes must now have a name that starts with a `@`. This aligns with what was introduced in the serializer.

See MIGRATION.md for more details, and tips on how to migrate.
146 changes: 146 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::{error::Result, Deserializer, Serializer};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
io::{Read, Write},
};
use xml::{
namespace::NS_NO_PREFIX, writer::events::StartElementBuilder, EmitterConfig, ParserConfig,
};

pub const TEXT: &str = "#text";
pub const CONTENT: &str = "#content";

#[derive(Clone, Debug)]
pub struct SerdeXml {
pub(crate) emitter: EmitterConfig,
pub(crate) parser: ParserConfig,
pub(crate) namespaces: Namespaces,
pub(crate) overlapping_sequences: bool,
}

impl Default for SerdeXml {
fn default() -> Self {
Self {
emitter: Default::default(),
parser: ParserConfig::new()
.trim_whitespace(true)
.whitespace_to_characters(true)
.cdata_to_characters(true)
.ignore_comments(true)
.coalesce_characters(true),
namespaces: Default::default(),
overlapping_sequences: false,
}
}
}

impl SerdeXml {
pub fn new() -> Self {
Default::default()
}

pub fn emitter(mut self, emitter: EmitterConfig) -> Self {
self.emitter = emitter;
self
}

pub fn parser(mut self, parser: ParserConfig) -> Self {
self.parser = parser;
self
}

pub fn default_namespace<S: ToString>(mut self, name: S) -> Self {
self.namespaces.put_default(name);
self
}

pub fn namespace<S: ToString>(mut self, prefix: S, name: S) -> Self {
self.namespaces.put(prefix, name);
self
}

/// Configures whether the deserializer should search all sibling elements when building a
/// sequence. Not required if all XML elements for sequences are adjacent. Disabled by
/// default. Enabling this option may incur additional memory usage.
///
/// ```rust
/// # use serde::Deserialize;
/// # use serde_xml_rs::from_reader;
/// #[derive(Debug, Deserialize, PartialEq)]
/// struct Foo {
/// bar: Vec<usize>,
/// baz: String,
/// }
/// # fn main() {
/// let s = r##"
/// <foo>
/// <bar>1</bar>
/// <bar>2</bar>
/// <baz>Hello, world</baz>
/// <bar>3</bar>
/// <bar>4</bar>
/// </foo>
/// "##;
/// let foo: Foo = serde_xml_rs::SerdeXml::new().overlapping_sequences(true).from_str(s).unwrap();
/// assert_eq!(foo, Foo { bar: vec![1, 2, 3, 4], baz: "Hello, world".to_string()});
/// # }
/// ```
pub fn overlapping_sequences(mut self, b: bool) -> Self {
self.overlapping_sequences = b;
self
}

pub fn from_str<'de, T: Deserialize<'de>>(self, s: &str) -> Result<T> {
self.from_reader(s.as_bytes())
}

pub fn from_reader<'de, T: Deserialize<'de>, R: Read>(self, reader: R) -> Result<T> {
T::deserialize(&mut Deserializer::from_config(self, reader))
}

pub fn to_string<S: Serialize>(self, value: &S) -> Result<String> {
let mut buffer = Vec::new();
self.to_writer(&mut buffer, value)?;
Ok(String::from_utf8(buffer)?)
}

pub fn to_writer<W, S>(self, writer: W, value: &S) -> Result<()>
where
W: Write,
S: Serialize,
{
let mut s = Serializer::from_config(self, writer);
value.serialize(&mut s)
}
}

#[derive(Clone, Debug, Default)]
pub struct Namespaces {
mapping: BTreeMap<String, String>,
}

impl Namespaces {
pub fn put_default<S: ToString>(&mut self, name: S) {
self.mapping
.insert(NS_NO_PREFIX.to_string(), name.to_string());
}

pub fn put<S: ToString>(&mut self, prefix: S, name: S) {
self.mapping.insert(prefix.to_string(), name.to_string());
}

pub fn get<S: AsRef<str>>(&self, prefix: S) -> Option<&String> {
self.mapping.get(prefix.as_ref())
}

pub(crate) fn add_to_start_element<'a>(
&self,
mut start_element_builder: StartElementBuilder<'a>,
) -> StartElementBuilder<'a> {
for (prefix, name) in &self.mapping {
start_element_builder = start_element_builder.ns(prefix, name);
}
start_element_builder
}
}
Loading
Loading