///|
fn Converter::exact_bounds(self : Converter, e : @bpmn.Element) -> @model.ExactBounds? {
fn value(key : String) -> @model.Number? {
match e.number(key) { Present(n) => @model.Number::parse(n.token); _ => None }
}
guard value("x") is Some(x) && value("y") is Some(y) && value("width") is Some(width) && value("height") is Some(height) else { self.add("bpmn/invalid-geometry","DI bounds require finite numeric coordinates",e~); return None }
Some(@model.ExactBounds::new(x,y,width,height))
}
///|
fn Converter::label(self : Converter, di : Int) -> @model.LabelGeometry? {
let index=self.sources[0].index
let labels=index.nodes[di].children.filter(i => actual(index.nodes[i].element) == Some(BpmnBPMNLabel))
if labels.is_empty() { return None }
if labels.length() > 1 { self.add("bpmn/invalid-geometry","Multiple labels on one DI element",e=index.nodes[di].element); return None }
let ordinal=labels[0]; let e=index.nodes[ordinal].element
let bounds=e.children.iter().find_first(c => actual(c) == Some(DcBounds)).bind(c => self.exact_bounds(c))
let styles=self.resolve_field(0,ordinal,"labelStyle")
let mut font : @model.Font?=None
let style=styles.get(0).map(key => {
let element=self.sources[key.0].index.nodes[key.1].element
font=element.children.iter().find_first(c => actual(c) == Some(DcFont)).map(c => {
let size=match c.number("size") { Present(n) => @model.Number::parse(n.token); _ => None }
@model.Font::new(name?=c.attribute("name"),size?,bold?=flag(c,"isBold"),italic?=flag(c,"isItalic"),underline?=flag(c,"isUnderline"),strike_through?=flag(c,"isStrikeThrough"))
})
self.identity(key.0,key.1)
})
Some(@model.LabelGeometry::new(bounds?,style?,font?))
}
///|
fn Converter::shape(self : Converter, ordinal : Int) -> @model.ShapeGeometry {
let e=self.sources[0].index.nodes[ordinal].element
let bounds=e.children.iter().find_first(c => actual(c) == Some(DcBounds)).bind(c => self.exact_bounds(c))
@model.ShapeGeometry::new(self.identity(0,ordinal),bounds?,label?=self.label(ordinal),horizontal?=flag(e,"isHorizontal"),expanded?=flag(e,"isExpanded"),marker_visible?=flag(e,"isMarkerVisible"),message_visible?=flag(e,"isMessageVisible"))
}
///|
fn Converter::edge(self : Converter, ordinal : Int) -> @model.EdgeGeometry {
let e=self.sources[0].index.nodes[ordinal].element
let points=[]
for p in e.children {
if actual(p) != Some(DcPoint) { continue }
match (p.number("x"),p.number("y")) {
(Present(x),Present(y)) => match (@model.Number::parse(x.token),@model.Number::parse(y.token)) {
(Some(x),Some(y)) => points.push(@model.ExactPoint::new(x,y))
_ => self.add("bpmn/invalid-geometry","DI waypoints require finite coordinates",e=p)
}
_ => self.add("bpmn/invalid-geometry","DI waypoint is missing a coordinate",e=p)
}
}
let source_shape=self.resolve_field(0,ordinal,"sourceElement").get(0).map(key => self.identity(key.0,key.1))
let target_shape=self.resolve_field(0,ordinal,"targetElement").get(0).map(key => self.identity(key.0,key.1))
@model.EdgeGeometry::new(self.identity(0,ordinal),points,label?=self.label(ordinal),source_shape?,target_shape?,message_visible?=match e.attribute("messageVisibleKind") { Some("initiating") => Some(Initiating); Some("non_initiating") => Some(NonInitiating); _ => None })
}
///|
fn integer(token : String) -> Int? { try Some(@string.parse_int(token)) catch { _ => None } }
///|
fn integer_bounds(geometry : @model.ShapeGeometry?) -> @model.Bounds {
if geometry.bind(g => g.bounds) is Some(b) && integer(b.x.token) is Some(x) && integer(b.y.token) is Some(y) && integer(b.width.token) is Some(width) && integer(b.height.token) is Some(height) { {x,y,width,height} }
else { {x:0,y:0,width:0,height:0} }
}
///|
fn integer_points(geometry : @model.EdgeGeometry?) -> Array[@model.Point] {
let out : Array[@model.Point]=[]
if geometry is Some(g) { for p in g.points {
guard integer(p.x.token) is Some(x) && integer(p.y.token) is Some(y) else { return [] }
out.push({x,y})
} }
out
}