An XML writer (maybe it'll have read someday, but for now I recommend quick-xml for deserializing) library that should be quick and easy to implement, with ergonomics and flexibility as the core goal.
Why make this when quick-xml exists?
I personally don't like how quick-xml handles writing. It's very fast, stable, and well supported. It also isn't very easy to use to write, and its documentation for that use-case is generally lacking. It also doesn't effectively support namespaces.
Quick-xml (especially with serde) is extremely good for reading XML.
Why no serde feature?
Two reasons:
- There's no deserializer implemented for flexml
- Some of quick-xmls issues with supporting a few XML features are stated as not being particularly nice for the XML spec.
If you'd like to change this, you're welcome to submit a pull request, and I am welcome to deny it if I don't like it.
Features
macros: Enables the flexml::macros::XMLNode procedural macro to implement the [IntoXMLNode] trait.
Examples
Macro usage example
use flexml::IntoXMLNode;
use flexml::macros::XMLNode;
#[derive(XMLNode)]
#[name("foo")]
#[namespaces(("Namespace1", "https://namespace1.com/namespace"),
("Namespace2", "https://namespace2.com/namespace"))]
#[namespace("Namespace1")]
struct Foo {
#[node]
data1: Vec<Node>,
#[node]
#[namespace("Namespace1")]
data2: Node,
#[attribute]
attrib1: String,
#[attribute("UpperCamelCase")]
attrib2: &'static str,
}
#[derive(XMLNode)]
struct Node {
#[node]
data1: String,
#[node]
data2: Vec<Node>,
}
fn foo() {
let test_structure = Foo {
data1: vec![Node {
data1: "First node, first datapoint".to_string(),
data2: vec![],
}],
data2: Node {
data1: String::from("String mixed with "),
data2: vec![Node {
data1: "Second node, sub-datapoint".to_string(),
data2: vec![],
}],
},
attrib1: "Attribute_value".to_string(),
attrib2: "Attribute_value_2",
};
assert_eq!(
r#"<n:foo attrib1="Attribute_value" Attrib2="Attribute_value_2" xmlns:n="https://namespace1.com/namespace"><Node>First node, first datapoint</Node><n:Node>String mixed with <Node>Second node, sub-datapoint</Node></n:Node></n:foo>"#,
test_structure.to_xml().to_string()
)
}
Which is equivalent to this non-macro implementation
use flexml::IntoXMLNode;
use flexml::XMLData;
use flexml::XMLNode;
use flexml::XMLNamespaces;
struct Root {
data1: Vec<Node>,
data2: Node,
attrib1: String,
attrib2: &'static str,
}
impl IntoXMLNode for Root {
fn to_xml(&self) -> XMLNode {
XMLNamespaces::insert("Namespace1",
"https://namespace1.com/namespace")
.expect("failed to insert namespace");
XMLNamespaces::insert("Namespace2",
"https://namespace2.com/namespace")
.expect("failed to insert namespace");
let data1_nodes: Vec<XMLNode> = self.data1.iter()
.map(|n| n.to_xml()).collect();
let node = XMLNode::new("root")
.attribute("attrib1", &self.attrib1)
.attribute("Attrib2", &self.attrib2)
.namespace("Namespace1").expect("Failed to set doc namespace")
.nodes(&data1_nodes)
.node(
self.data2
.to_xml()
.namespace("Namespace1")
.expect("Failed to set node namespace"),
);
node
}
}
struct Node {
data1: String,
data2: Vec<Node>,
}
impl IntoXMLNode for Node {
fn to_xml(&self) -> XMLNode {
let node = XMLNode::new("Node")
.text(&self.data1)
.data(
self.data2
.iter()
.map(|d| d.into())
.collect::<Vec<XMLData>>()
.as_slice(),
);
node
}
}
fn foo() {
let test_structure = Root {
data1: vec![Node {
data1: "First node, first datapoint".to_string(),
data2: vec![],
}],
data2: Node {
data1: String::from("String mixed with "),
data2: vec![Node {
data1: "Second node, sub-datapoint".to_string(),
data2: vec![],
}],
},
attrib1: "Attribute_value".to_string(),
attrib2: "Attribute_value_2",
};
assert_eq!(
r#"<n:root attrib1="Attribute_value" Attrib2="Attribute_value_2" xmlns:n="https://namespace1.com/namespace"><Node>First node, first datapoint</Node><n:Node>String mixed with <Node>Second node, sub-datapoint</Node></n:Node></n:root>"#,
test_structure.to_xml().to_string()
)
}