Skip to content

Commit 40d2973

Browse files
committed
Add reserved name #content to flatten element hierarchies.
1 parent 1d117c1 commit 40d2973

File tree

2 files changed

+86
-4
lines changed

2 files changed

+86
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
88
repository = "https://github.com/adamreichold/serde-roxmltree"
99
documentation = "https://docs.rs/serde-roxmltree"
1010
readme = "README.md"
11-
version = "0.8.4"
11+
version = "0.8.5"
1212
edition = "2021"
1313

1414
[dependencies]

src/lib.rs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,39 @@
122122
//! # Ok::<(), Box<dyn std::error::Error>>(())
123123
//! ```
124124
//!
125+
//! The reserved name `#content` is used to flatten one level of the hierarchy and
126+
//! revisit those nodes and attributes as if embedded inside another struct. This can
127+
//! useful to handle partial alternatives:
128+
//!
129+
//! ```
130+
//! use serde::Deserialize;
131+
//! use serde_roxmltree::from_str;
132+
//!
133+
//! #[derive(Debug, PartialEq, Deserialize)]
134+
//! #[serde(rename_all = "lowercase")]
135+
//! enum Alternative {
136+
//! Float(f32),
137+
//! Integer(i32),
138+
//! }
139+
//!
140+
//! #[derive(Debug, PartialEq, Deserialize)]
141+
//! struct Record {
142+
//! #[serde(rename = "#content")]
143+
//! alternative: Alternative,
144+
//! string: String,
145+
//! }
146+
//!
147+
//! let record = from_str::<Record>("<record><float>42.0</float><string>foo</string></record>")?;
148+
//! assert_eq!(record.alternative, Alternative::Float(42.0));
149+
//! assert_eq!(record.string, "foo");
150+
//!
151+
//! let record = from_str::<Record>("<record><integer>23</integer><string>bar</string></record>")?;
152+
//! assert_eq!(record.alternative, Alternative::Integer(23));
153+
//! assert_eq!(record.string, "bar");
154+
//! #
155+
//! # Ok::<(), Box<dyn std::error::Error>>(())
156+
//! ```
157+
//!
125158
//! Optionally, attribute names can be prefixed by `@` to distinguish them from tag names:
126159
//!
127160
//! ```
@@ -362,6 +395,7 @@ enum Source<'de, 'input> {
362395
Node(Node<'de, 'input>),
363396
Attribute(Attribute<'de, 'input>),
364397
Text(&'de str),
398+
Content(Node<'de, 'input>),
365399
}
366400

367401
#[derive(Default)]
@@ -440,12 +474,13 @@ where
440474
}
441475
}
442476
Source::Text(_) => "$text",
477+
Source::Content(_) => "#content",
443478
}
444479
}
445480

446481
fn node(&self) -> Result<&Node<'de, 'input>, Error> {
447482
match &self.source {
448-
Source::Node(node) => Ok(node),
483+
Source::Node(node) | Source::Content(node) => Ok(node),
449484
Source::Attribute(_) | Source::Text(_) => Err(Error::MissingNode),
450485
}
451486
}
@@ -473,7 +508,9 @@ where
473508

474509
let text = once(Source::Text(node.text().unwrap_or_default()));
475510

476-
Ok(children.chain(attributes).chain(text))
511+
let content = once(Source::Content(*node));
512+
513+
Ok(children.chain(attributes).chain(text).chain(content))
477514
}
478515

479516
fn siblings(&self) -> Result<impl Iterator<Item = Node<'de, 'de>>, Error> {
@@ -494,7 +531,7 @@ where
494531

495532
fn text(&self) -> &'de str {
496533
match self.source {
497-
Source::Node(node) => node.text().unwrap_or_default(),
534+
Source::Node(node) | Source::Content(node) => node.text().unwrap_or_default(),
498535
Source::Attribute(attr) => attr.value(),
499536
Source::Text(text) => text,
500537
}
@@ -1174,6 +1211,51 @@ mod tests {
11741211
assert_eq!(val, Root::Bar(42));
11751212
}
11761213

1214+
#[test]
1215+
fn mixed_enum_and_struct_children() {
1216+
#[derive(Debug, PartialEq, Deserialize)]
1217+
enum Foobar {
1218+
Foo(u32),
1219+
Bar(i64),
1220+
}
1221+
1222+
#[derive(Deserialize)]
1223+
struct Root {
1224+
#[serde(rename = "#content")]
1225+
foobar: Foobar,
1226+
qux: f32,
1227+
}
1228+
1229+
let val = from_str::<Root>(r#"<root><Foo>23</Foo><qux>42.0</qux></root>"#).unwrap();
1230+
assert_eq!(val.foobar, Foobar::Foo(23));
1231+
assert_eq!(val.qux, 42.0);
1232+
}
1233+
1234+
#[test]
1235+
fn mixed_enum_and_repeated_struct_children() {
1236+
#[derive(Debug, PartialEq, Deserialize)]
1237+
enum Foobar {
1238+
Foo(u32),
1239+
Bar(i64),
1240+
}
1241+
1242+
#[derive(Deserialize)]
1243+
struct Root {
1244+
#[serde(rename = "#content")]
1245+
foobar: Foobar,
1246+
qux: Vec<f32>,
1247+
baz: String,
1248+
}
1249+
1250+
let val = from_str::<Root>(
1251+
r#"<root><Bar>42</Bar><qux>1.0</qux><baz>baz</baz><qux>2.0</qux><qux>3.0</qux></root>"#,
1252+
)
1253+
.unwrap();
1254+
assert_eq!(val.foobar, Foobar::Bar(42));
1255+
assert_eq!(val.qux, [1.0, 2.0, 3.0]);
1256+
assert_eq!(val.baz, "baz");
1257+
}
1258+
11771259
#[test]
11781260
fn borrowed_str() {
11791261
let doc = Document::parse("<root><child>foobar</child></root>").unwrap();

0 commit comments

Comments
 (0)