xes/
lib.rs

1//! Read and write eXtensible Event Stream (XES) format.
2
3pub(crate) mod ontology;
4
5pub use ontology::Attribute;
6pub use ontology::Event;
7pub use ontology::Extension;
8pub use ontology::Log;
9pub use ontology::Trace;
10use quick_xml::events::BytesEnd;
11use quick_xml::events::BytesStart;
12use quick_xml::events::Event as XmlEvent;
13use std::collections::HashMap;
14use std::path::Path;
15
16const ATTRIBUTE_TAGS: [&str; 7] = [
17    "list", "string", "datetime", "long", "double", "boolean", "id",
18];
19
20fn parse_attribute(attributee: &roxmltree::Node) -> (String, Attribute) {
21    let key = attributee.attribute("key").unwrap().to_owned();
22    match attributee.attribute("value") {
23        Some(value) => {
24            let value = value.to_owned();
25            let value = match attributee.tag_name().name() {
26                "string" => Attribute::String(value),
27                "datetime" => Attribute::DateTime(value),
28                "long" => Attribute::Long(value.parse().unwrap()),
29                "double" => Attribute::Double(value.parse().unwrap()),
30                "boolean" => Attribute::Boolean(value.parse().unwrap()),
31                "id" => Attribute::ID(value),
32                _ => panic!(),
33            };
34            (key, value)
35        }
36        None => {
37            let mut sub_attributes = HashMap::new();
38            for sub_attributee in attributee
39                .children()
40                .filter(|e| ATTRIBUTE_TAGS.contains(&e.tag_name().name()))
41            {
42                let (k, v) = parse_attribute(&sub_attributee);
43                sub_attributes.insert(k, v);
44            }
45            let value = Attribute::List(sub_attributes);
46            (key, value)
47        }
48    }
49}
50
51fn parse_event(evente: &roxmltree::Node) -> Event {
52    let mut attributes = HashMap::new();
53    for attributee in evente
54        .children()
55        .filter(|e| ATTRIBUTE_TAGS.contains(&e.tag_name().name()))
56    {
57        let (key, value) = parse_attribute(&attributee);
58        attributes.insert(key, value);
59    }
60    Event { attributes }
61}
62
63fn parse_trace(tracee: &roxmltree::Node) -> Trace {
64    let mut attributes = HashMap::new();
65    for attributee in tracee
66        .children()
67        .filter(|e| ATTRIBUTE_TAGS.contains(&e.tag_name().name()))
68    {
69        let (key, value) = parse_attribute(&attributee);
70        attributes.insert(key, value);
71    }
72    let mut events = Vec::new();
73    for evente in tracee.children().filter(|e| e.tag_name().name() == "event") {
74        events.push(parse_event(&evente));
75    }
76    Trace { attributes, events }
77}
78
79fn parse_log(loge: &roxmltree::Node) -> Log {
80    let version = loge.attribute("version").unwrap().parse().unwrap();
81    let features = loge
82        .attribute("features")
83        .unwrap()
84        .split(',')
85        .map(|s| s.trim())
86        .map(String::from)
87        .collect();
88    let mut log = Log::new(version, features);
89    for exte in loge
90        .children()
91        .filter(|e| e.tag_name().name() == "extension")
92    {
93        let name = exte.attribute("name").unwrap().to_owned();
94        let prefix = exte.attribute("prefix").unwrap().to_owned();
95        let uri = exte.attribute("uri").unwrap().to_owned();
96        log.extensions.push(Extension { name, prefix, uri });
97    }
98    for attributee in loge
99        .children()
100        .filter(|e| ATTRIBUTE_TAGS.contains(&e.tag_name().name()))
101    {
102        let (key, value) = parse_attribute(&attributee);
103        log.attributes.insert(key, value);
104    }
105    for tracee in loge.children().filter(|e| e.tag_name().name() == "trace") {
106        log.traces.push(parse_trace(&tracee));
107    }
108    for evente in loge.children().filter(|e| e.tag_name().name() == "event") {
109        log.events.push(parse_event(&evente));
110    }
111    log
112}
113
114/// Transform `XES`-file to Rust representation.
115pub fn read<P: AsRef<Path>>(path: P) -> Vec<Log> {
116    let text = std::fs::read_to_string(path).unwrap();
117    let document = roxmltree::Document::parse(&text).unwrap();
118    let mut logs = Vec::new();
119    for log in document
120        .root()
121        .children()
122        .filter(|e| e.tag_name().name() == "log")
123    {
124        logs.push(parse_log(&log));
125    }
126    logs
127}
128
129fn write_extension(extension: &Extension, events: &mut Vec<XmlEvent>) {
130    let mut exte = BytesStart::new("extension");
131    exte.push_attribute(("name", extension.name.as_str()));
132    exte.push_attribute(("prefix", extension.prefix.as_str()));
133    exte.push_attribute(("uri", extension.uri.as_str()));
134    events.push(XmlEvent::Start(exte));
135    events.push(XmlEvent::End(BytesEnd::new("extension")));
136}
137
138fn write_attribute(attribute: (&String, &Attribute), events: &mut Vec<XmlEvent>) {
139    let (k, v) = attribute;
140    let element_name = match v {
141        Attribute::List(_) => "list",
142        Attribute::String(_) => "string",
143        Attribute::Long(_) => "long",
144        Attribute::Double(_) => "double",
145        Attribute::DateTime(_) => "date",
146        Attribute::Boolean(_) => "boolean",
147        Attribute::ID(_) => "id",
148    };
149    let mut attribute = BytesStart::new(element_name);
150    attribute.push_attribute(("key", k.as_str()));
151    match v {
152        Attribute::List(list) => {
153            events.push(XmlEvent::Start(BytesStart::new("list")));
154            for sub_attribute in list {
155                write_attribute(sub_attribute, events);
156            }
157            events.push(XmlEvent::End(BytesEnd::new("list")));
158        }
159        Attribute::String(value) => {
160            attribute.push_attribute(("value", value.as_str()));
161        }
162        Attribute::Long(value) => {
163            attribute.push_attribute(("value", value.to_string().as_str()));
164        }
165        Attribute::Double(value) => {
166            attribute.push_attribute(("value", value.to_string().as_str()));
167        }
168        Attribute::DateTime(value) => {
169            attribute.push_attribute(("value", value.to_string().as_str()));
170        }
171        Attribute::Boolean(value) => {
172            attribute.push_attribute(("value", value.to_string().as_str()));
173        }
174        Attribute::ID(value) => {
175            attribute.push_attribute(("value", value.to_string().as_str()));
176        }
177    }
178    events.push(XmlEvent::Start(attribute));
179    events.push(XmlEvent::End(BytesEnd::new(element_name)));
180}
181
182fn write_event(event: &Event, events: &mut Vec<XmlEvent>) {
183    events.push(XmlEvent::Start(BytesStart::new("event")));
184    for attribute in &event.attributes {
185        write_attribute(attribute, events);
186    }
187    events.push(XmlEvent::End(BytesEnd::new("event")));
188}
189
190fn write_trace(trace: &Trace, events: &mut Vec<XmlEvent>) {
191    events.push(XmlEvent::Start(BytesStart::new("trace")));
192    for attribute in &trace.attributes {
193        write_attribute(attribute, events);
194    }
195    for event in &trace.events {
196        write_event(event, events);
197    }
198    events.push(XmlEvent::End(BytesEnd::new("trace")));
199}
200
201fn write_log(log: &Log, events: &mut Vec<XmlEvent>) {
202    let mut loge = BytesStart::new("log");
203    loge.push_attribute(("version", log.version.as_str()));
204    loge.push_attribute(("features", log.features.join(",").as_str()));
205    events.push(XmlEvent::Start(loge));
206    for extension in &log.extensions {
207        write_extension(extension, events);
208    }
209    for attribute in &log.attributes {
210        write_attribute(attribute, events);
211    }
212    for trace in &log.traces {
213        write_trace(trace, events);
214    }
215    for event in &log.events {
216        write_event(event, events);
217    }
218    events.push(XmlEvent::End(BytesEnd::new("log")));
219}
220
221/// Transform Rust representation to `XES`-file.
222pub fn write<P: AsRef<Path>>(log: &Log, path: P) {
223    use quick_xml::Writer;
224    use std::io::Cursor;
225    let mut events = Vec::new();
226    write_log(log, &mut events);
227    let mut writer = Writer::new(Cursor::new(Vec::new()));
228    for event in events {
229        writer.write_event(event).unwrap();
230    }
231    let contents = writer.into_inner().into_inner();
232    std::fs::write(path, contents).unwrap();
233}