Skip to main content

bpmn_engine/model/
xml.rs

1//! BPMN 2.0 XML Format
2//!
3//! XML representation of BPMN 2.0 process definitions.
4//!
5//! This module handles parsing and serialization of BPMN 2.0 XML format,
6//! which is the standard format defined by OMG.
7
8use crate::model::elements::*;
9use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
10use quick_xml::Reader;
11use quick_xml::Writer;
12use std::collections::HashMap;
13use std::io::Cursor;
14
15/// BPMN 2.0 XML Namespaces
16pub mod namespaces {
17    pub const BPMN2: &str = "http://www.omg.org/spec/BPMN/20100524/MODEL";
18    pub const BPMNDI: &str = "http://www.omg.org/spec/BPMN/20100524/DI";
19    pub const DC: &str = "http://www.omg.org/spec/DD/20100524/DC";
20    pub const DI: &str = "http://www.omg.org/spec/DD/20100524/DI";
21}
22
23/// Helper function to extract attributes from XML element
24fn extract_attributes(e: &BytesStart) -> HashMap<String, String> {
25    let mut attrs = HashMap::new();
26    for attr in e.attributes() {
27        if let Ok(attr) = attr {
28            let key = String::from_utf8_lossy(attr.key.as_ref()).to_string();
29            let value = String::from_utf8_lossy(&attr.value).to_string();
30            attrs.insert(key, value);
31        }
32    }
33    attrs
34}
35
36/// Helper function to check if element name matches (handles namespaces)
37fn matches_element_name(name: &[u8], patterns: &[&[u8]]) -> bool {
38    patterns.iter().any(|&pattern| name == pattern)
39}
40
41/// Parse BPMN XML to ProcessDefinition
42///
43/// # Arguments
44/// * `xml` - XML string containing BPMN 2.0 definition
45///
46/// # Returns
47/// * `Ok(ProcessDefinition)` - Parsed process definition
48/// * `Err(ParseError)` - Parse error
49pub fn parse_bpmn_xml(xml: &str) -> Result<ProcessDefinition, crate::model::format::ParseError> {
50    let mut reader = Reader::from_str(xml);
51    reader.trim_text(true);
52
53    let mut buf = Vec::new();
54    let mut process_id: Option<String> = None;
55    let mut process_name: Option<String> = None;
56    let mut is_executable: bool = true;
57    let mut elements: HashMap<String, ProcessElement> = HashMap::new();
58    let mut flows: HashMap<String, SequenceFlow> = HashMap::new();
59    let mut variables: HashMap<String, Variable> = HashMap::new();
60
61    loop {
62        match reader.read_event_into(&mut buf) {
63            Ok(Event::Start(e)) | Ok(Event::Empty(e)) => {
64                let name = e.name();
65                let attrs = extract_attributes(&e);
66                
67                // Process element
68                if matches_element_name(name.as_ref(), &[b"bpmn2:process", b"bpmn:process", b"process"]) {
69                    if let Some(id) = attrs.get("id") {
70                        process_id = Some(id.clone());
71                    }
72                    if let Some(name_attr) = attrs.get("name") {
73                        process_name = Some(name_attr.clone());
74                    }
75                    if let Some(exec) = attrs.get("isExecutable") {
76                        is_executable = exec == "true";
77                    }
78                }
79                // Start Event
80                else if matches_element_name(name.as_ref(), &[b"bpmn2:startEvent", b"bpmn:startEvent", b"startEvent"]) {
81                    if let Some(id) = attrs.get("id") {
82                        elements.insert(
83                            id.clone(),
84                            ProcessElement::StartEvent(StartEvent {
85                                base: ElementBase {
86                                    id: id.clone(),
87                                    name: attrs.get("name").cloned(),
88                                    documentation: None,
89                                },
90                                event_definition: None,
91                            }),
92                        );
93                    }
94                }
95                // End Event
96                else if matches_element_name(name.as_ref(), &[b"bpmn2:endEvent", b"bpmn:endEvent", b"endEvent"]) {
97                    if let Some(id) = attrs.get("id") {
98                        elements.insert(
99                            id.clone(),
100                            ProcessElement::EndEvent(EndEvent {
101                                base: ElementBase {
102                                    id: id.clone(),
103                                    name: attrs.get("name").cloned(),
104                                    documentation: None,
105                                },
106                                event_definition: None,
107                            }),
108                        );
109                    }
110                }
111                // Service Task
112                else if matches_element_name(name.as_ref(), &[b"bpmn2:serviceTask", b"bpmn:serviceTask", b"serviceTask"]) {
113                    if let Some(id) = attrs.get("id") {
114                        elements.insert(
115                            id.clone(),
116                            ProcessElement::ServiceTask(ServiceTask {
117                                base: ElementBase {
118                                    id: id.clone(),
119                                    name: attrs.get("name").cloned(),
120                                    documentation: None,
121                                },
122                                implementation: attrs.get("implementation").cloned(),
123                                operation_ref: attrs.get("operationRef").cloned(),
124                                io_mapping: Default::default(),
125                            }),
126                        );
127                    }
128                }
129                // User Task
130                else if matches_element_name(name.as_ref(), &[b"bpmn2:userTask", b"bpmn:userTask", b"userTask"]) {
131                    if let Some(id) = attrs.get("id") {
132                        elements.insert(
133                            id.clone(),
134                            ProcessElement::UserTask(UserTask {
135                                base: ElementBase {
136                                    id: id.clone(),
137                                    name: attrs.get("name").cloned(),
138                                    documentation: None,
139                                },
140                                assignment: None,
141                                form_key: attrs.get("formKey").cloned(),
142                            }),
143                        );
144                    }
145                }
146                // Script Task
147                else if matches_element_name(name.as_ref(), &[b"bpmn2:scriptTask", b"bpmn:scriptTask", b"scriptTask"]) {
148                    if let Some(id) = attrs.get("id") {
149                        elements.insert(
150                            id.clone(),
151                            ProcessElement::ScriptTask(ScriptTask {
152                                base: ElementBase {
153                                    id: id.clone(),
154                                    name: attrs.get("name").cloned(),
155                                    documentation: None,
156                                },
157                                script_format: attrs.get("scriptFormat").cloned(),
158                                script: None,
159                            }),
160                        );
161                    }
162                }
163                // Manual Task
164                else if matches_element_name(name.as_ref(), &[b"bpmn2:manualTask", b"bpmn:manualTask", b"manualTask"]) {
165                    if let Some(id) = attrs.get("id") {
166                        elements.insert(
167                            id.clone(),
168                            ProcessElement::ManualTask(ManualTask {
169                                base: ElementBase {
170                                    id: id.clone(),
171                                    name: attrs.get("name").cloned(),
172                                    documentation: None,
173                                },
174                            }),
175                        );
176                    }
177                }
178                // Exclusive Gateway
179                else if matches_element_name(name.as_ref(), &[b"bpmn2:exclusiveGateway", b"bpmn:exclusiveGateway", b"exclusiveGateway"]) {
180                    if let Some(id) = attrs.get("id") {
181                        elements.insert(
182                            id.clone(),
183                            ProcessElement::ExclusiveGateway(ExclusiveGateway {
184                                base: ElementBase {
185                                    id: id.clone(),
186                                    name: attrs.get("name").cloned(),
187                                    documentation: None,
188                                },
189                                default_flow: attrs.get("default").cloned(),
190                            }),
191                        );
192                    }
193                }
194                // Parallel Gateway
195                else if matches_element_name(name.as_ref(), &[b"bpmn2:parallelGateway", b"bpmn:parallelGateway", b"parallelGateway"]) {
196                    if let Some(id) = attrs.get("id") {
197                        elements.insert(
198                            id.clone(),
199                            ProcessElement::ParallelGateway(ParallelGateway {
200                                base: ElementBase {
201                                    id: id.clone(),
202                                    name: attrs.get("name").cloned(),
203                                    documentation: None,
204                                },
205                            }),
206                        );
207                    }
208                }
209                // Inclusive Gateway
210                else if matches_element_name(name.as_ref(), &[b"bpmn2:inclusiveGateway", b"bpmn:inclusiveGateway", b"inclusiveGateway"]) {
211                    if let Some(id) = attrs.get("id") {
212                        elements.insert(
213                            id.clone(),
214                            ProcessElement::InclusiveGateway(InclusiveGateway {
215                                base: ElementBase {
216                                    id: id.clone(),
217                                    name: attrs.get("name").cloned(),
218                                    documentation: None,
219                                },
220                                default_flow: attrs.get("default").cloned(),
221                            }),
222                        );
223                    }
224                }
225                // Sequence Flow
226                else if matches_element_name(name.as_ref(), &[b"bpmn2:sequenceFlow", b"bpmn:sequenceFlow", b"sequenceFlow"]) {
227                    if let (Some(id), Some(source), Some(target)) = (
228                        attrs.get("id"),
229                        attrs.get("sourceRef"),
230                        attrs.get("targetRef"),
231                    ) {
232                        flows.insert(
233                            id.clone(),
234                            SequenceFlow {
235                                id: id.clone(),
236                                name: attrs.get("name").cloned(),
237                                source_ref: source.clone(),
238                                target_ref: target.clone(),
239                                condition_expression: None,
240                            },
241                        );
242                    }
243                }
244            }
245            Ok(Event::Eof) => break,
246            Err(e) => {
247                return Err(crate::model::format::ParseError::Xml(format!(
248                    "XML parse error: {}",
249                    e
250                )));
251            }
252            _ => {}
253        }
254        buf.clear();
255    }
256
257    let process_id = process_id.ok_or_else(|| {
258        crate::model::format::ParseError::Xml("Process ID not found".to_string())
259    })?;
260
261    Ok(ProcessDefinition {
262        id: process_id,
263        name: process_name,
264        process_type: "process".to_string(),
265        is_executable,
266        elements,
267        flows,
268        variables,
269    })
270}
271
272/// Serialize ProcessDefinition to BPMN XML
273///
274/// # Arguments
275/// * `definition` - Process definition to serialize
276///
277/// # Returns
278/// * `Ok(String)` - XML string
279/// * `Err(SerializeError)` - Serialization error
280pub fn serialize_bpmn_xml(definition: &ProcessDefinition) -> Result<String, crate::model::format::SerializeError> {
281    let mut writer = Writer::new(Cursor::new(Vec::new()));
282    
283    // Write XML declaration
284    let decl = BytesDecl::new("1.0", Some("UTF-8"), None);
285    writer.write_event(Event::Decl(decl))
286        .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
287
288    // Write definitions element
289    let mut definitions = BytesStart::new("bpmn2:definitions");
290    definitions.push_attribute(("xmlns:bpmn2", namespaces::BPMN2));
291    definitions.push_attribute(("xmlns:bpmndi", namespaces::BPMNDI));
292    definitions.push_attribute(("xmlns:dc", namespaces::DC));
293    definitions.push_attribute(("xmlns:di", namespaces::DI));
294    definitions.push_attribute(("id", "Definitions"));
295    definitions.push_attribute(("targetNamespace", "http://bpmn.io/schema/bpmn"));
296    
297    writer.write_event(Event::Start(definitions))
298        .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
299
300    // Write process element
301    let mut process = BytesStart::new("bpmn2:process");
302    process.push_attribute(("id", definition.id.as_str()));
303    if let Some(name) = &definition.name {
304        process.push_attribute(("name", name.as_str()));
305    }
306    process.push_attribute(("isExecutable", if definition.is_executable { "true" } else { "false" }));
307    
308    writer.write_event(Event::Start(process))
309        .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
310
311    // Write elements
312    for element in definition.elements.values() {
313        match element {
314            ProcessElement::StartEvent(e) => {
315                let mut start = BytesStart::new("bpmn2:startEvent");
316                start.push_attribute(("id", e.base.id.as_str()));
317                if let Some(name) = &e.base.name {
318                    start.push_attribute(("name", name.as_str()));
319                }
320                writer.write_event(Event::Empty(start))
321                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
322            }
323            ProcessElement::EndEvent(e) => {
324                let mut end = BytesStart::new("bpmn2:endEvent");
325                end.push_attribute(("id", e.base.id.as_str()));
326                if let Some(name) = &e.base.name {
327                    end.push_attribute(("name", name.as_str()));
328                }
329                writer.write_event(Event::Empty(end))
330                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
331            }
332            ProcessElement::ServiceTask(e) => {
333                let mut task = BytesStart::new("bpmn2:serviceTask");
334                task.push_attribute(("id", e.base.id.as_str()));
335                if let Some(name) = &e.base.name {
336                    task.push_attribute(("name", name.as_str()));
337                }
338                writer.write_event(Event::Empty(task))
339                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
340            }
341            ProcessElement::UserTask(e) => {
342                let mut task = BytesStart::new("bpmn2:userTask");
343                task.push_attribute(("id", e.base.id.as_str()));
344                if let Some(name) = &e.base.name {
345                    task.push_attribute(("name", name.as_str()));
346                }
347                writer.write_event(Event::Empty(task))
348                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
349            }
350            ProcessElement::ExclusiveGateway(e) => {
351                let mut gateway = BytesStart::new("bpmn2:exclusiveGateway");
352                gateway.push_attribute(("id", e.base.id.as_str()));
353                if let Some(name) = &e.base.name {
354                    gateway.push_attribute(("name", name.as_str()));
355                }
356                writer.write_event(Event::Empty(gateway))
357                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
358            }
359            ProcessElement::ParallelGateway(e) => {
360                let mut gateway = BytesStart::new("bpmn2:parallelGateway");
361                gateway.push_attribute(("id", e.base.id.as_str()));
362                if let Some(name) = &e.base.name {
363                    gateway.push_attribute(("name", name.as_str()));
364                }
365                writer.write_event(Event::Empty(gateway))
366                    .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
367            }
368            _ => {
369                // Handle other element types as needed
370            }
371        }
372    }
373
374    // Write sequence flows
375    for flow in definition.flows.values() {
376        let mut seq_flow = BytesStart::new("bpmn2:sequenceFlow");
377        seq_flow.push_attribute(("id", flow.id.as_str()));
378        seq_flow.push_attribute(("sourceRef", flow.source_ref.as_str()));
379        seq_flow.push_attribute(("targetRef", flow.target_ref.as_str()));
380        writer.write_event(Event::Empty(seq_flow))
381            .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
382    }
383
384    // Close process
385    writer.write_event(Event::End(BytesEnd::new("bpmn2:process")))
386        .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
387
388    // Close definitions
389    writer.write_event(Event::End(BytesEnd::new("bpmn2:definitions")))
390        .map_err(|e| crate::model::format::SerializeError::Xml(format!("{}", e)))?;
391
392    let result = writer.into_inner().into_inner();
393    String::from_utf8(result).map_err(|e| {
394        crate::model::format::SerializeError::Xml(format!("UTF-8 error: {}", e))
395    })
396}