Skip to main content

config/
xml.rs

1use crate::{
2    util::*, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider, ConfigurationSource, FileSource,
3    LoadError, LoadResult, Value,
4};
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::fmt::{self, Display, Formatter};
8use std::fs::File;
9use std::io::BufReader;
10use std::ops::Deref;
11use std::rc::Rc;
12use std::sync::{Arc, RwLock};
13use tokens::{ChangeToken, FileChangeToken, SharedChangeToken, SingleChangeToken, Subscription};
14use xml_rs::attribute::OwnedAttribute;
15use xml_rs::name::OwnedName;
16use xml_rs::reader::{EventReader, XmlEvent};
17
18trait LocalNameResolver {
19    fn local_name_or_error(&self, element: &OwnedName, line: usize) -> Result<String, String>;
20}
21
22impl LocalNameResolver for OwnedName {
23    fn local_name_or_error(&self, element: &OwnedName, line: usize) -> Result<String, String> {
24        if self.namespace.is_none() {
25            Ok(self.local_name.clone())
26        } else {
27            Err(format!(
28                "XML namespaces are not supported. ({}, Line: {})",
29                &element.local_name, line
30            ))
31        }
32    }
33}
34
35trait VecExtensions<TKey: PartialEq, TValue> {
36    fn get_or_add(&mut self, key: TKey) -> &mut TValue;
37}
38
39impl VecExtensions<String, Vec<Rc<RefCell<Element>>>> for Vec<(String, Vec<Rc<RefCell<Element>>>)> {
40    fn get_or_add(&mut self, key: String) -> &mut Vec<Rc<RefCell<Element>>> {
41        let index = self.iter_mut().position(|i| i.0 == key).unwrap_or(self.len());
42
43        if index == self.len() {
44            self.push((key, Vec::new()));
45        }
46
47        &mut self[index].1
48    }
49}
50
51struct Attribute(String, String);
52
53struct Element {
54    line: usize,
55    element_name: String,
56    name: Option<String>,
57    sibling_name: String,
58    children: Vec<(String, Vec<Rc<RefCell<Element>>>)>,
59    text: Option<String>,
60    attributes: Vec<Attribute>,
61}
62
63impl Element {
64    fn new(element_name: OwnedName, attributes: Vec<OwnedAttribute>, line: usize) -> Result<Self, String> {
65        let name = get_name(&element_name, &attributes, line)?;
66        let local_name = element_name.local_name_or_error(&element_name, line)?;
67        let sibling_name = name
68            .as_ref()
69            .map(|n| ConfigurationPath::combine(&[&local_name.to_uppercase(), &n.to_uppercase()]))
70            .unwrap_or(local_name.to_uppercase());
71
72        Ok(Self {
73            line,
74            element_name: local_name,
75            name,
76            sibling_name,
77            children: Default::default(),
78            text: None,
79            attributes: attributes
80                .into_iter()
81                .map(|a| Ok(Attribute(a.name.local_name_or_error(&element_name, line)?, a.value)))
82                .collect::<Result<Vec<Attribute>, String>>()?,
83        })
84    }
85}
86
87#[derive(Default)]
88struct Prefix {
89    text: String,
90    lengths: Vec<usize>,
91}
92
93impl Prefix {
94    fn push<S: AsRef<str>>(&mut self, value: S) {
95        if self.text.is_empty() {
96            self.text.push_str(value.as_ref());
97            self.lengths.push(value.as_ref().len());
98        } else {
99            self.text.push_str(ConfigurationPath::key_delimiter());
100            self.text.push_str(value.as_ref());
101            self.lengths
102                .push(value.as_ref().len() + ConfigurationPath::key_delimiter().len());
103        }
104    }
105
106    fn pop(&mut self) {
107        if let Some(length) = self.lengths.pop() {
108            let idx = self.text.len() - length;
109            for _ in 0..length {
110                let _ = self.text.remove(idx);
111            }
112        }
113    }
114}
115
116impl Display for Prefix {
117    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
118        f.write_str(&self.text)
119    }
120}
121
122fn get_name(element: &OwnedName, attributes: &Vec<OwnedAttribute>, line: usize) -> Result<Option<String>, String> {
123    for attribute in attributes {
124        let local_name = attribute.name.local_name_or_error(element, line)?;
125
126        match local_name.as_str() {
127            "name" | "Name" | "NAME" => {
128                return Ok(Some(attribute.value.clone()));
129            }
130            _ => {}
131        }
132    }
133
134    Ok(None)
135}
136
137fn process_element(
138    prefix: &mut Prefix,
139    element: &Element,
140    config: &mut HashMap<String, (String, String)>,
141) -> Result<(), String> {
142    process_attributes(prefix, element, config)?;
143    process_element_content(prefix, element, config)?;
144    process_children(prefix, element, config)
145}
146
147fn process_element_content(
148    prefix: &mut Prefix,
149    element: &Element,
150    config: &mut HashMap<String, (String, String)>,
151) -> Result<(), String> {
152    if let Some(ref value) = element.text {
153        add_to_config(prefix.to_string(), value.clone(), element, config)
154    } else {
155        Ok(())
156    }
157}
158
159fn process_element_child(
160    prefix: &mut Prefix,
161    child: &Element,
162    index: Option<usize>,
163    config: &mut HashMap<String, (String, String)>,
164) -> Result<(), String> {
165    prefix.push(&child.element_name);
166
167    if let Some(ref name) = child.name {
168        prefix.push(name);
169    }
170
171    if let Some(i) = index {
172        prefix.push(i.to_string());
173    }
174
175    process_element(prefix, child, config)?;
176
177    if index.is_some() {
178        prefix.pop();
179    }
180
181    if child.name.is_some() {
182        prefix.pop();
183    }
184
185    prefix.pop();
186    Ok(())
187}
188
189fn process_attributes(
190    prefix: &mut Prefix,
191    element: &Element,
192    config: &mut HashMap<String, (String, String)>,
193) -> Result<(), String> {
194    for attribute in &element.attributes {
195        prefix.push(&attribute.0);
196        add_to_config(prefix.to_string(), attribute.1.clone(), element, config)?;
197        prefix.pop();
198    }
199
200    Ok(())
201}
202
203fn process_children(
204    prefix: &mut Prefix,
205    element: &Element,
206    config: &mut HashMap<String, (String, String)>,
207) -> Result<(), String> {
208    for children in element.children.iter().map(|i| &i.1) {
209        if children.len() == 1 {
210            process_element_child(prefix, &children[0].deref().borrow(), None, config)?;
211        } else {
212            for (i, child) in children.iter().enumerate() {
213                process_element_child(prefix, &child.deref().borrow(), Some(i), config)?;
214            }
215        }
216    }
217
218    Ok(())
219}
220
221fn add_to_config(
222    key: String,
223    value: String,
224    element: &Element,
225    config: &mut HashMap<String, (String, String)>,
226) -> Result<(), String> {
227    if let Some((dup_key, _)) = config.insert(key.to_uppercase(), (key, value)) {
228        Err(format!(
229            "A duplicate key '{}' was found. ({}, Line: {})",
230            &dup_key, &element.element_name, element.line
231        ))
232    } else {
233        Ok(())
234    }
235}
236
237fn to_config(mut root: Option<Rc<RefCell<Element>>>) -> Result<HashMap<String, (String, String)>, String> {
238    if let Some(cell) = root.take() {
239        let element = &cell.deref().borrow();
240        let mut data = HashMap::new();
241        let mut prefix = Prefix::default();
242
243        if let Some(ref name) = element.name {
244            prefix.push(name);
245        }
246
247        process_element(&mut prefix, element, &mut data)?;
248        data.shrink_to_fit();
249        Ok(data)
250    } else {
251        Ok(HashMap::with_capacity(0))
252    }
253}
254
255fn visit(file: File) -> Result<HashMap<String, (String, String)>, String> {
256    let content = BufReader::new(file);
257    let events = EventReader::new(content);
258    let mut has_content = false;
259    let mut last_name = None;
260    let mut line = 0;
261    let mut root = None;
262    let mut current = Vec::<(OwnedName, Rc<RefCell<Element>>)>::new();
263
264    for event in events.into_iter() {
265        match event {
266            Ok(XmlEvent::StartElement { name, attributes, .. }) => {
267                line += 1;
268                has_content = false;
269                last_name = Some(name.clone());
270                let element = Element::new(name.clone(), attributes, line)?;
271                let key = element.sibling_name.clone();
272                let child = Rc::new(RefCell::new(element));
273
274                if let Some(parent) = current.last() {
275                    parent.1.borrow_mut().children.get_or_add(key).push(child.clone());
276                } else {
277                    root = Some(child.clone());
278                }
279
280                current.push((name, child));
281            }
282            Ok(XmlEvent::EndElement { name }) => {
283                if has_content {
284                    if let Some(ref last) = last_name {
285                        if last != &name {
286                            line += 1;
287                        }
288                    }
289                }
290
291                if let Some((current_name, _)) = current.pop() {
292                    last_name = Some(current_name);
293                }
294            }
295            Ok(XmlEvent::CData(text)) | Ok(XmlEvent::Characters(text)) => {
296                has_content = true;
297                if let Some(parent) = current.last() {
298                    parent.1.borrow_mut().text = Some(text);
299                }
300            }
301            _ => {}
302        };
303    }
304
305    to_config(root)
306}
307
308struct InnerProvider {
309    file: FileSource,
310    data: RwLock<HashMap<String, (String, String)>>,
311    token: RwLock<SharedChangeToken<SingleChangeToken>>,
312}
313
314impl InnerProvider {
315    fn new(file: FileSource) -> Self {
316        Self {
317            file,
318            data: RwLock::new(HashMap::with_capacity(0)),
319            token: Default::default(),
320        }
321    }
322
323    fn load(&self, reload: bool) -> LoadResult {
324        if !self.file.path.is_file() {
325            if self.file.optional || reload {
326                let mut data = self.data.write().unwrap();
327                if !data.is_empty() {
328                    *data = HashMap::with_capacity(0);
329                }
330
331                return Ok(());
332            } else {
333                return Err(LoadError::File {
334                    message: format!(
335                        "The configuration file '{}' was not found and is not optional.",
336                        self.file.path.display()
337                    ),
338                    path: self.file.path.clone(),
339                });
340            }
341        }
342
343        if let Ok(file) = File::open(&self.file.path) {
344            let data = visit(file).map_err(|e| LoadError::File {
345                message: e,
346                path: self.file.path.clone(),
347            })?;
348            *self.data.write().unwrap() = data;
349        } else {
350            *self.data.write().unwrap() = HashMap::with_capacity(0);
351        }
352
353        let previous = std::mem::take(&mut *self.token.write().unwrap());
354
355        previous.notify();
356        Ok(())
357    }
358
359    fn get(&self, key: &str) -> Option<Value> {
360        self.data
361            .read()
362            .unwrap()
363            .get(&key.to_uppercase())
364            .map(|t| t.1.clone().into())
365    }
366
367    fn reload_token(&self) -> Box<dyn ChangeToken> {
368        Box::new(self.token.read().unwrap().clone())
369    }
370
371    fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
372        let data = self.data.read().unwrap();
373        accumulate_child_keys(&data, earlier_keys, parent_path)
374    }
375}
376
377/// Represents a [`ConfigurationProvider`](crate::ConfigurationProvider) for `*.xml` files.
378pub struct XmlConfigurationProvider {
379    inner: Arc<InnerProvider>,
380    _subscription: Option<Box<dyn Subscription>>,
381}
382
383impl XmlConfigurationProvider {
384    /// Initializes a new `*.xml` file configuration provider.
385    ///
386    /// # Arguments
387    ///
388    /// * `file` - The `*.xml` [`FileSource`](crate::FileSource) information
389    pub fn new(file: FileSource) -> Self {
390        let path = file.path.clone();
391        let inner = Arc::new(InnerProvider::new(file));
392        let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
393            Some(Box::new(tokens::on_change(
394                move || FileChangeToken::new(path.clone()),
395                |state| {
396                    let provider = state.unwrap();
397                    std::thread::sleep(provider.file.reload_delay);
398                    provider.load(true).ok();
399                },
400                Some(inner.clone()),
401            )))
402        } else {
403            None
404        };
405
406        Self {
407            inner,
408            _subscription: subscription,
409        }
410    }
411}
412
413impl ConfigurationProvider for XmlConfigurationProvider {
414    fn get(&self, key: &str) -> Option<Value> {
415        self.inner.get(key)
416    }
417
418    fn reload_token(&self) -> Box<dyn ChangeToken> {
419        self.inner.reload_token()
420    }
421
422    fn load(&mut self) -> LoadResult {
423        self.inner.load(false)
424    }
425
426    fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
427        self.inner.child_keys(earlier_keys, parent_path)
428    }
429}
430
431/// Represents a [`ConfigurationSource`](crate::ConfigurationSource) for `*.xml` files.
432pub struct XmlConfigurationSource {
433    file: FileSource,
434}
435
436impl XmlConfigurationSource {
437    /// Initializes a new `*.xml` file configuration source.
438    ///
439    /// # Arguments
440    ///
441    /// * `file` - The `*.xml` [`FileSource`](crate::FileSource) information
442    pub fn new(file: FileSource) -> Self {
443        Self { file }
444    }
445}
446
447impl ConfigurationSource for XmlConfigurationSource {
448    fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
449        Box::new(XmlConfigurationProvider::new(self.file.clone()))
450    }
451}
452
453pub mod ext {
454
455    use super::*;
456
457    /// Defines extension methods for [`ConfigurationBuilder`](crate::ConfigurationBuilder).
458    pub trait XmlConfigurationExtensions {
459        /// Adds a `*.xml` file as a configuration source.
460        ///
461        /// # Arguments
462        ///
463        /// * `file` - The `*.xml` [`FileSource`](crate::FileSource) information
464        fn add_xml_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
465    }
466
467    impl XmlConfigurationExtensions for dyn ConfigurationBuilder + '_ {
468        fn add_xml_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
469            self.add(Box::new(XmlConfigurationSource::new(file.into())));
470            self
471        }
472    }
473
474    impl<T: ConfigurationBuilder> XmlConfigurationExtensions for T {
475        fn add_xml_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
476            self.add(Box::new(XmlConfigurationSource::new(file.into())));
477            self
478        }
479    }
480}