#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct CaseCentricMarker;
impl core::fmt::Display for CaseCentricMarker {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("case-centric")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct XesExtension {
name: String,
prefix: String,
uri: String,
}
impl XesExtension {
pub fn new(name: impl Into<String>, prefix: impl Into<String>, uri: impl Into<String>) -> Self {
XesExtension {
name: name.into(),
prefix: prefix.into(),
uri: uri.into(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn uri(&self) -> &str {
&self.uri
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct XesEvent {
attributes: Vec<(String, String)>,
}
impl XesEvent {
pub fn new() -> Self {
XesEvent::default()
}
pub fn with(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.push((key.into(), value.into()));
self
}
#[must_use]
pub fn attribute(&self, key: &str) -> Option<&str> {
self.attributes
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn concept_name(&self) -> Option<&str> {
self.attribute("concept:name")
}
#[must_use]
pub fn timestamp(&self) -> Option<&str> {
self.attribute("time:timestamp")
}
#[must_use]
pub fn resource(&self) -> Option<&str> {
self.attribute("org:resource")
}
#[must_use]
pub fn lifecycle_transition(&self) -> Option<XesLifecycleTransition> {
self.attribute("lifecycle:transition")
.and_then(XesLifecycleTransition::parse)
}
#[must_use]
pub fn lifecycle_transition_raw(&self) -> Option<&str> {
self.attribute("lifecycle:transition")
}
pub fn attributes(&self) -> &[(String, String)] {
&self.attributes
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct XesTraceAttributes {
attributes: Vec<(String, String)>,
}
impl XesTraceAttributes {
pub fn new() -> Self {
XesTraceAttributes::default()
}
pub fn with(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.push((key.into(), value.into()));
self
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&str> {
self.attributes
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn concept_name(&self) -> Option<&str> {
self.get("concept:name")
}
pub fn all(&self) -> &[(String, String)] {
&self.attributes
}
pub fn len(&self) -> usize {
self.attributes.len()
}
pub fn is_empty(&self) -> bool {
self.attributes.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct XesTrace {
name: String,
events: Vec<XesEvent>,
}
impl XesTrace {
pub fn new(name: impl Into<String>, events: impl IntoIterator<Item = XesEvent>) -> Self {
XesTrace {
name: name.into(),
events: events.into_iter().collect(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn events(&self) -> &[XesEvent] {
&self.events
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[doc(alias = "XES event log")]
#[doc(alias = "case-centric")]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct XesLog {
name: String,
extensions: Vec<XesExtension>,
traces: Vec<XesTrace>,
}
impl XesLog {
pub fn new(
name: impl Into<String>,
extensions: impl IntoIterator<Item = XesExtension>,
traces: impl IntoIterator<Item = XesTrace>,
) -> Self {
XesLog {
name: name.into(),
extensions: extensions.into_iter().collect(),
traces: traces.into_iter().collect(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn extensions(&self) -> &[XesExtension] {
&self.extensions
}
pub fn traces(&self) -> &[XesTrace] {
&self.traces
}
#[must_use = "check the shape-check result"]
pub fn validate(&self) -> Result<(), XesRefusal> {
if self.name.is_empty() {
return Err(XesRefusal::MissingLogName);
}
for x in &self.extensions {
if x.prefix().is_empty() {
return Err(XesRefusal::InvalidExtension);
}
}
if self.traces.is_empty() {
return Err(XesRefusal::NoTraces);
}
let declared_prefixes: Vec<&str> = self.extensions.iter().map(|x| x.prefix()).collect();
for t in &self.traces {
if t.name().is_empty() {
return Err(XesRefusal::MissingTraceName);
}
if t.is_empty() {
return Err(XesRefusal::EmptyTrace);
}
for e in t.events() {
if e.concept_name().is_none() {
return Err(XesRefusal::MissingConceptName);
}
for (key, _) in e.attributes() {
if let Some(prefix) = key.split(':').next() {
if !prefix.is_empty()
&& key.contains(':')
&& !declared_prefixes.contains(&prefix)
{
return Err(XesRefusal::UndeclaredExtensionPrefix);
}
}
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct XesToOcedProjectionShape {
projection_name: &'static str,
case_object_type: String,
activity_attribute_key: &'static str,
timestamp_attribute_key: &'static str,
}
impl XesToOcedProjectionShape {
pub fn standard() -> Self {
XesToOcedProjectionShape {
projection_name: "xes-to-oced:case-as-object",
case_object_type: "case".to_owned(),
activity_attribute_key: "concept:name",
timestamp_attribute_key: "time:timestamp",
}
}
pub fn with_case_type(case_object_type: impl Into<String>) -> Self {
XesToOcedProjectionShape {
projection_name: "xes-to-oced:case-as-object",
case_object_type: case_object_type.into(),
activity_attribute_key: "concept:name",
timestamp_attribute_key: "time:timestamp",
}
}
pub fn projection_name(&self) -> &'static str {
self.projection_name
}
pub fn case_object_type(&self) -> &str {
&self.case_object_type
}
pub fn activity_attribute_key(&self) -> &'static str {
self.activity_attribute_key
}
pub fn timestamp_attribute_key(&self) -> &'static str {
self.timestamp_attribute_key
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct XesDeclaredExtensionLaw;
impl XesDeclaredExtensionLaw {
pub const NAME: &'static str = "xes-declared-extension-prefix-law";
pub const REFUSAL_VARIANT: &'static str = "UndeclaredExtensionPrefix";
pub const fn governs(refusal: XesRefusal) -> bool {
matches!(refusal, XesRefusal::UndeclaredExtensionPrefix)
}
pub const fn description() -> &'static str {
"IEEE 1849-2016 §5.2: every namespaced attribute key (prefix:local) \
must reference an extension prefix declared in the log header."
}
}
impl core::fmt::Display for XesDeclaredExtensionLaw {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "law:{}", Self::NAME)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct XesExtensionPrefixWitness {
prefix: &'static str,
}
impl XesExtensionPrefixWitness {
pub const fn new(prefix: &'static str) -> Self {
XesExtensionPrefixWitness { prefix }
}
pub const fn prefix(self) -> &'static str {
self.prefix
}
pub fn is_standard(self) -> bool {
XesStandardPrefix::parse(self.prefix).is_some()
}
pub const fn standard_witnesses() -> [XesExtensionPrefixWitness; 4] {
[
XesExtensionPrefixWitness::new("concept"),
XesExtensionPrefixWitness::new("time"),
XesExtensionPrefixWitness::new("lifecycle"),
XesExtensionPrefixWitness::new("org"),
]
}
}
impl core::fmt::Display for XesExtensionPrefixWitness {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "xes-prefix:{}", self.prefix)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum XesLifecycleTransition {
Schedule,
Assign,
Start,
Suspend,
Resume,
InProgress,
Abort,
Withdraw,
Complete,
Unknown,
AutoSkip,
ManualSkip,
Reassign,
Plan,
}
impl XesLifecycleTransition {
pub const fn as_str(self) -> &'static str {
match self {
XesLifecycleTransition::Schedule => "schedule",
XesLifecycleTransition::Assign => "assign",
XesLifecycleTransition::Start => "start",
XesLifecycleTransition::Suspend => "suspend",
XesLifecycleTransition::Resume => "resume",
XesLifecycleTransition::InProgress => "inprogress",
XesLifecycleTransition::Abort => "abort",
XesLifecycleTransition::Withdraw => "withdraw",
XesLifecycleTransition::Complete => "complete",
XesLifecycleTransition::Unknown => "unknown",
XesLifecycleTransition::AutoSkip => "autoskip",
XesLifecycleTransition::ManualSkip => "manualskip",
XesLifecycleTransition::Reassign => "reassign",
XesLifecycleTransition::Plan => "plan",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"schedule" => Some(XesLifecycleTransition::Schedule),
"assign" => Some(XesLifecycleTransition::Assign),
"start" => Some(XesLifecycleTransition::Start),
"suspend" => Some(XesLifecycleTransition::Suspend),
"resume" => Some(XesLifecycleTransition::Resume),
"inprogress" => Some(XesLifecycleTransition::InProgress),
"abort" => Some(XesLifecycleTransition::Abort),
"withdraw" => Some(XesLifecycleTransition::Withdraw),
"complete" => Some(XesLifecycleTransition::Complete),
"unknown" => Some(XesLifecycleTransition::Unknown),
"autoskip" => Some(XesLifecycleTransition::AutoSkip),
"manualskip" => Some(XesLifecycleTransition::ManualSkip),
"reassign" => Some(XesLifecycleTransition::Reassign),
"plan" => Some(XesLifecycleTransition::Plan),
_ => None,
}
}
pub const fn is_terminal(self) -> bool {
matches!(
self,
XesLifecycleTransition::Complete
| XesLifecycleTransition::Abort
| XesLifecycleTransition::Withdraw
| XesLifecycleTransition::ManualSkip
| XesLifecycleTransition::AutoSkip
)
}
}
impl core::fmt::Display for XesLifecycleTransition {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for XesLifecycleTransition {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<XesLifecycleTransition> for &'static str {
fn from(t: XesLifecycleTransition) -> &'static str {
t.as_str()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum XesStandardPrefix {
Concept,
Time,
Lifecycle,
Org,
}
impl XesStandardPrefix {
pub const fn as_str(self) -> &'static str {
match self {
XesStandardPrefix::Concept => "concept",
XesStandardPrefix::Time => "time",
XesStandardPrefix::Lifecycle => "lifecycle",
XesStandardPrefix::Org => "org",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
match s {
"concept" => Some(XesStandardPrefix::Concept),
"time" => Some(XesStandardPrefix::Time),
"lifecycle" => Some(XesStandardPrefix::Lifecycle),
"org" => Some(XesStandardPrefix::Org),
_ => None,
}
}
pub const fn all() -> [XesStandardPrefix; 4] {
[
XesStandardPrefix::Concept,
XesStandardPrefix::Time,
XesStandardPrefix::Lifecycle,
XesStandardPrefix::Org,
]
}
}
impl core::fmt::Display for XesStandardPrefix {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for XesStandardPrefix {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<XesStandardPrefix> for &'static str {
fn from(p: XesStandardPrefix) -> &'static str {
p.as_str()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum XesRefusal {
MissingLogName,
InvalidExtension,
NoTraces,
MissingTraceName,
EmptyTrace,
MissingConceptName,
InvalidTimestamp,
InvalidLifecycleTransition,
UndeclaredExtensionPrefix,
LiftingLoss,
}
impl core::fmt::Display for XesRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let law = match self {
XesRefusal::MissingLogName => "MissingLogName",
XesRefusal::InvalidExtension => "InvalidExtension",
XesRefusal::NoTraces => "NoTraces",
XesRefusal::MissingTraceName => "MissingTraceName",
XesRefusal::EmptyTrace => "EmptyTrace",
XesRefusal::MissingConceptName => "MissingConceptName",
XesRefusal::InvalidTimestamp => "InvalidTimestamp",
XesRefusal::InvalidLifecycleTransition => "InvalidLifecycleTransition",
XesRefusal::UndeclaredExtensionPrefix => "UndeclaredExtensionPrefix",
XesRefusal::LiftingLoss => "LiftingLoss",
};
write!(f, "XES refused by law: {law}")
}
}