1use serde::{Deserialize, Serialize};
2use std::fmt::{Display, Formatter, Write};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct FormatOptions {
7 indent: Option<String>,
8}
9
10impl FormatOptions {
11 #[must_use]
13 pub const fn compact() -> Self {
14 Self { indent: None }
15 }
16
17 #[must_use]
19 pub fn pretty() -> Self {
20 Self::pretty_with_indent(" ")
21 }
22
23 #[must_use]
25 pub fn pretty_with_indent(indent: impl Into<String>) -> Self {
26 Self {
27 indent: Some(indent.into()),
28 }
29 }
30}
31
32impl Default for FormatOptions {
33 fn default() -> Self {
34 Self::compact()
35 }
36}
37
38#[doc(hidden)]
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
40pub struct ElementData {
41 attributes: Vec<(String, String)>,
42 children: Vec<Node>,
43}
44
45impl ElementData {
46 pub fn set_attribute(&mut self, name: impl Into<String>, value: impl Display) {
47 let name = name.into();
48 let value = value.to_string();
49 if let Some((_, current)) = self.attributes.iter_mut().find(|(key, _)| key == &name) {
50 *current = value;
51 } else {
52 self.attributes.push((name, value));
53 }
54 }
55
56 pub fn push_child(&mut self, child: impl Into<Node>) {
57 self.children.push(child.into());
58 }
59}
60
61#[doc(hidden)]
62pub fn render_element(name: &str, data: &ElementData, f: &mut Formatter<'_>) -> std::fmt::Result {
63 write!(f, "<{name}")?;
64 for (attribute, value) in &data.attributes {
65 write!(f, " {attribute}=\"{}\"", escape_attribute(value))?;
66 }
67 if data.children.is_empty() {
68 return f.write_str("/>");
69 }
70 f.write_str(">")?;
71 for child in &data.children {
72 write!(f, "{child}")?;
73 }
74 write!(f, "</{name}>")
75}
76
77#[doc(hidden)]
78pub fn render_element_with_options(
79 output: &mut String,
80 name: &str,
81 data: &ElementData,
82 options: &FormatOptions,
83 depth: usize,
84) {
85 if let Some(indent) = &options.indent {
86 output.push_str(&indent.repeat(depth));
87 }
88 write!(output, "<{name}").expect("writing to String cannot fail");
89 for (attribute, value) in &data.attributes {
90 write!(output, " {attribute}=\"{}\"", escape_attribute(value))
91 .expect("writing to String cannot fail");
92 }
93 if data.children.is_empty() {
94 output.push_str("/>");
95 return;
96 }
97
98 output.push('>');
99 let pretty_children =
100 options.indent.is_some() && data.children.iter().all(Node::supports_pretty_indentation);
101 if pretty_children {
102 output.push('\n');
103 for (index, child) in data.children.iter().enumerate() {
104 child.render_with_options(output, options, depth + 1);
105 if index + 1 != data.children.len() {
106 output.push('\n');
107 }
108 }
109 output.push('\n');
110 output.push_str(&options.indent.as_deref().unwrap_or_default().repeat(depth));
111 } else {
112 let compact = FormatOptions::compact();
113 for child in &data.children {
114 child.render_with_options(output, &compact, 0);
115 }
116 }
117 write!(output, "</{name}>").expect("writing to String cannot fail");
118}
119
120fn escape_attribute(value: &str) -> String {
121 value
122 .replace('&', "&")
123 .replace('"', """)
124 .replace('<', "<")
125}
126
127fn escape_text(value: &str) -> String {
128 value.replace('&', "&").replace('<', "<")
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct Raw(String);
133
134impl Raw {
135 pub fn new(svg: impl Into<String>) -> Self {
136 Self(svg.into())
137 }
138}
139
140impl Display for Raw {
141 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
142 f.write_str(&self.0)
143 }
144}
145
146#[allow(clippy::nursery, clippy::pedantic)]
147pub mod generated {
148 include!(concat!(env!("OUT_DIR"), "/generated.rs"));
149}
150
151pub use generated::types;
152pub use generated::*;