1use super::{
2 ChildOfListElement, ComparableElement, ElementConverter, HasElementName, PomValue,
3 UpdatableElement, XMLEditorError,
4};
5use edit_xml::{Document, Element};
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9#[error("Missing Element {0}")]
10pub struct MissingElementError(pub &'static str);
11
12pub fn find_element_or_err(
13 element: Element,
14 name: &'static str,
15 document: &Document,
16) -> Result<Element, MissingElementError> {
17 element
18 .find(document, name)
19 .ok_or(MissingElementError(name))
20}
21pub fn find_to_string_or_none(
22 element: Element,
23 name: &'static str,
24 document: &Document,
25) -> Option<String> {
26 element
27 .find(document, name)
28 .map(|x| x.text_content(document))
29}
30pub fn create_basic_text_element(
31 document: &mut Document,
32 name: impl Into<String>,
33 value: impl PomValue,
34) -> Element {
35 Element::build(name)
36 .add_text(value.to_string_for_editor())
37 .finish(document)
38}
39
40pub fn get_or_create_top_level_element(
41 name: &'static str,
42 document: &mut Document,
43 parent: Element,
44) -> Element {
45 if let Some(element) = parent.find(document, name) {
46 return element;
47 }
48 let element = Element::new(document, name);
49 parent
50 .push_child(document, element)
51 .expect("Failed to add element");
52 element
53}
54pub(crate) fn find_or_create_then_set_text_content(
62 document: &mut Document,
63 parent: Element,
64 name: &'static str,
65 value: impl Into<String>,
66) {
67 let element = get_or_create_top_level_element(name, document, parent);
68 element.set_text_content(document, value);
69}
70pub(crate) fn sync_element<V: Into<String>>(
76 document: &mut Document,
77 parent: Element,
78 name: &'static str,
79 value: Option<V>,
80) {
81 if let Some(value) = value {
82 let element = get_or_create_top_level_element(name, document, parent);
83 element.clear_children(document);
84 element.set_text_content(document, value);
85 } else {
86 let element = parent.find(document, name);
87 if let Some(element) = element {
88 element.detach(document).expect("Failed to remove element");
89 }
90 }
91}
92
93pub(crate) fn add_or_update_item<I>(
99 document: &mut Document,
100 parent_element: Option<Element>,
101 insert_into: Element,
102 item: I,
103) -> Result<Option<I>, XMLEditorError>
104where
105 I: UpdatableElement
106 + ElementConverter
107 + ChildOfListElement
108 + HasElementName
109 + ComparableElement,
110{
111 let Some(parent_container) = parent_element else {
112 let dependencies = Element::new(document, I::parent_element_name());
114 let value = item.into_element(document)?;
115 dependencies.push_child(document, value)?;
116 insert_into.push_child(document, dependencies)?;
117 return Ok(None);
118 };
119 let elements_in_parent = get_all_children_of_element::<I>(document, parent_container)?;
120 for (current_value, element) in elements_in_parent {
121 if current_value.is_same_item(&item) {
124 item.update_element(element, document)?;
125 return Ok(Some(current_value));
126 }
127 }
128 let value = item.into_element(document)?;
130 parent_container.push_child(document, value)?;
131 Ok(None)
132}
133pub(crate) fn get_all_children_of_element<E>(
135 document: &Document,
136 element: Element,
137) -> Result<Vec<(E, Element)>, XMLEditorError>
138where
139 E: ElementConverter + HasElementName,
140{
141 let mut result = vec![];
142
143 for raw_element in element.child_elements(document) {
144 let element_name = raw_element.name(document);
145 if element_name != E::element_name() {
146 return Err(XMLEditorError::UnexpectedElementType {
147 expected: E::element_name(),
148 found: element_name.to_owned(),
149 });
150 }
151 let value = E::from_element(raw_element, document)?;
152 result.push((value, raw_element));
153 }
154 Ok(result)
155}
156
157macro_rules! add_if_present {
158 (
159 $document:ident,
160 $children:ident,
161 $element:ident,
162 $name:literal
163 ) => {
164 if let Some(value) = $element {
165 $children.push(crate::editor::utils::create_basic_text_element(
166 $document, $name, value,
167 ));
168 }
169 };
170}
171
172pub(crate) use add_if_present;
173macro_rules! from_element_using_builder {
174 (
175 $builder:ident,
176 $element:ident,
177 $document:ident,
178 $(
179 $name:literal => $set_func:ident
180 ),*
181 ) => {
182 fn from_element(
183 element: edit_xml::Element,
184 document: &edit_xml::Document,
185 ) -> Result<Self, crate::editor::XMLEditorError> {
186 let mut builder = $builder::default();
187 for child in element.child_elements(document) {
188 match child.name(document) {
189 $(
190 $name => {
191 builder.$set_func(child.text_content(document));
192 }
193 )*
194 _ => {}
195 }
196 }
197 let result = builder.build()?;
198 return Ok(result);
199 }
200 };
201}
202pub(crate) use from_element_using_builder;
203
204macro_rules! typed_from_element_using_builder {
205 (
206 $builder:ident,
207 $element:ident,
208 $document:ident,
209 $(
210 $name:literal($element_type:ty) => $set_func:ident
211 ),*
212 ) => {
213 fn from_element(
214 element: edit_xml::Element,
215 document: &edit_xml::Document,
216 ) -> Result<Self, crate::editor::XMLEditorError> {
217 let mut builder = $builder::default();
218 for child in element.child_elements(document) {
219 match child.name(document) {
220 $(
221 $name => {
222 builder.$set_func(<$element_type as crate::editor::PomValue>::from_element(child, document)?);
223 }
224 )*
225 _ => {}
226 }
227 }
228 let result = builder.build()?;
229 return Ok(result);
230 }
231 };
232}
233pub(crate) use typed_from_element_using_builder;
234
235#[cfg(test)]
236pub(crate) mod test_utils {
237 use crate::editor::{ElementConverter, HasElementName, XMLEditorError};
238 use pretty_assertions::assert_eq;
239 #[track_caller]
240 pub fn create_xml_to_element<E>(xml: &str) -> Result<E, XMLEditorError>
241 where
242 E: ElementConverter + HasElementName,
243 {
244 let actual_xml = format!(
245 r#"<?xml version="1.0" encoding="UTF-8"?>
246 {xml}
247 "#
248 );
249 let document = edit_xml::Document::parse_str(&actual_xml).unwrap();
250 let Some(raw_element) = document.root_element() else {
251 println!("{}", actual_xml);
252 panic!("No root element found");
253 };
254 let name = raw_element.name(&document);
255 assert_eq!(
256 name,
257 E::element_name(),
258 "Expected element name to be {}",
259 E::element_name()
260 );
261 E::from_element(raw_element, &document)
262 }
263}