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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use std::path::Path;
use heck::AsSnakeCase;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
/// MAVLink dialect XML definition.
#[derive(Debug, Clone)]
pub struct XmlDialectDefinition {
name: String,
path: String,
includes: Vec<Self>,
version: Option<u8>, // TODO: change to `u32`
dialect: Option<u8>, // TODO: change to `u32`
}
impl PartialEq for XmlDialectDefinition {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl XmlDialectDefinition {
/// Loads XML dialect definition from provided path.
///
/// # Arguments
///
/// * `path` - path to XML definition file.
pub fn new(path: &str) -> Self {
Self::load_from_path(path)
}
/// Dialect name according to its file name.
pub fn name(&self) -> &str {
&self.name
}
/// Canonical name of the dialect, its unique identifier.
///
/// See: [`XmlDialectDefinition::canonize_name`]
pub fn canonical_name(&self) -> String {
Self::canonize_name(&self.name)
}
/// Path to dialect XML definition.
pub fn path(&self) -> &str {
&self.path
}
/// Version if specified.
pub fn version(&self) -> Option<u8> {
self.version
}
/// MAVLink dialect if specified.
pub fn dialect(&self) -> Option<u8> {
self.dialect
}
/// List of XML definition included to this dialect.
pub fn includes(&self) -> &[Self] {
&self.includes
}
/// Converts dialect `name` to its canonical name (`ID`).
///
/// Dialects with the same canonical name will be considered the same.
///
/// As a dialect `ID` we use its `snake_case` version of its XML definition file basename
/// (without extension). Which means that `MyDialect`, `my_dialect`, and `my-dialect` will be
/// considered the same and have a canonical name `my_dialect`. At the same time, `mydialect`
/// will be considered as a different dialect.
pub fn canonize_name(name: &str) -> String {
AsSnakeCase(name).to_string()
}
fn load_from_path(path: &str) -> Self {
let name = Path::new(path)
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string();
let base_path = Path::new(path).parent();
let mut reader = Reader::from_file(path).unwrap();
let mut buf = Vec::new();
let mut tag_stack: Vec<String> = Vec::new();
let mut includes_content: Vec<String> = Vec::new();
let mut version: Option<u8> = None;
let mut dialect: Option<u8> = None;
// Parse includes, version and dialect
loop {
match reader.read_event_into(&mut buf) {
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
// exits the loop when reaching end of file
Ok(Event::Eof) => break,
Ok(Event::Start(e)) => {
let tag_name = String::from_utf8_lossy(e.as_ref()).to_string();
tag_stack.push(tag_name.clone());
// Stop scanning dialect definition once content sections reached
match tag_name.as_ref() {
// Exit once enums reached
"enums" => break,
// Exit as messages reached
"messages" => break,
&_ => {}
}
}
Ok(Event::Text(t)) if !tag_stack.is_empty() => {
let data = String::from_utf8_lossy(t.as_ref()).to_string();
let last_open_tag = tag_stack.last().unwrap().as_ref();
match last_open_tag {
"include" => includes_content.push(data),
"version" => version = Some(data.parse::<u8>().unwrap()),
"dialect" => dialect = Some(data.parse::<u8>().unwrap()),
&_ => {}
}
}
Ok(Event::End(e)) => {
let tag_name = String::from_utf8_lossy(e.as_ref()).to_string();
let last_open_tag = tag_stack.last().unwrap().to_string();
if !tag_stack.is_empty() && last_open_tag == tag_name {
tag_stack.pop();
} else {
panic!("Invalid closing tag '{tag_name}' after '{last_open_tag}'!")
}
}
// There are several other `Event`s we do not consider here
_ => (),
}
}
// Load includes
let includes: Vec<Self> = includes_content
.iter()
.map(|filepath| -> Self {
let included_path = base_path
.unwrap()
.join(Path::new(filepath))
.to_str()
.unwrap()
.to_string();
Self::load_from_path(&included_path)
})
.collect();
Self {
name,
path: path.to_string(),
includes,
version,
dialect,
}
}
}