blockly_parser/
lib.rs

1extern crate sxd_document;
2
3use std::collections::HashMap;
4
5use sxd_document::{
6    parser,
7    Package,
8};
9use sxd_document::dom::{
10    Document,
11    ChildOfRoot,
12    Element,
13    ChildOfElement,
14};
15
16#[derive(Debug)]
17pub struct Program {
18    pub groups: Vec<StatementBody>
19}
20
21#[derive(PartialEq, Debug)]
22pub struct StatementBody {
23    pub blocks: Vec<Block>
24}
25
26#[derive(PartialEq, Debug)]
27pub struct Block {
28    pub block_type: String,
29    pub id: String,
30    pub fields: HashMap<String, FieldValue>,
31    pub statements: HashMap<String, StatementBody>,
32}
33
34#[derive(PartialEq, Debug)]
35pub enum FieldValue {
36    SimpleField(String),
37    ExpressionField(Block),
38}
39
40impl Program {
41    pub fn new() -> Self {
42        Self {
43            groups: Vec::new()
44        }
45    }
46}
47
48impl StatementBody {
49    fn new(first_block: Option<Element>) -> Self {
50        let mut blocks = Vec::new();
51        if let Some(el) = first_block {
52            // Create each block, put them into the statement body
53            let mut block_el: Element;
54            block_el = el;
55            loop {
56                blocks.push(Block::new(block_el));
57                if let Some(next_block) = get_next_block_element(&block_el) {
58                    block_el = next_block;
59                } else {
60                    break;
61                }
62            }
63        }
64        Self {
65            blocks
66        }
67    }
68}
69
70impl Block {
71    fn new(block_el: Element) -> Self {
72        let mut block = Self {
73            block_type: "".to_string(),
74            id: "".to_string(),
75            fields: HashMap::new(),
76            statements: HashMap::new()
77        };
78
79        for attribute in block_el.attributes().iter() {
80            let name = attribute.name().local_part();
81            let value = attribute.value().to_string();
82            match name {
83                "type" => { block.block_type = value; },
84                "id" => { block.id = value; },
85                _ => {}
86            }
87        }
88
89        for child in block_el.children().iter() {
90            if let &ChildOfElement::Element(child_el) = child {
91                let child_name = child_el.name().local_part();
92                match child_name {
93                    "statement" => {
94                        let statement_el = child_el;
95                        let statement_name = get_attribute(statement_el, "name").unwrap();
96                        let statement_body = StatementBody::new(get_first_child_element(statement_el));
97                        block.statements.insert(statement_name, statement_body);
98                    },
99                    "field" => {
100                        let field_el = child_el;
101                        let field_name = get_attribute(field_el, "name").unwrap();
102                        let field_value = FieldValue::new(field_el);
103                        block.fields.insert(field_name, field_value);
104                    },
105                    _ => {}
106                }
107            }
108        }
109
110        block
111    }
112}
113
114impl FieldValue {
115    fn new(field_el: Element) -> Self {
116        for child in field_el.children().iter() {
117            match child {
118                &ChildOfElement::Text(text_node) => {
119                    let value = text_node.text().to_string();
120                    return FieldValue::SimpleField(value);
121                },
122                _ => panic!("TODO: Implement expression fields")
123            }
124        }
125        panic!("Expected child nodes for field");
126    }
127}
128
129// Utilities for creating Blockly data structures
130
131pub fn program_from_xml(xml: &str) -> Program {
132    let mut program = Program::new();
133
134    let package: Package = parser::parse(xml).expect("Failed to parse XML!");
135    let document: Document = package.as_document();
136
137    let xml_element = get_xml_element(document).expect("Failed to find XML element!");
138
139    for child in xml_element.children().iter() {
140        if let &ChildOfElement::Element(el) = child {
141            let element_name = el.name().local_part();
142            match element_name {
143                "block" => {
144                    program.groups.push(StatementBody::new(Some(el)));
145                },
146                // TODO: handle `variables`
147                _ => {}
148            }
149        }
150    }
151
152    program
153}
154
155fn get_next_block_element<'b>(block_el: &Element<'b>) -> Option<Element<'b>> {
156    let next_el: Option<Element> = block_el.children()
157        .iter()
158        .filter_map(|child| {
159            if let &ChildOfElement::Element(el) = child {
160                if el.name().local_part() == "next" {
161                    return Some(el);
162                }
163            }
164            None
165        })
166        .next();
167
168    if let Some(next_el) = next_el {
169        let next_block_el: Option<Element> = next_el.children()
170            .iter()
171            .filter_map(|&child| {
172                if let ChildOfElement::Element(el) = child {
173                    if el.name().local_part() == "block" {
174                        return Some(el);
175                    }
176                }
177                None
178            })
179            .next();
180        return next_block_el;
181    }
182
183    None
184}
185
186// General DOM utilities
187
188fn get_xml_element(document: Document) -> Option<Element> {
189    document.root()
190        .children()
191        .iter()
192        .filter_map(|child| {
193            if let &ChildOfRoot::Element(el) = child {
194                if el.name().local_part() == "xml" {
195                    return Some(el);
196                }
197            }
198            None
199        })
200        .next()
201}
202
203fn get_first_child_element(element: Element) -> Option<Element> {
204    element.children()
205        .iter()
206        .filter_map(|child| {
207            if let &ChildOfElement::Element(el) = child {
208                return Some(el);
209            }
210            None
211        })
212        .next()
213}
214
215fn get_attribute(element: Element, attribute_name: &str) -> Option<String> {
216    element.attributes()
217        .iter()
218        .filter_map(|attribute| {
219            let name = attribute.name().local_part();
220            if name == attribute_name {
221                let value = attribute.value().to_string();
222                return Some(value);
223            }
224            None
225        })
226        .next()
227}
228
229
230#[cfg(test)]
231mod test {
232    use super::*;
233
234    fn get_fragment_root(package: &Package) -> Option<Element> {
235        package.as_document()
236            .root()
237            .children()
238            .iter()
239            .filter_map(|child| {
240                if let &ChildOfRoot::Element(el) = child {
241                    return Some(el);
242                }
243                None
244            })
245            .next()
246    }
247
248    #[test]
249    fn test_new_block() {
250        let xml: &str = r#"
251            <block type="inner_loop" id="]Lb|t?wfd#;s)[llJx8Y">
252                <field name="COUNT">3</field>
253                <statement name="BODY">
254                </statement>
255            </block>
256        "#;
257        let fragment: Package = parser::parse(xml).expect("Failed to parse XML!");
258        let root_element = get_fragment_root(&fragment).unwrap();
259
260        let block = Block::new(root_element);
261        assert_eq!(block.block_type, "inner_loop");
262        assert_eq!(block.id, "]Lb|t?wfd#;s)[llJx8Y");
263        let count_field = block.fields.get("COUNT");
264        assert!(count_field.is_some());
265        assert_eq!(count_field.unwrap(), &FieldValue::SimpleField("3".to_string()));
266    }
267
268    #[test]
269    fn test_get_next_block_element() {
270        let xml: &str = r#"
271            <block type="led_on" id="^3xb.m4E9i0;3$R10(=5">
272                <field name="TIME">300</field>
273                <next>
274                    <block type="led_off" id="HX4*sB9=gbJtq$Y{ke6b">
275                        <field name="TIME">100</field>
276                    </block>
277                </next>
278            </block>
279        "#;
280        let fragment: Package = parser::parse(xml).expect("Failed to parse XML!");
281        let root_element = get_fragment_root(&fragment).unwrap();
282
283        let next_block = get_next_block_element(&root_element);
284        assert!(next_block.is_some());
285        let next_block_unwrapped = next_block.unwrap();
286        assert_eq!(get_attribute(next_block_unwrapped, "type"), Some("led_off".to_string()));
287        assert_eq!(get_attribute(next_block_unwrapped, "id"), Some("HX4*sB9=gbJtq$Y{ke6b".to_string()));
288    }
289
290    #[test]
291    fn test_program_from_xml_advanced() {
292        let xml: &str = r#"
293            <xml xmlns="http://www.w3.org/1999/xhtml">
294                <variables></variables>
295                <block type="main_loop" id="[.)/fqUYv92(mzb{?:~u" deletable="false" movable="false" x="50" y="50">
296                    <statement name="BODY">
297                        <block type="inner_loop" id="]Lb|t?wfd#;s)[llJx8Y">
298                            <field name="COUNT">3</field>
299                            <statement name="BODY">
300                                <block type="led_on" id="^3xb.m4E9i0;3$R10(=5">
301                                    <field name="TIME">300</field>
302                                    <next>
303                                        <block type="led_off" id="HX4*sB9=gbJtq$Y{ke6b">
304                                            <field name="TIME">100</field>
305                                        </block>
306                                    </next>
307                                </block>
308                            </statement>
309                            <next>
310                                <block type="led_on" id="kB~f~7W`wkGa0i4z3mHw">
311                                    <field name="TIME">100</field>
312                                    <next>
313                                        <block type="led_off" id="$fdlZB)btzA8YtB/!xz`">
314                                            <field name="TIME">100</field>
315                                        </block>
316                                    </next>
317                                </block>
318                            </next>
319                        </block>
320                    </statement>
321                </block>
322            </xml>
323        "#;
324
325        let program: Program = program_from_xml(xml);
326        assert_eq!(program.groups.len(), 1);
327
328        let group = program.groups.get(0).unwrap();
329        assert_eq!(group.blocks.len(), 1);
330
331        let main_loop_block = group.blocks.get(0).unwrap();
332        assert_eq!(main_loop_block.block_type, "main_loop");
333        assert_eq!(main_loop_block.id, "[.)/fqUYv92(mzb{?:~u");
334
335        let main_loop_statements = &main_loop_block.statements;
336        assert_eq!(main_loop_statements.len(), 1);
337        assert!(main_loop_statements.contains_key("BODY"));
338
339        let main_loop_body = main_loop_statements.get("BODY");
340        let main_loop_body_statement = main_loop_body.as_ref().unwrap();
341        assert_eq!(main_loop_body_statement.blocks.len(), 3);
342
343        let inner_loop_block = main_loop_body_statement.blocks.get(0).unwrap();
344        assert_eq!(inner_loop_block.block_type, "inner_loop");
345        assert_eq!(inner_loop_block.id, "]Lb|t?wfd#;s)[llJx8Y");
346        assert_eq!(inner_loop_block.fields.get("COUNT"), Some(&FieldValue::SimpleField("3".to_string())));
347
348        let inner_loop_statement_maybe = inner_loop_block.statements.get("BODY");
349        assert!(inner_loop_statement_maybe.is_some());
350        let inner_loop_statement = inner_loop_statement_maybe.unwrap();
351        assert_eq!(inner_loop_statement.blocks.len(), 2);
352
353        let led_on_block = inner_loop_statement.blocks.get(0).unwrap();
354        assert_eq!(led_on_block.block_type, "led_on");
355        assert_eq!(led_on_block.id, "^3xb.m4E9i0;3$R10(=5");
356        assert_eq!(led_on_block.fields.get("TIME"), Some(&FieldValue::SimpleField("300".to_string())));
357
358        let led_off_block = inner_loop_statement.blocks.get(1).unwrap();
359        assert_eq!(led_off_block.block_type, "led_off");
360        assert_eq!(led_off_block.id, "HX4*sB9=gbJtq$Y{ke6b");
361    }
362}