1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! CMSIS-SVD file parser
//!
//! # Usage
//!
//! ``` no_run
//! use svd_parser as svd;
//!
//! use std::fs::File;
//! use std::io::Read;
//!
//! let xml = &mut String::new();
//! File::open("STM32F30x.svd").unwrap().read_to_string(xml);
//!
//! println!("{:?}", svd::parse(xml));
//! ```
//!
//! # References
//!
//! - [SVD Schema file](https://www.keil.com/pack/doc/CMSIS/SVD/html/schema_1_2_gr.html)
//! - [SVD file database](https://github.com/posborne/cmsis-svd/tree/master/data)
//! - [Sample SVD file](https://www.keil.com/pack/doc/CMSIS/SVD/html/svd_Example_pg.html)

#![deny(warnings)]

use std::collections::HashMap;
use xmltree::Element;

// ElementExt extends XML elements with useful methods
pub mod elementext;
// SVD contains svd primitives
pub mod svd;
pub use svd::*;
// Error defines SVD error types
pub mod error;
use anyhow::Result;
// Parse defines parsing interfaces
pub mod parse;
use parse::Parse;
// Encode defines encoding interfaces
pub mod encode;
use encode::Encode;
// Types defines simple types and parse/encode implementations
pub mod types;

#[cfg(feature = "derive-from")]
pub mod derive_from;
#[cfg(feature = "derive-from")]
pub use derive_from::DeriveFrom;

/// Parses the contents of an SVD (XML) string
pub fn parse(xml: &str) -> Result<Device> {
    let xml = trim_utf8_bom(xml);
    let tree = Element::parse(xml.as_bytes())?;
    Device::parse(&tree)
}

/// Encodes a device object to an SVD (XML) string
pub fn encode(d: &Device) -> Result<String> {
    let root = d.encode()?;
    let mut wr = Vec::new();
    root.write(&mut wr).unwrap();
    Ok(String::from_utf8(wr).unwrap())
}

/// Return the &str trimmed UTF-8 BOM if the input &str contains the BOM.
fn trim_utf8_bom(s: &str) -> &str {
    if s.len() > 2 && s.as_bytes().starts_with(b"\xef\xbb\xbf") {
        &s[3..]
    } else {
        s
    }
}

/// Helper to create new base xml elements
pub(crate) fn new_element(name: &str, text: Option<String>) -> Element {
    Element {
        prefix: None,
        namespace: None,
        namespaces: None,
        name: String::from(name),
        attributes: HashMap::new(),
        children: Vec::new(),
        text,
    }
}

/// Generic test helper function
/// Takes an array of (item, xml) pairs where the item implements
/// Parse and Encode and tests object encoding and decoding
#[cfg(test)]
pub fn run_test<
    T: Parse<Error = anyhow::Error, Object = T>
        + Encode<Error = anyhow::Error>
        + core::fmt::Debug
        + PartialEq,
>(
    tests: &[(T, &str)],
) {
    for t in tests {
        let mut tree1 = Element::parse(t.1.as_bytes()).unwrap();
        let elem = T::parse(&tree1).unwrap();
        // Hack to make assert be order agnostic
        tree1.children.sort_by(|e1, e2| e1.name.cmp(&e2.name));
        tree1.children.iter_mut().for_each(|e| {
            e.children.sort_by(|e1, e2| e1.name.cmp(&e2.name));
        });
        assert_eq!(
            elem, t.0,
            "Error parsing xml` (mismatch between parsed and expected)"
        );
        let mut tree2 = elem.encode().unwrap();
        // Hack to make assert be order agnostic
        tree2.children.sort_by(|e1, e2| e1.name.cmp(&e2.name));
        tree2.children.iter_mut().for_each(|e| {
            e.children.sort_by(|e1, e2| e1.name.cmp(&e2.name));
        });
        assert_eq!(
            tree1, tree2,
            "Error encoding xml (mismatch between encoded and original)"
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::str;

    #[test]
    fn test_trim_utf8_bom_from_str() {
        // UTF-8 BOM + "xyz"
        let bom_str = str::from_utf8(b"\xef\xbb\xbfxyz").unwrap();
        assert_eq!("xyz", trim_utf8_bom(bom_str));
        assert_eq!("xyz", trim_utf8_bom("xyz"));
    }
}