1use crate::core::{
4 Attribute, Document, ErrorKind, NamespaceDeclaration, NodeId, QName, XmlError, XmlResult,
5};
6
7pub fn element(name: impl Into<String>) -> XmlResult<ElementBuilder> {
9 ElementBuilder::new(name)
10}
11
12pub fn text(value: impl Into<String>) -> XmlNode {
14 XmlNode::Text(value.into())
15}
16
17pub fn fragment() -> FragmentBuilder {
19 FragmentBuilder::new()
20}
21
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
24pub struct DocumentBuilder {
25 root: Option<ElementBuilder>,
26}
27
28impl DocumentBuilder {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn root(mut self, root: ElementBuilder) -> XmlResult<Self> {
34 if self.root.is_some() {
35 return Err(XmlError::new(
36 crate::core::ErrorKind::InvalidOperation,
37 "document builder already has a root element",
38 ));
39 }
40
41 self.root = Some(root);
42 Ok(self)
43 }
44
45 pub fn build(self) -> XmlResult<Document> {
46 let root = self.root.ok_or_else(|| {
47 XmlError::new(
48 crate::core::ErrorKind::InvalidOperation,
49 "document builder requires a root element",
50 )
51 })?;
52
53 let mut document = Document::new();
54 let root_id = document.add_root_element(root.name.clone())?;
55 apply_element(&mut document, root_id, root)?;
56 Ok(document)
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct ElementBuilder {
63 name: QName,
64 attributes: Vec<Attribute>,
65 namespaces: Vec<NamespaceDeclaration>,
66 children: FragmentBuilder,
67}
68
69impl ElementBuilder {
70 pub fn new(local_name: impl Into<String>) -> XmlResult<Self> {
71 Self::from_qname(QName::new(local_name)?)
72 }
73
74 pub fn qualified(
75 prefix: impl Into<String>,
76 local_name: impl Into<String>,
77 namespace_uri: impl Into<String>,
78 ) -> XmlResult<Self> {
79 Self::from_qname(QName::qualified(prefix, local_name, namespace_uri)?)
80 }
81
82 pub fn namespaced(
83 local_name: impl Into<String>,
84 namespace_uri: impl Into<String>,
85 ) -> XmlResult<Self> {
86 Self::from_qname(QName::namespaced(local_name, namespace_uri)?)
87 }
88
89 pub fn from_qname(name: QName) -> XmlResult<Self> {
90 Ok(Self {
91 name,
92 attributes: Vec::new(),
93 namespaces: Vec::new(),
94 children: FragmentBuilder::new(),
95 })
96 }
97
98 pub fn attr(mut self, name: impl Into<String>, value: impl ToString) -> XmlResult<Self> {
99 self.attributes
100 .push(Attribute::new(QName::new(name)?, value.to_string()));
101 Ok(self)
102 }
103
104 pub fn qualified_attr(
105 mut self,
106 prefix: impl Into<String>,
107 local_name: impl Into<String>,
108 namespace_uri: impl Into<String>,
109 value: impl ToString,
110 ) -> XmlResult<Self> {
111 self.attributes.push(Attribute::new(
112 QName::qualified(prefix, local_name, namespace_uri)?,
113 value.to_string(),
114 ));
115 Ok(self)
116 }
117
118 pub fn attr_if(self, name: impl Into<String>, value: Option<impl ToString>) -> XmlResult<Self> {
119 match value {
120 Some(value) => self.attr(name, value),
121 None => Ok(self),
122 }
123 }
124
125 pub fn default_namespace(mut self, uri: impl Into<String>) -> XmlResult<Self> {
126 self.namespaces.push(NamespaceDeclaration::default(uri)?);
127 Ok(self)
128 }
129
130 pub fn namespace(
131 mut self,
132 prefix: impl Into<String>,
133 uri: impl Into<String>,
134 ) -> XmlResult<Self> {
135 self.namespaces
136 .push(NamespaceDeclaration::prefixed(prefix, uri)?);
137 Ok(self)
138 }
139
140 pub fn child(mut self, child: impl IntoXmlFragment) -> XmlResult<Self> {
141 self.children = self.children.child(child)?;
142 Ok(self)
143 }
144
145 pub fn children<I, T>(mut self, children: I) -> XmlResult<Self>
146 where
147 I: IntoIterator<Item = T>,
148 T: IntoXmlFragment,
149 {
150 self.children = self.children.children(children)?;
151 Ok(self)
152 }
153
154 pub fn text(self, value: impl Into<String>) -> XmlResult<Self> {
155 self.child(XmlNode::Text(value.into()))
156 }
157
158 pub fn comment(self, value: impl Into<String>) -> XmlResult<Self> {
159 self.child(XmlNode::Comment(value.into()))
160 }
161
162 pub fn cdata(self, value: impl Into<String>) -> XmlResult<Self> {
163 self.child(XmlNode::CData(value.into()))
164 }
165
166 pub fn processing_instruction(
167 self,
168 target: impl Into<String>,
169 data: Option<impl Into<String>>,
170 ) -> XmlResult<Self> {
171 self.child(XmlNode::ProcessingInstruction {
172 target: target.into(),
173 data: data.map(Into::into),
174 })
175 }
176
177 pub fn name(&self) -> &QName {
178 &self.name
179 }
180
181 pub fn child_nodes(&self) -> &[XmlNode] {
182 self.children.nodes()
183 }
184}
185
186#[derive(Debug, Clone, Default, PartialEq, Eq)]
188pub struct FragmentBuilder {
189 nodes: Vec<XmlNode>,
190}
191
192impl FragmentBuilder {
193 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn child(mut self, child: impl IntoXmlFragment) -> XmlResult<Self> {
198 self.nodes.extend(child.into_xml_fragment()?.nodes);
199 Ok(self)
200 }
201
202 pub fn children<I, T>(mut self, children: I) -> XmlResult<Self>
203 where
204 I: IntoIterator<Item = T>,
205 T: IntoXmlFragment,
206 {
207 for child in children {
208 self = self.child(child)?;
209 }
210 Ok(self)
211 }
212
213 pub fn text(self, value: impl Into<String>) -> XmlResult<Self> {
214 self.child(XmlNode::Text(value.into()))
215 }
216
217 pub fn nodes(&self) -> &[XmlNode] {
218 &self.nodes
219 }
220
221 pub fn into_single_element(self) -> XmlResult<ElementBuilder> {
222 let mut nodes = self.nodes.into_iter();
223 let Some(first) = nodes.next() else {
224 return Err(XmlError::new(
225 ErrorKind::InvalidOperation,
226 "fragment requires one root element to build a document",
227 ));
228 };
229
230 if nodes.next().is_some() {
231 return Err(XmlError::new(
232 ErrorKind::InvalidOperation,
233 "fragment has multiple top-level nodes; document requires one root element",
234 ));
235 }
236
237 match first {
238 XmlNode::Element(element) => Ok(element),
239 _ => Err(XmlError::new(
240 ErrorKind::InvalidOperation,
241 "document root must be an element",
242 )),
243 }
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
249pub enum XmlNode {
250 Element(ElementBuilder),
251 Text(String),
252 Comment(String),
253 CData(String),
254 ProcessingInstruction {
255 target: String,
256 data: Option<String>,
257 },
258}
259
260pub trait IntoXmlFragment {
262 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder>;
263}
264
265impl IntoXmlFragment for FragmentBuilder {
266 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
267 Ok(self)
268 }
269}
270
271impl IntoXmlFragment for ElementBuilder {
272 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
273 Ok(FragmentBuilder {
274 nodes: vec![XmlNode::Element(self)],
275 })
276 }
277}
278
279impl IntoXmlFragment for XmlNode {
280 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
281 Ok(FragmentBuilder { nodes: vec![self] })
282 }
283}
284
285impl<T> IntoXmlFragment for Option<T>
286where
287 T: IntoXmlFragment,
288{
289 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
290 match self {
291 Some(value) => value.into_xml_fragment(),
292 None => Ok(FragmentBuilder::new()),
293 }
294 }
295}
296
297impl<T> IntoXmlFragment for XmlResult<T>
298where
299 T: IntoXmlFragment,
300{
301 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
302 self?.into_xml_fragment()
303 }
304}
305
306impl<T> IntoXmlFragment for Vec<T>
307where
308 T: IntoXmlFragment,
309{
310 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
311 FragmentBuilder::new().children(self)
312 }
313}
314
315impl IntoXmlFragment for String {
316 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
317 XmlNode::Text(self).into_xml_fragment()
318 }
319}
320
321impl IntoXmlFragment for &str {
322 fn into_xml_fragment(self) -> XmlResult<FragmentBuilder> {
323 XmlNode::Text(self.to_owned()).into_xml_fragment()
324 }
325}
326
327fn apply_element(
328 document: &mut Document,
329 element_id: NodeId,
330 element: ElementBuilder,
331) -> XmlResult<()> {
332 for namespace in element.namespaces {
333 document.add_namespace_declaration(element_id, namespace)?;
334 }
335
336 for attribute in element.attributes {
337 document.add_attribute(element_id, attribute)?;
338 }
339
340 apply_fragment(document, element_id, element.children)
341}
342
343fn apply_fragment(
344 document: &mut Document,
345 parent: NodeId,
346 fragment: FragmentBuilder,
347) -> XmlResult<()> {
348 for node in fragment.nodes {
349 match node {
350 XmlNode::Element(element) => {
351 let child_id = document.add_element(parent, element.name.clone())?;
352 apply_element(document, child_id, element)?;
353 }
354 XmlNode::Text(text) => {
355 document.add_text(parent, text)?;
356 }
357 XmlNode::Comment(comment) => {
358 document.add_comment(parent, comment)?;
359 }
360 XmlNode::CData(cdata) => {
361 document.add_cdata(parent, cdata)?;
362 }
363 XmlNode::ProcessingInstruction { target, data } => {
364 document.add_processing_instruction(parent, target, data)?;
365 }
366 }
367 }
368
369 Ok(())
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::writer::to_string_compact;
376
377 fn item(code: &str, name: &str) -> XmlResult<ElementBuilder> {
378 element("Item")?.attr("code", code)?.text(name)
379 }
380
381 fn component_simple(id: &str) -> XmlResult<ElementBuilder> {
382 element("ID")?.text(id)
383 }
384
385 fn component_with_children(children: impl IntoXmlFragment) -> XmlResult<ElementBuilder> {
386 element("Wrapper")?.child(children)
387 }
388
389 #[test]
390 fn document_builder_creates_xml_with_three_levels() -> XmlResult<()> {
391 let document = DocumentBuilder::new()
392 .root(
393 element("Root")?
394 .child(element("Child")?.child(element("Grandchild")?.text("v")?)?)?,
395 )?
396 .build()?;
397
398 assert_eq!(
399 to_string_compact(&document)?,
400 "<Root><Child><Grandchild>v</Grandchild></Child></Root>"
401 );
402 Ok::<(), XmlError>(())
403 }
404
405 #[test]
406 fn builder_serializes_result_with_writer() -> XmlResult<()> {
407 let document = DocumentBuilder::new()
408 .root(element("Root")?.text("value")?)?
409 .build()?;
410
411 assert_eq!(to_string_compact(&document)?, "<Root>value</Root>");
412 Ok::<(), XmlError>(())
413 }
414
415 #[test]
416 fn document_builder_supports_static_and_dynamic_attributes() -> XmlResult<()> {
417 let dynamic_value = Some(42);
418 let document = DocumentBuilder::new()
419 .root(
420 element("Root")?
421 .attr("static", "yes")?
422 .attr_if("dynamic", dynamic_value)?,
423 )?
424 .build()?;
425
426 assert_eq!(
427 to_string_compact(&document)?,
428 "<Root static=\"yes\" dynamic=\"42\"/>"
429 );
430 Ok::<(), XmlError>(())
431 }
432
433 #[test]
434 fn namespaces_can_be_declared_with_default_and_prefix() -> XmlResult<()> {
435 let document = DocumentBuilder::new()
436 .root(
437 ElementBuilder::qualified("doc", "Root", "urn:doc")?
438 .default_namespace("urn:default")?
439 .namespace("doc", "urn:doc")?
440 .qualified_attr("doc", "id", "urn:doc", "123")?,
441 )?
442 .build()?;
443
444 assert_eq!(
445 to_string_compact(&document)?,
446 "<doc:Root xmlns=\"urn:default\" xmlns:doc=\"urn:doc\" doc:id=\"123\"/>"
447 );
448 Ok::<(), XmlError>(())
449 }
450
451 #[test]
452 fn fragment_builder_collects_children_from_vec() -> XmlResult<()> {
453 let children = vec![item("a", "A")?, item("b", "B")?];
454 let document = DocumentBuilder::new()
455 .root(element("Items")?.child(children)?)?
456 .build()?;
457
458 assert_eq!(
459 to_string_compact(&document)?,
460 "<Items><Item code=\"a\">A</Item><Item code=\"b\">B</Item></Items>"
461 );
462 Ok::<(), XmlError>(())
463 }
464
465 #[test]
466 fn collections_can_be_added_from_iterators() -> XmlResult<()> {
467 let document = DocumentBuilder::new()
468 .root(
469 element("Items")?.children(
470 ["a", "b"]
471 .into_iter()
472 .map(|code| item(code, code.to_uppercase().as_str())),
473 )?,
474 )?
475 .build()?;
476
477 assert_eq!(
478 to_string_compact(&document)?,
479 "<Items><Item code=\"a\">A</Item><Item code=\"b\">B</Item></Items>"
480 );
481 Ok::<(), XmlError>(())
482 }
483
484 #[test]
485 fn option_represents_conditional_content() -> XmlResult<()> {
486 let include_child = true;
487 let optional = include_child.then(|| element("Optional").and_then(|node| node.text("yes")));
488 let document = DocumentBuilder::new()
489 .root(element("Root")?.child(optional.transpose()?)?)?
490 .build()?;
491
492 assert_eq!(
493 to_string_compact(&document)?,
494 "<Root><Optional>yes</Optional></Root>"
495 );
496 Ok::<(), XmlError>(())
497 }
498
499 #[test]
500 fn rust_functions_can_act_as_components() -> XmlResult<()> {
501 let document = DocumentBuilder::new()
502 .root(element("Root")?.child(component_simple("123")?)?)?
503 .build()?;
504
505 assert_eq!(to_string_compact(&document)?, "<Root><ID>123</ID></Root>");
506 Ok::<(), XmlError>(())
507 }
508
509 #[test]
510 fn children_can_be_passed_to_component_functions() -> XmlResult<()> {
511 let children = fragment()
512 .child(element("A")?.text("one")?)?
513 .child(element("B")?.text("two")?)?;
514 let document = DocumentBuilder::new()
515 .root(component_with_children(children)?)?
516 .build()?;
517
518 assert_eq!(
519 to_string_compact(&document)?,
520 "<Wrapper><A>one</A><B>two</B></Wrapper>"
521 );
522 Ok::<(), XmlError>(())
523 }
524
525 #[test]
526 fn invalid_names_return_xml_error() {
527 let error = element("1invalid").expect_err("invalid name must fail");
528
529 assert_eq!(error.kind(), &crate::core::ErrorKind::InvalidName);
530 }
531}