非常好,**B1 是整个 BPMN 能否落地的第一块基石**。
我下面给你的是 **「工程级 BPMN XML Parser 设计 + Rust 示例」**,目标是:
> ✅ 能稳定解析真实 BPMN 文件
> ✅ 不和 runtime 耦合
> ✅ 为后续 Compiler / Gateway / Event 留扩展点
不是一次性写完 BPMN,而是 **“先跑起来、可持续演进”** 的方案。
---
# 🎯 一、Parser 的设计目标(先立边界)
### Parser 只负责 4 件事
1. 解析 XML
2. 抽取 BPMN 元素
3. 建立 ID 引用关系
4. 输出 **BpmnModel(AST)**
❌ **绝不做**
- 执行语义
- Token 逻辑
- 条件计算
- 并行判断
---
# 🧩 二、推荐技术选型(Rust)
### XML 解析库(推荐)
| `roxmltree` | 只读、零拷贝、非常适合 AST |
| `quick-xml` | 流式,适合大文件(Phase 2) |
👉 **B1 阶段我建议用 `roxmltree`**
简单、可维护、Debug 体验好。
```toml
[dependencies]
roxmltree = "0.18"
```
---
# 📐 三、BpmnModel(AST)设计(核心)
### 顶层模型
```rust
pub struct BpmnModel {
pub process_id: String,
pub nodes: HashMap<String, BpmnNode>,
pub flows: Vec<BpmnSequenceFlow>,
}
```
---
## BpmnNode(统一表示)
```rust
pub enum BpmnNode {
StartEvent {
id: String,
outgoing: Vec<String>,
},
EndEvent {
id: String,
incoming: Vec<String>,
},
ServiceTask {
id: String,
name: Option<String>,
task_type: String,
incoming: Vec<String>,
outgoing: Vec<String>,
},
UserTask {
id: String,
name: Option<String>,
incoming: Vec<String>,
outgoing: Vec<String>,
},
ExclusiveGateway {
id: String,
incoming: Vec<String>,
outgoing: Vec<String>,
default_flow: Option<String>,
},
ParallelGateway {
id: String,
incoming: Vec<String>,
outgoing: Vec<String>,
},
}
```
👉 **注意**
- outgoing / incoming 存的是 **sequenceFlow ID**
- 这样后续可以单独解析 flow
---
## SequenceFlow
```rust
pub struct BpmnSequenceFlow {
pub id: String,
pub source_ref: String,
pub target_ref: String,
pub condition: Option<String>,
}
```
condition 暂时保留字符串(Compiler 再处理)
---
# 🔍 四、XML 结构识别(你只需关心这些)
BPMN XML 本质长这样:
```xml
<definitions>
<process id="order-process">
<startEvent id="start"/>
<serviceTask id="pay" name="Pay">
<extensionElements>
<camunda:type>payment</camunda:type>
</extensionElements>
</serviceTask>
<sequenceFlow id="flow1" sourceRef="start" targetRef="pay"/>
</process>
</definitions>
```
---
# 🧠 五、Parser 的模块拆分(非常重要)
```text
bpmn/
├── parser.rs // parse entry
├── model.rs // AST
├── xml.rs // xml helpers
└── error.rs // parse errors
```
---
# 🛠 六、核心解析流程(逐步)
## 1️⃣ 入口函数
```rust
pub fn parse_bpmn(xml: &str) -> Result<BpmnModel, BpmnError> {
let doc = roxmltree::Document::parse(xml)?;
let process = find_process(&doc)?;
let nodes = parse_nodes(&process)?;
let flows = parse_sequence_flows(&process)?;
Ok(BpmnModel {
process_id: process.attribute("id").unwrap().to_string(),
nodes,
flows,
})
}
```
---
## 2️⃣ 找到 `<process>`
```rust
fn find_process(doc: &Document) -> Result<Node, BpmnError> {
doc.descendants()
.find(|n| n.has_tag_name("process"))
.ok_or(BpmnError::ProcessNotFound)
}
```
---
## 3️⃣ 解析节点(Start / Task / Gateway)
```rust
fn parse_nodes(process: &Node) -> Result<HashMap<String, BpmnNode>, BpmnError> {
let mut nodes = HashMap::new();
for node in process.children().filter(|n| n.is_element()) {
match node.tag_name().name() {
"startEvent" => {
let id = node.attribute("id").unwrap().to_string();
nodes.insert(id.clone(), BpmnNode::StartEvent {
id,
outgoing: vec![],
});
}
"serviceTask" => {
let id = node.attribute("id").unwrap().to_string();
let task_type = parse_task_type(&node)?;
nodes.insert(id.clone(), BpmnNode::ServiceTask {
id,
name: node.attribute("name").map(|s| s.to_string()),
task_type,
incoming: vec![],
outgoing: vec![],
});
}
_ => {}
}
}
Ok(nodes)
}
```
---
## 4️⃣ 解析 Sequence Flow
```rust
fn parse_sequence_flows(process: &Node) -> Result<Vec<BpmnSequenceFlow>, BpmnError> {
let mut flows = vec![];
for flow in process.children().filter(|n| n.has_tag_name("sequenceFlow")) {
flows.push(BpmnSequenceFlow {
id: flow.attribute("id").unwrap().to_string(),
source_ref: flow.attribute("sourceRef").unwrap().to_string(),
target_ref: flow.attribute("targetRef").unwrap().to_string(),
condition: parse_condition(flow),
});
}
Ok(flows)
}
```
---
## 5️⃣ 建立 incoming / outgoing 引用
Parser 的最后一步(非常重要):
```rust
fn wire_flows(model: &mut BpmnModel) {
for flow in &model.flows {
model.nodes.get_mut(&flow.source_ref)
.map(|n| n.add_outgoing(&flow.id));
model.nodes.get_mut(&flow.target_ref)
.map(|n| n.add_incoming(&flow.id));
}
}
```
👉 建议用 trait:
```rust
trait FlowAttach {
fn add_incoming(&mut self, flow_id: &str);
fn add_outgoing(&mut self, flow_id: &str);
}
```
---
# ⚠️ 七、错误模型(别忽略)
```rust
pub enum BpmnError {
Xml(roxmltree::Error),
ProcessNotFound,
MissingAttribute(String),
UnsupportedElement(String),
}
```
Parser **应该严格**:
- ID 不存在 → error
- Flow source / target 不存在 → error
---
# 🧪 八、最小测试(必须有)
```rust
#[test]
fn parse_simple_bpmn() {
let xml = include_str!("simple.bpmn");
let model = parse_bpmn(xml).unwrap();
assert!(model.nodes.contains_key("start"));
assert!(model.nodes.contains_key("pay"));
assert_eq!(model.flows.len(), 1);
}
```
---
# 🏁 九、你现在做到什么程度就算 B1 完成?
✅ 能 parse 一个标准 BPMN XML
✅ 能构建 AST(nodes + flows)
✅ 不 panic,错误可读
✅ Compiler 可以无痛接入
---
# 🔜 下一步建议(顺序)
你现在 **100% 应该继续**:
- **B2** → Exclusive / Parallel Gateway 的 join & 并发细节
- **B4** → BPMN 编译期错误模型(比 Parser 错误更重要)
如果你愿意,我可以 **下一步直接帮你写 `compiler.rs` 的骨架**,把 BPMN AST → 你现有的 `ProcessDefinition` 串起来。
直接回我:
👉 **B2** 或 **B4**