1use crate::{
2 BusinessProcessModelAndNotation,
3 parser::{
4 parser::{can_eof, close_tag, empty_tag, is_in_namespace, open_tag},
5 parser_state::ParserState,
6 },
7};
8use anyhow::{Context, Error, Result};
9use quick_xml::{
10 NsReader,
11 events::{BytesStart, Event},
12};
13use std::{io::BufRead, str::FromStr};
14
15impl BusinessProcessModelAndNotation {
16 pub fn import_from_reader(
18 reader: &mut dyn BufRead,
19 disallow_sequence_flow_weights: bool,
20 ) -> Result<Self>
21 where
22 Self: Sized,
23 {
24 let mut xml_reader = NsReader::from_reader(reader);
25 xml_reader.config_mut().trim_text(true);
26
27 let mut buf = vec![];
28 let mut state = ParserState::new();
29 loop {
30 buf.clear();
31 let (namespace, xml_event) = xml_reader
32 .read_resolved_event_into(&mut buf)
33 .with_context(|| "cannot read XML event")?;
34 let in_namespace = is_in_namespace(namespace);
35 match (in_namespace, xml_event) {
36 (Some(n), Event::Start(e)) => {
38 open_tag(&mut state, &e, n).with_context(|| {
39 format!(
40 "start tag `{}` at position {}",
41 String::from_utf8_lossy(e.local_name().as_ref()),
42 xml_reader.buffer_position()
43 )
44 })?;
45 }
46
47 (Some(n), Event::End(e)) => close_tag(&mut state, &e, n).with_context(|| {
49 format!(
50 "close tag `{}` at position {}",
51 String::from_utf8_lossy(e.local_name().as_ref()),
52 xml_reader.buffer_position()
53 )
54 })?,
55
56 (Some(n), Event::Empty(e)) => empty_tag(&mut state, &e, n).with_context(|| {
58 format!(
59 "empty tag `{}` at position {}",
60 String::from_utf8_lossy(e.local_name().as_ref()),
61 xml_reader.buffer_position()
62 )
63 })?,
64
65 (_, Event::Eof) => {
67 can_eof(&state).with_context(|| "unexpected end of file")?;
68 return Ok(state.to_model(disallow_sequence_flow_weights)?);
69 }
70
71 _ => (),
72 }
73 }
74 }
75}
76
77impl FromStr for BusinessProcessModelAndNotation {
78 type Err = Error;
79
80 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
81 let mut reader = std::io::Cursor::new(s);
82 Self::import_from_reader(&mut reader, false)
83 }
84}
85
86pub(crate) fn parse_attribute(e: &BytesStart, attribute_name: &str) -> Option<String> {
87 if let Ok(Some(attribute)) = e.try_get_attribute(attribute_name) {
88 Some(
89 attribute
90 .decode_and_unescape_value(e.decoder())
91 .ok()?
92 .as_ref()
93 .to_owned(),
94 )
95 } else {
96 None
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use crate::{
103 BusinessProcessModelAndNotation,
104 stochastic_business_process_model_and_notation::StochasticBusinessProcessModelAndNotation,
105 traits::processable::Processable,
106 };
107 use std::fs::{self};
108
109 #[test]
110 fn bpmn_import() {
111 let fin = fs::read_to_string("testfiles/model.bpmn").unwrap();
112 let bpmn = fin.parse::<BusinessProcessModelAndNotation>().unwrap();
113
114 assert_eq!(bpmn.sequence_flows_non_recursive().len(), 0);
115 assert_eq!(bpmn.elements().len(), 10);
116 }
117
118 #[test]
119 fn sbpmn_import() {
120 let fin = fs::read_to_string("testfiles/model.sbpmn").unwrap();
121 let _bpmn = fin
122 .parse::<StochasticBusinessProcessModelAndNotation>()
123 .unwrap();
124 }
125
126 #[test]
127 #[should_panic]
128 fn sbpmn_import_fail() {
129 let fin = fs::read_to_string("testfiles/model-lanes.bpmn").unwrap();
130 let bpmn = fin
131 .parse::<StochasticBusinessProcessModelAndNotation>()
132 .unwrap();
133
134 dbg!(bpmn);
135 }
136
137 #[test]
138 #[should_panic]
139 fn bpmn_pool_invalid() {
140 let fin = fs::read_to_string("testfiles/invalid-pool.bpmn").unwrap();
141 let _bpmn = fin.parse::<BusinessProcessModelAndNotation>().unwrap();
142 }
143
144 #[test]
145 #[should_panic]
146 fn bpmn_message_invalid() {
147 let fin = fs::read_to_string("testfiles/invalid-message.bpmn").unwrap();
148 fin.parse::<BusinessProcessModelAndNotation>().unwrap();
149 }
150
151 #[test]
152 fn bpmn_lanes_import() {
153 let fin = fs::read_to_string("testfiles/model-lanes.bpmn").unwrap();
154 let bpmn = fin.parse::<BusinessProcessModelAndNotation>().unwrap();
155
156 assert_eq!(bpmn.elements.len(), 2);
157 assert_eq!(bpmn.message_flows.len(), 1);
158 }
159}