///|
fn XmlNode::shape(
self : XmlNode,
attrs : Array[String],
children : Array[String],
) -> Unit raise XmlError {
for key, _ in self.attrs {
if !attrs.contains(key) {
raise XmlError("unsupported attribute \{key} on \{self.name}")
}
}
for child in self.children {
if !children.contains(child.name) {
raise XmlError("unsupported element \{child.name} in \{self.name}")
}
}
if self.name != "text" && self.text.trim() != "" {
raise XmlError("unexpected text in \{self.name}")
}
}
///|
fn XmlNode::required(self : XmlNode, key : String) -> String raise XmlError {
match self.attrs.get(key) {
Some(s) if s.trim() != "" => s
_ => raise XmlError("missing \{key} on \{self.name}")
}
}
///|
fn XmlNode::value(
self : XmlNode,
name : String,
default : String,
) -> String raise XmlError {
let found = self.children.filter(fn(c) { c.name == name })
if found.length() == 0 {
return default
}
if found.length() != 1 {
raise XmlError("duplicate \{name}")
}
let wrapper = found[0]
wrapper.shape([], ["text"])
if wrapper.children.length() != 1 {
raise XmlError("expected one text in \{name}")
}
let text = wrapper.children[0]
text.shape([], [])
text.text
}
///|
fn natural(s : String) -> Int raise XmlError {
let text = s.trim().to_owned()
if text == "" {
raise XmlError("expected nonnegative decimal integer")
}
let mut n = 0
for i in 0.. 9 {
raise XmlError("expected nonnegative decimal integer")
}
if n > (2147483647 - c) / 10 {
raise XmlError("integer overflow")
}
n = n * 10 + c
}
n
}
///|
fn[T] pnml_result(r : Result[T, PetriError]) -> T raise XmlError {
match r {
Ok(x) => x
Err(e) => raise XmlError("model error: \{e.to_repr()}")
}
}
///|
fn load_pnml(input : String) -> PetriNet raise XmlError {
let root = xml_parse(input)
if root.name != "pnml" {
raise XmlError("expected pnml root")
}
root.shape([], ["net"])
if root.children.length() != 1 {
raise XmlError("expected exactly one net")
}
let model = root.children[0]
model.shape(["id", "type"], ["place", "transition", "arc"])
ignore(model.required("id"))
if model.attrs.get("type") is Some(t) {
if t != "http://www.pnml.org/version-2009/grammar/ptnet" {
raise XmlError("unsupported net type")
}
}
let net = PetriNet::new()
let ids : Map[String, Bool] = Map([])
let places : Map[String, Int] = Map([])
let transitions : Map[String, Int] = Map([])
for node in model.children {
let id = node.required("id")
if ids.contains(id) {
raise XmlError("duplicate id \{id}")
}
ids[id] = true
match node.name {
"place" => {
node.shape(["id"], ["name", "initialMarking"])
places[id] = pnml_result(
net.add_place(
node.value("name", id),
natural(node.value("initialMarking", "0")),
),
)
}
"transition" => {
node.shape(["id"], ["name"])
transitions[id] = pnml_result(
net.add_transition(node.value("name", id)),
)
net.pnml_transition_ids[id] = transitions[id]
}
"arc" => node.shape(["id", "source", "target"], ["inscription"])
_ => raise XmlError("unsupported node")
}
}
for node in model.children {
if node.name == "arc" {
let source = node.required("source")
let target = node.required("target")
let weight = natural(node.value("inscription", "1"))
match
(
places.get(source),
transitions.get(target),
transitions.get(source),
places.get(target),
) {
(Some(p), Some(t), _, _) => pnml_result(net.add_input(p, t, weight))
(_, _, Some(t), Some(p)) => pnml_result(net.add_output(t, p, weight))
_ => raise XmlError("arc must join a declared place and transition")
}
}
}
net
}
///|
/// Parse the documented flat, namespace-free PNML subset. First error wins.
pub fn parse_pnml(input : String) -> Result[PetriNet, Array[PetriError]] {
Ok(load_pnml(input)) catch {
XmlError(message) => Err([ParseError(message)])
}
}
///|
fn xml_escape(s : String) -> String {
s
.replace(old="&", new="&")
.replace(old="<", new="<")
.replace(old=">", new=">")
.replace(old="\"", new=""")
.replace(old="'", new="'")
}
///|
/// Emit canonical flat PNML with generated IDs and preserved model order.
pub fn serialize_pnml(net : PetriNet) -> String {
let out = [
"",
]
for p in 0..\{xml_escape(net.places[p])}\{net.initial[p]}",
)
}
for t in 0..\{xml_escape(net.transitions[t].name)}",
)
}
let mut id = 0
for t in 0..\{a.weight}",
)
id += 1
}
for a in net.transitions[t].outputs {
out.push(
"\{a.weight}",
)
id += 1
}
}
out.push("")
out.join("\n")
}