@logicflow/extension 2.1.4 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +23 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/es/bpmn-adapter/index.d.ts +84 -1
- package/es/bpmn-adapter/index.js +235 -12
- package/es/bpmn-adapter/json2xml.d.ts +10 -1
- package/es/bpmn-adapter/json2xml.js +28 -4
- package/es/bpmn-adapter/xml2json.js +2 -7
- package/es/components/mini-map/index.js +9 -7
- package/es/components/selection-select/index.d.ts +1 -0
- package/es/components/selection-select/index.js +11 -5
- package/es/tools/proximity-connect/index.d.ts +8 -0
- package/es/tools/proximity-connect/index.js +9 -4
- package/es/tools/snapshot/index.d.ts +8 -0
- package/es/tools/snapshot/index.js +2 -2
- package/lib/bpmn-adapter/index.d.ts +84 -1
- package/lib/bpmn-adapter/index.js +235 -12
- package/lib/bpmn-adapter/json2xml.d.ts +10 -1
- package/lib/bpmn-adapter/json2xml.js +29 -4
- package/lib/bpmn-adapter/xml2json.js +2 -7
- package/lib/components/mini-map/index.js +9 -7
- package/lib/components/selection-select/index.d.ts +1 -0
- package/lib/components/selection-select/index.js +11 -5
- package/lib/tools/proximity-connect/index.d.ts +8 -0
- package/lib/tools/proximity-connect/index.js +9 -4
- package/lib/tools/snapshot/index.d.ts +8 -0
- package/lib/tools/snapshot/index.js +2 -2
- package/package.json +5 -5
- package/src/bpmn-adapter/index.ts +256 -17
- package/src/bpmn-adapter/json2xml.ts +30 -4
- package/src/bpmn-adapter/xml2json.ts +2 -7
- package/src/components/mini-map/index.ts +11 -11
- package/src/components/selection-select/index.ts +11 -8
- package/src/tools/proximity-connect/index.ts +14 -4
- package/src/tools/snapshot/index.ts +10 -2
- package/stats.html +1 -1
|
@@ -18,6 +18,7 @@ var ProximityConnect = /** @class */ (function () {
|
|
|
18
18
|
function ProximityConnect(_a) {
|
|
19
19
|
var lf = _a.lf, options = _a.options;
|
|
20
20
|
this.enable = true;
|
|
21
|
+
this.type = 'default';
|
|
21
22
|
this.currentDistance = Infinity; // 当前间距
|
|
22
23
|
this.thresholdDistance = 100; // 节点-节点连接距离阈值
|
|
23
24
|
this.reverseDirection = false; // 节点-节点连线方向,默认是拖拽节点连向最近节点
|
|
@@ -37,6 +38,8 @@ var ProximityConnect = /** @class */ (function () {
|
|
|
37
38
|
// 节点开始拖拽事件
|
|
38
39
|
this.lf.graphModel.eventCenter.on('node:dragstart', function (_a) {
|
|
39
40
|
var data = _a.data;
|
|
41
|
+
if (_this.type === 'anchor')
|
|
42
|
+
return;
|
|
40
43
|
if (!_this.enable)
|
|
41
44
|
return;
|
|
42
45
|
var graphModel = _this.lf.graphModel;
|
|
@@ -45,12 +48,14 @@ var ProximityConnect = /** @class */ (function () {
|
|
|
45
48
|
});
|
|
46
49
|
// 节点拖拽事件
|
|
47
50
|
this.lf.graphModel.eventCenter.on('node:drag', function () {
|
|
51
|
+
if (_this.type === 'anchor')
|
|
52
|
+
return;
|
|
48
53
|
_this.handleNodeDrag();
|
|
49
54
|
});
|
|
50
55
|
// 锚点开始拖拽事件
|
|
51
56
|
this.lf.graphModel.eventCenter.on('anchor:dragstart', function (_a) {
|
|
52
57
|
var data = _a.data, nodeModel = _a.nodeModel;
|
|
53
|
-
if (!_this.enable)
|
|
58
|
+
if (!_this.enable || _this.type === 'node')
|
|
54
59
|
return;
|
|
55
60
|
_this.currentNode = nodeModel;
|
|
56
61
|
_this.currentAnchor = data;
|
|
@@ -58,20 +63,20 @@ var ProximityConnect = /** @class */ (function () {
|
|
|
58
63
|
// 锚点拖拽事件
|
|
59
64
|
this.lf.graphModel.eventCenter.on('anchor:drag', function (_a) {
|
|
60
65
|
var _b = _a.e, clientX = _b.clientX, clientY = _b.clientY;
|
|
61
|
-
if (!_this.enable)
|
|
66
|
+
if (!_this.enable || _this.type === 'node')
|
|
62
67
|
return;
|
|
63
68
|
_this.handleAnchorDrag(clientX, clientY);
|
|
64
69
|
});
|
|
65
70
|
// 节点、锚点拖拽结束事件
|
|
66
71
|
this.lf.graphModel.eventCenter.on('node:drop', function () {
|
|
67
|
-
if (!_this.enable)
|
|
72
|
+
if (!_this.enable || _this.type === 'anchor')
|
|
68
73
|
return;
|
|
69
74
|
_this.handleDrop();
|
|
70
75
|
});
|
|
71
76
|
// 锚点拖拽需要单独判断一下当前拖拽终点是否在某个锚点上,如果是,就不触发插件的连线,以免出现创建了两条连线的问题,表现见 issue 2140
|
|
72
77
|
this.lf.graphModel.eventCenter.on('anchor:dragend', function (_a) {
|
|
73
78
|
var e = _a.e, edgeModel = _a.edgeModel;
|
|
74
|
-
if (!_this.enable)
|
|
79
|
+
if (!_this.enable || _this.type === 'node')
|
|
75
80
|
return;
|
|
76
81
|
var _b = _this.lf.graphModel.getPointByClient({
|
|
77
82
|
x: e.clientX,
|
|
@@ -32,6 +32,14 @@ export type ToImageOptions = {
|
|
|
32
32
|
* - `true`:只导出画面区域内的可见元素
|
|
33
33
|
*/
|
|
34
34
|
partial?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 导出图片时的安全系数,用于确保导出的图片能够容纳所有元素,默认值为 1.1
|
|
37
|
+
*/
|
|
38
|
+
safetyFactor?: number;
|
|
39
|
+
/**
|
|
40
|
+
* 导出图片时的安全边距,用于确保导出的图片能够容纳所有元素,默认值为 40
|
|
41
|
+
*/
|
|
42
|
+
safetyMargin?: number;
|
|
35
43
|
};
|
|
36
44
|
export type SnapshotResponse = {
|
|
37
45
|
data: Blob | string;
|
|
@@ -407,7 +407,7 @@ var Snapshot = /** @class */ (function () {
|
|
|
407
407
|
graphModel = this.lf.graphModel;
|
|
408
408
|
transformModel = graphModel.transformModel;
|
|
409
409
|
SCALE_X = transformModel.SCALE_X, SCALE_Y = transformModel.SCALE_Y, TRANSLATE_X = transformModel.TRANSLATE_X, TRANSLATE_Y = transformModel.TRANSLATE_Y;
|
|
410
|
-
safetyFactor = 1.1 // 安全系数,增加
|
|
410
|
+
safetyFactor = toImageOptions.safetyFactor || 1.1 // 安全系数,增加10%的空间
|
|
411
411
|
;
|
|
412
412
|
actualWidth = (bbox.width / SCALE_X) * safetyFactor;
|
|
413
413
|
actualHeight = (bbox.height / SCALE_Y) * safetyFactor;
|
|
@@ -416,7 +416,7 @@ var Snapshot = /** @class */ (function () {
|
|
|
416
416
|
canvas = document.createElement('canvas');
|
|
417
417
|
canvas.style.width = "".concat(bboxWidth, "px");
|
|
418
418
|
canvas.style.height = "".concat(bboxHeight, "px");
|
|
419
|
-
safetyMargin = 40 // 额外的安全边距
|
|
419
|
+
safetyMargin = toImageOptions.safetyMargin || 40 // 额外的安全边距
|
|
420
420
|
;
|
|
421
421
|
_b = this.getCanvasDimensionsByBrowser(), maxCanvasDimension = _b.maxCanvasDimension, otherMaxCanvasDimension = _b.otherMaxCanvasDimension;
|
|
422
422
|
MAX_CANVAS_DIMENSION = maxCanvasDimension;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logicflow/extension",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "LogicFlow Extensions",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.js",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"author": "Logicflow-Team",
|
|
21
21
|
"license": "Apache-2.0",
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@logicflow/
|
|
24
|
-
"@logicflow/
|
|
23
|
+
"@logicflow/core": "2.1.4",
|
|
24
|
+
"@logicflow/vue-node-registry": "1.1.5"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@antv/hierarchy": "^0.6.11",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"preact": "^10.17.1",
|
|
33
33
|
"rangy": "^1.3.1",
|
|
34
34
|
"vanilla-picker": "^2.12.3",
|
|
35
|
-
"@logicflow/core": "2.1.
|
|
36
|
-
"@logicflow/vue-node-registry": "1.1.
|
|
35
|
+
"@logicflow/core": "2.1.4",
|
|
36
|
+
"@logicflow/vue-node-registry": "1.1.5"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"less": "^4.1.1",
|
|
@@ -2,6 +2,20 @@ import { getBpmnId } from './bpmnIds'
|
|
|
2
2
|
import { handleAttributes, lfJson2Xml } from './json2xml'
|
|
3
3
|
import { lfXml2Json } from './xml2json'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* 模块说明(BPMN Adapter)
|
|
7
|
+
*
|
|
8
|
+
* 该模块负责在 LogicFlow 内部图数据(GraphData)与 BPMN XML/JSON 之间进行双向转换:
|
|
9
|
+
* - adapterOut:将 LogicFlow 图数据转换为 BPMN JSON(随后由 json2xml 转为 XML)
|
|
10
|
+
* - adapterIn:将 BPMN JSON 转换为 LogicFlow 图数据(如果是 XML,则先经 xml2json 转为 JSON)
|
|
11
|
+
*
|
|
12
|
+
* 设计要点与特殊处理:
|
|
13
|
+
* - BPMN XML 的属性在 JSON 中以前缀 '-' 表示(如 '-id'、'-name'),本模块严格遵循该约定。
|
|
14
|
+
* - XML 中同名子节点可能出现多次,xml2json 解析后会以数组表示;本模块对数组与单对象场景均做兼容处理。
|
|
15
|
+
* - BPMN 画布坐标以元素左上角为基准,而 LogicFlow 以元素中心为基准;转换时需进行坐标基准转换。
|
|
16
|
+
* - 文本内容在导出时进行 XML 转义,在导入时进行反转义,确保特殊字符(如 <, >, & 等)能被正确保留。
|
|
17
|
+
*/
|
|
18
|
+
|
|
5
19
|
import {
|
|
6
20
|
ExclusiveGatewayConfig,
|
|
7
21
|
StartEventConfig,
|
|
@@ -10,6 +24,12 @@ import {
|
|
|
10
24
|
UserTaskConfig,
|
|
11
25
|
} from '../bpmn/constant'
|
|
12
26
|
|
|
27
|
+
/**
|
|
28
|
+
* LogicFlow 节点配置(导入/导出过程中使用的中间结构)
|
|
29
|
+
* - id/type/x/y:节点基本信息
|
|
30
|
+
* - text:节点文本的中心坐标与内容(值为未转义的原始字符串)
|
|
31
|
+
* - properties:节点的额外属性(会保留到 BPMN 的扩展字段)
|
|
32
|
+
*/
|
|
13
33
|
type NodeConfig = {
|
|
14
34
|
id: string
|
|
15
35
|
properties?: Record<string, unknown>
|
|
@@ -23,11 +43,21 @@ type NodeConfig = {
|
|
|
23
43
|
y: number
|
|
24
44
|
}
|
|
25
45
|
|
|
46
|
+
/**
|
|
47
|
+
* 点坐标结构(用于边的路径点)
|
|
48
|
+
*/
|
|
26
49
|
type Point = {
|
|
27
50
|
x: number
|
|
28
51
|
y: number
|
|
29
52
|
}
|
|
30
53
|
|
|
54
|
+
/**
|
|
55
|
+
* LogicFlow 边配置(导入/导出过程中使用的中间结构)
|
|
56
|
+
* - id/type/sourceNodeId/targetNodeId:边的基本信息
|
|
57
|
+
* - pointsList:边的路径点(用于 BPMN 的 di:waypoint)
|
|
58
|
+
* - text:边文本的位置与内容(值为未转义的原始字符串)
|
|
59
|
+
* - properties:边的扩展属性
|
|
60
|
+
*/
|
|
31
61
|
type EdgeConfig = {
|
|
32
62
|
id: string
|
|
33
63
|
sourceNodeId: string
|
|
@@ -50,6 +80,9 @@ type EdgeConfig = {
|
|
|
50
80
|
properties: Record<string, unknown>
|
|
51
81
|
}
|
|
52
82
|
|
|
83
|
+
/**
|
|
84
|
+
* BPMN 元素类型映射(用于在 JSON 中定位具体的 BPMN 节点类型)
|
|
85
|
+
*/
|
|
53
86
|
enum BpmnElements {
|
|
54
87
|
START = 'bpmn:startEvent',
|
|
55
88
|
END = 'bpmn:endEvent',
|
|
@@ -59,6 +92,11 @@ enum BpmnElements {
|
|
|
59
92
|
FLOW = 'bpmn:sequenceFlow',
|
|
60
93
|
}
|
|
61
94
|
|
|
95
|
+
/**
|
|
96
|
+
* BPMN 过程元素的标准属性键列表
|
|
97
|
+
* - 在解析 `processValue` 时,这些键会被视为标准属性而非扩展属性;
|
|
98
|
+
* - 其余未在列表中的键会进入 LogicFlow 的 `properties` 中,以保留扩展数据。
|
|
99
|
+
*/
|
|
62
100
|
const defaultAttrs = [
|
|
63
101
|
'-name',
|
|
64
102
|
'-id',
|
|
@@ -78,6 +116,10 @@ const defaultAttrs = [
|
|
|
78
116
|
* 这意味着出现在这个数组里的字段当它的值是数组或是对象时不会被视为一个节点而是一个属性
|
|
79
117
|
* @reference node type reference https://www.w3schools.com/xml/dom_nodetype.asp
|
|
80
118
|
*/
|
|
119
|
+
/**
|
|
120
|
+
* 导出至 BPMN JSON 时,作为属性保留的字段列表
|
|
121
|
+
* - 当这些字段的值为对象或数组时,仍视为属性(在 JSON 中以 '-' 前缀表示),而非子节点。
|
|
122
|
+
*/
|
|
81
123
|
const defaultRetainedFields = [
|
|
82
124
|
'properties',
|
|
83
125
|
'startPoint',
|
|
@@ -85,11 +127,39 @@ const defaultRetainedFields = [
|
|
|
85
127
|
'pointsList',
|
|
86
128
|
]
|
|
87
129
|
|
|
130
|
+
/**
|
|
131
|
+
* XML 实体反转义:
|
|
132
|
+
* - 将常见的 XML 实体还原为字符:`<`→`<`、`>`→`>`、`&`→`&`、`"`→`"`、`'`→`'`;保障流程图能正常回填
|
|
133
|
+
* - 使用 `String(text || '')` 规范化输入,避免 `null/undefined` 导致错误;
|
|
134
|
+
* - 注意:此实现为单次替换,若存在嵌套/二次编码(例如 `&lt;`),会先还原为 `<`,
|
|
135
|
+
* 如需完全解码,可在外层循环调用或调整替换顺序策略。
|
|
136
|
+
*/
|
|
137
|
+
const unescapeXml = (text: string) =>
|
|
138
|
+
String(text || '')
|
|
139
|
+
.replace(/</g, '<')
|
|
140
|
+
.replace(/>/g, '>')
|
|
141
|
+
.replace(/&/g, '&')
|
|
142
|
+
.replace(/"/g, '"')
|
|
143
|
+
.replace(/'/g, "'")
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 将普通 JSON 转换为 XML 风格 JSON(xmlJson)
|
|
147
|
+
* 输入:任意 JSON 对象;可选的保留属性字段 retainedFields
|
|
148
|
+
* 输出:遵循 XML 属性前缀约定的 xmlJson(属性键以 '-' 开头)
|
|
149
|
+
* 规则:
|
|
150
|
+
* - 原始字符串直接返回;数组逐项转换;对象根据键类型决定是否加 '-' 前缀。
|
|
151
|
+
* - 保留字段(fields)中出现的键以属性形式(带 '-')保留,否则视为子节点。
|
|
152
|
+
*/
|
|
88
153
|
function toXmlJson(retainedFields?: string[]) {
|
|
89
154
|
const fields = retainedFields
|
|
90
155
|
? defaultRetainedFields.concat(retainedFields)
|
|
91
156
|
: defaultRetainedFields
|
|
92
157
|
return (json: string | any[] | Record<string, any>) => {
|
|
158
|
+
/**
|
|
159
|
+
* 递归转换核心方法
|
|
160
|
+
* @param obj 输入对象/数组/字符串
|
|
161
|
+
* @returns 转换后的 xmlJson
|
|
162
|
+
*/
|
|
93
163
|
function ToXmlJson(obj: string | any[] | Record<string, any>) {
|
|
94
164
|
const xmlJson = {}
|
|
95
165
|
if (typeof obj === 'string') {
|
|
@@ -123,7 +193,9 @@ function toXmlJson(retainedFields?: string[]) {
|
|
|
123
193
|
}
|
|
124
194
|
|
|
125
195
|
/**
|
|
126
|
-
* 将xmlJson
|
|
196
|
+
* 将 XML 风格 JSON(xmlJson)转换回普通 JSON(内部使用)
|
|
197
|
+
* 输入:遵循 '-' 属性前缀约定的 xmlJson
|
|
198
|
+
* 输出:去除前缀并恢复原有结构的普通 JSON
|
|
127
199
|
*/
|
|
128
200
|
function toNormalJson(xmlJson) {
|
|
129
201
|
const json = {}
|
|
@@ -152,6 +224,18 @@ function toNormalJson(xmlJson) {
|
|
|
152
224
|
* 2)如果只有一个子元素,json中表示为正常属性
|
|
153
225
|
* 3)如果是多个子元素,json中使用数组存储
|
|
154
226
|
*/
|
|
227
|
+
/**
|
|
228
|
+
* 将 LogicFlow 图数据中的节点与边转换为 BPMN 的 process 数据结构
|
|
229
|
+
* 输入:
|
|
230
|
+
* - bpmnProcessData:输出目标对象(会被填充 '-id'、各 bpmn:* 节点以及 sequenceFlow)
|
|
231
|
+
* - data:LogicFlow 图数据(nodes/edges)
|
|
232
|
+
* - retainedFields:可选保留属性字段,用于控制属性与子节点的映射
|
|
233
|
+
* 输出:直接修改 bpmnProcessData
|
|
234
|
+
* 特殊处理:
|
|
235
|
+
* - 节点文本(node.text.value)作为 BPMN 的 '-name' 属性;
|
|
236
|
+
* - 维护 incoming/outgoing 的顺序,保证解析兼容性;
|
|
237
|
+
* - 多子元素时转为数组结构(XML 约定)。
|
|
238
|
+
*/
|
|
155
239
|
function convertLf2ProcessData(
|
|
156
240
|
bpmnProcessData,
|
|
157
241
|
data,
|
|
@@ -223,6 +307,16 @@ function convertLf2ProcessData(
|
|
|
223
307
|
/**
|
|
224
308
|
* adapterOut 设置bpmn diagram信息
|
|
225
309
|
*/
|
|
310
|
+
/**
|
|
311
|
+
* 将 LogicFlow 图数据转换为 BPMN 的图形数据(BPMNDiagram/BPMNPlane 下的 Shape 与 Edge)
|
|
312
|
+
* 输入:
|
|
313
|
+
* - bpmnDiagramData:输出目标对象(填充 BPMNShape/BPMNEdge)
|
|
314
|
+
* - data:LogicFlow 图数据(nodes/edges)
|
|
315
|
+
* 输出:直接修改 bpmnDiagramData
|
|
316
|
+
* 特殊处理:
|
|
317
|
+
* - 节点坐标从中心点转换为左上角基准;
|
|
318
|
+
* - 文本的显示边界(Bounds)根据文本长度近似计算,用于在 BPMN 渲染器正确定位标签。
|
|
319
|
+
*/
|
|
226
320
|
function convertLf2DiagramData(bpmnDiagramData, data) {
|
|
227
321
|
bpmnDiagramData['bpmndi:BPMNEdge'] = data.edges.map((edge) => {
|
|
228
322
|
const edgeId = edge.id
|
|
@@ -287,26 +381,36 @@ function convertLf2DiagramData(bpmnDiagramData, data) {
|
|
|
287
381
|
/**
|
|
288
382
|
* 将bpmn数据转换为LogicFlow内部能识别数据
|
|
289
383
|
*/
|
|
384
|
+
/**
|
|
385
|
+
* 将 BPMN JSON 转换为 LogicFlow 可识别的图数据
|
|
386
|
+
* 输入:
|
|
387
|
+
* - bpmnData:包含 'bpmn:definitions' 的 BPMN JSON
|
|
388
|
+
* 输出:{ nodes, edges }:LogicFlow 的 GraphConfigData
|
|
389
|
+
* 特殊处理:
|
|
390
|
+
* - 若缺失 process 或 plane,返回空数据以避免渲染错误。
|
|
391
|
+
*/
|
|
290
392
|
function convertBpmn2LfData(bpmnData) {
|
|
291
393
|
let nodes: NodeConfig[] = []
|
|
292
394
|
let edges: EdgeConfig[] = []
|
|
293
395
|
const definitions = bpmnData['bpmn:definitions']
|
|
294
396
|
if (definitions) {
|
|
397
|
+
// 如后续需多图/多流程支持,再扩展为遍历与合并,现在看起来是没有这个场景
|
|
295
398
|
const process = definitions['bpmn:process']
|
|
399
|
+
const diagram = definitions['bpmndi:BPMNDiagram']
|
|
400
|
+
const plane = diagram?.['bpmndi:BPMNPlane']
|
|
401
|
+
if (!process || !plane) {
|
|
402
|
+
return { nodes, edges }
|
|
403
|
+
}
|
|
296
404
|
Object.keys(process).forEach((key) => {
|
|
297
405
|
if (key.indexOf('bpmn:') === 0) {
|
|
298
406
|
const value = process[key]
|
|
299
407
|
if (key === BpmnElements.FLOW) {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
'bpmndi:BPMNEdge'
|
|
303
|
-
]
|
|
408
|
+
const edgesRaw = plane['bpmndi:BPMNEdge']
|
|
409
|
+
const bpmnEdges = Array.isArray(edgesRaw) ? edgesRaw : edgesRaw
|
|
304
410
|
edges = getLfEdges(value, bpmnEdges)
|
|
305
411
|
} else {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
'bpmndi:BPMNShape'
|
|
309
|
-
]
|
|
412
|
+
const shapesRaw = plane['bpmndi:BPMNShape']
|
|
413
|
+
const shapes = Array.isArray(shapesRaw) ? shapesRaw : shapesRaw
|
|
310
414
|
nodes = nodes.concat(getLfNodes(value, shapes, key))
|
|
311
415
|
}
|
|
312
416
|
}
|
|
@@ -318,6 +422,14 @@ function convertBpmn2LfData(bpmnData) {
|
|
|
318
422
|
}
|
|
319
423
|
}
|
|
320
424
|
|
|
425
|
+
/**
|
|
426
|
+
* 根据 BPMN 的 process 子节点与 plane 中的 BPMNShape 生成 LogicFlow 节点数组
|
|
427
|
+
* 输入:
|
|
428
|
+
* - value:当前类型(如 bpmn:userTask)的值,可能为对象或数组
|
|
429
|
+
* - shapes:plane['bpmndi:BPMNShape'],可能为对象或数组
|
|
430
|
+
* - key:当前处理的 BPMN 类型键名(如 'bpmn:userTask')
|
|
431
|
+
* 输出:LogicFlow 节点数组
|
|
432
|
+
*/
|
|
321
433
|
function getLfNodes(value, shapes, key) {
|
|
322
434
|
const nodes: NodeConfig[] = []
|
|
323
435
|
if (Array.isArray(value)) {
|
|
@@ -349,10 +461,23 @@ function getLfNodes(value, shapes, key) {
|
|
|
349
461
|
return nodes
|
|
350
462
|
}
|
|
351
463
|
|
|
464
|
+
/**
|
|
465
|
+
* 将单个 BPMNShape 与其对应的 process 节点合成为 LogicFlow 节点配置
|
|
466
|
+
* 输入:
|
|
467
|
+
* - shapeValue:plane 中的 BPMNShape(包含 Bounds 与可选 BPMNLabel)
|
|
468
|
+
* - type:BPMN 节点类型键(如 'bpmn:userTask')
|
|
469
|
+
* - processValue:process 中对应的节点对象(包含 '-id'、'-name' 等)
|
|
470
|
+
* 输出:LogicFlow NodeConfig
|
|
471
|
+
* 特殊处理:
|
|
472
|
+
* - 坐标从左上角转为中心点;
|
|
473
|
+
* - 文本从 '-name' 读取并进行 XML 实体反转义;
|
|
474
|
+
* - 文本位置优先使用 BPMNLabel 的 Bounds。
|
|
475
|
+
*/
|
|
352
476
|
function getNodeConfig(shapeValue, type, processValue) {
|
|
353
477
|
let x = Number(shapeValue['dc:Bounds']['-x'])
|
|
354
478
|
let y = Number(shapeValue['dc:Bounds']['-y'])
|
|
355
|
-
|
|
479
|
+
// 反转义 XML 实体,确保导入后文本包含特殊字符时能被完整还原
|
|
480
|
+
const name = unescapeXml(processValue['-name'])
|
|
356
481
|
const shapeConfig = BpmnAdapter.shapeConfigMap.get(type)
|
|
357
482
|
if (shapeConfig) {
|
|
358
483
|
x += shapeConfig.width / 2
|
|
@@ -399,6 +524,13 @@ function getNodeConfig(shapeValue, type, processValue) {
|
|
|
399
524
|
return nodeConfig
|
|
400
525
|
}
|
|
401
526
|
|
|
527
|
+
/**
|
|
528
|
+
* 根据 BPMN 的 sequenceFlow 与 BPMNEdge 生成 LogicFlow 边数组
|
|
529
|
+
* 输入:
|
|
530
|
+
* - value:process['bpmn:sequenceFlow'],对象或数组
|
|
531
|
+
* - bpmnEdges:plane['bpmndi:BPMNEdge'],对象或数组
|
|
532
|
+
* 输出:LogicFlow 边数组
|
|
533
|
+
*/
|
|
402
534
|
function getLfEdges(value, bpmnEdges) {
|
|
403
535
|
const edges: EdgeConfig[] = []
|
|
404
536
|
if (Array.isArray(value)) {
|
|
@@ -427,11 +559,31 @@ function getLfEdges(value, bpmnEdges) {
|
|
|
427
559
|
return edges
|
|
428
560
|
}
|
|
429
561
|
|
|
562
|
+
/**
|
|
563
|
+
* 将单个 BPMNEdge 与其对应的 sequenceFlow 合成为 LogicFlow 边配置
|
|
564
|
+
* 输入:
|
|
565
|
+
* - edgeValue:BPMNEdge(包含 di:waypoint 以及可选 BPMNLabel/Bounds)
|
|
566
|
+
* - processValue:sequenceFlow(包含 '-id'、'-sourceRef'、'-targetRef'、'-name' 等)
|
|
567
|
+
* 输出:LogicFlow EdgeConfig
|
|
568
|
+
* 特殊处理:
|
|
569
|
+
* - 文本从 '-name' 读取并进行 XML 实体反转义;
|
|
570
|
+
* - 若缺失 BPMNLabel,则以边的几何中心近似作为文本位置;
|
|
571
|
+
* - pointsList 由 waypoints 转换得到,数值类型统一为 Number。
|
|
572
|
+
*/
|
|
430
573
|
function getEdgeConfig(edgeValue, processValue): EdgeConfig {
|
|
431
574
|
let text
|
|
432
|
-
|
|
575
|
+
// 反转义 XML 实体,确保导入后文本包含特殊字符时能被完整还原
|
|
576
|
+
const textVal = processValue['-name']
|
|
577
|
+
? unescapeXml(`${processValue['-name']}`)
|
|
578
|
+
: ''
|
|
433
579
|
if (textVal) {
|
|
434
|
-
|
|
580
|
+
let textBounds
|
|
581
|
+
if (
|
|
582
|
+
edgeValue['bpmndi:BPMNLabel'] &&
|
|
583
|
+
edgeValue['bpmndi:BPMNLabel']['dc:Bounds']
|
|
584
|
+
) {
|
|
585
|
+
textBounds = edgeValue['bpmndi:BPMNLabel']['dc:Bounds']
|
|
586
|
+
}
|
|
435
587
|
// 如果边文本换行,则其偏移量应该是最长一行的位置
|
|
436
588
|
let textLength = 0
|
|
437
589
|
textVal.split('\n').forEach((textSpan) => {
|
|
@@ -440,10 +592,26 @@ function getEdgeConfig(edgeValue, processValue): EdgeConfig {
|
|
|
440
592
|
}
|
|
441
593
|
})
|
|
442
594
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
595
|
+
if (textBounds) {
|
|
596
|
+
text = {
|
|
597
|
+
value: textVal,
|
|
598
|
+
x: Number(textBounds['-x']) + (textLength * 10) / 2,
|
|
599
|
+
y: Number(textBounds['-y']) + 7,
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
// 兼容缺少 BPMNLabel 的图:使用边的几何中心作为文本位置
|
|
603
|
+
const waypoints = edgeValue['di:waypoint'] || []
|
|
604
|
+
const first = waypoints[0]
|
|
605
|
+
const last = waypoints[waypoints.length - 1] || first
|
|
606
|
+
const centerX =
|
|
607
|
+
(Number(first?.['-x'] || 0) + Number(last?.['-x'] || 0)) / 2
|
|
608
|
+
const centerY =
|
|
609
|
+
(Number(first?.['-y'] || 0) + Number(last?.['-y'] || 0)) / 2
|
|
610
|
+
text = {
|
|
611
|
+
value: textVal,
|
|
612
|
+
x: centerX,
|
|
613
|
+
y: centerY,
|
|
614
|
+
}
|
|
447
615
|
}
|
|
448
616
|
}
|
|
449
617
|
let properties
|
|
@@ -474,6 +642,14 @@ function getEdgeConfig(edgeValue, processValue): EdgeConfig {
|
|
|
474
642
|
return edge
|
|
475
643
|
}
|
|
476
644
|
|
|
645
|
+
/**
|
|
646
|
+
* BpmnAdapter:基础适配器
|
|
647
|
+
*
|
|
648
|
+
* 作用:在 LogicFlow 数据与 BPMN JSON 之间进行转换,并注入 adapterIn/adapterOut 钩子。
|
|
649
|
+
* - processAttributes:导出时 BPMN process 的基础属性(可配置 isExecutable、id 等)。
|
|
650
|
+
* - definitionAttributes:导出时 BPMN definitions 的基础属性与命名空间声明。
|
|
651
|
+
* - shapeConfigMap:不同 BPMN 元素类型的默认宽高,用于坐标/Bounds 计算。
|
|
652
|
+
*/
|
|
477
653
|
class BpmnAdapter {
|
|
478
654
|
static pluginName = 'bpmn-adapter'
|
|
479
655
|
static shapeConfigMap = new Map()
|
|
@@ -494,6 +670,11 @@ class BpmnAdapter {
|
|
|
494
670
|
[key: string]: any
|
|
495
671
|
}
|
|
496
672
|
|
|
673
|
+
/**
|
|
674
|
+
* 构造函数
|
|
675
|
+
* - 注入 LogicFlow 的 adapterIn/adapterOut(处理 JSON 方向的适配)
|
|
676
|
+
* - 初始化 process 与 definitions 的基础属性
|
|
677
|
+
*/
|
|
497
678
|
constructor({ lf }) {
|
|
498
679
|
lf.adapterIn = (data) => this.adapterIn(data)
|
|
499
680
|
lf.adapterOut = (data, retainedFields?: string[]) =>
|
|
@@ -524,6 +705,13 @@ class BpmnAdapter {
|
|
|
524
705
|
* ["properties", "startPoint", "endPoint", "pointsList"]合并,
|
|
525
706
|
* 这意味着出现在这个数组里的字段当它的值是数组或是对象时不会被视为一个节点而是一个属性。
|
|
526
707
|
*/
|
|
708
|
+
/**
|
|
709
|
+
* adapterOut:将 LogicFlow 图数据转换为 BPMN JSON
|
|
710
|
+
* 输入:
|
|
711
|
+
* - data:LogicFlow GraphData
|
|
712
|
+
* - retainedFields:扩展属性保留字段
|
|
713
|
+
* 输出:BPMN JSON(包含 definitions/process/diagram/plane)
|
|
714
|
+
*/
|
|
527
715
|
adapterOut = (data, retainedFields?: string[]) => {
|
|
528
716
|
const bpmnProcessData = { ...this.processAttributes }
|
|
529
717
|
convertLf2ProcessData(bpmnProcessData, data, retainedFields)
|
|
@@ -543,6 +731,11 @@ class BpmnAdapter {
|
|
|
543
731
|
}
|
|
544
732
|
return bpmnData
|
|
545
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* adapterIn:将 BPMN JSON 转换为 LogicFlow 图数据
|
|
736
|
+
* 输入:bpmnData:BPMN JSON
|
|
737
|
+
* 输出:GraphConfigData(nodes/edges)
|
|
738
|
+
*/
|
|
546
739
|
adapterIn = (bpmnData) => {
|
|
547
740
|
if (bpmnData) {
|
|
548
741
|
return convertBpmn2LfData(bpmnData)
|
|
@@ -571,9 +764,19 @@ BpmnAdapter.shapeConfigMap.set(BpmnElements.USER, {
|
|
|
571
764
|
height: UserTaskConfig.height,
|
|
572
765
|
})
|
|
573
766
|
|
|
767
|
+
/**
|
|
768
|
+
* BpmnXmlAdapter:XML 适配器(继承 BpmnAdapter)
|
|
769
|
+
*
|
|
770
|
+
* 作用:处理 XML 输入/输出的适配,使用 xml2json/json2xml 完成格式转换。
|
|
771
|
+
* 特殊处理:在 XML 导入前对 name 属性的非法字符进行预处理转义,提升容错。
|
|
772
|
+
*/
|
|
574
773
|
class BpmnXmlAdapter extends BpmnAdapter {
|
|
575
774
|
static pluginName = 'bpmnXmlAdapter'
|
|
576
775
|
|
|
776
|
+
/**
|
|
777
|
+
* 构造函数
|
|
778
|
+
* - 覆盖 LogicFlow 的 adapterIn/adapterOut,使其面向 XML 输入与输出。
|
|
779
|
+
*/
|
|
577
780
|
constructor(data) {
|
|
578
781
|
super(data)
|
|
579
782
|
const { lf } = data
|
|
@@ -581,10 +784,46 @@ class BpmnXmlAdapter extends BpmnAdapter {
|
|
|
581
784
|
lf.adapterOut = this.adapterXmlOut
|
|
582
785
|
}
|
|
583
786
|
|
|
787
|
+
// 预处理:修复属性值中非法的XML字符(仅针对 name 属性)
|
|
788
|
+
/**
|
|
789
|
+
* 预处理 XML:仅对 name 属性值进行非法字符转义(<, >, &),避免 DOM 解析失败。
|
|
790
|
+
* 注意:不影响已合法的实体(如 &),仅在属性值中生效,不修改其它内容。
|
|
791
|
+
*/
|
|
792
|
+
private sanitizeNameAttributes(xml: string): string {
|
|
793
|
+
return xml.replace(/name="([^"]*)"/g, (_, val) => {
|
|
794
|
+
const safe = val
|
|
795
|
+
.replace(/&(?!#?\w+;)/g, '&')
|
|
796
|
+
.replace(/</g, '<')
|
|
797
|
+
.replace(/>/g, '>')
|
|
798
|
+
return `name="${safe}"`
|
|
799
|
+
})
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* adapterXmlIn:将 BPMN XML 转换为 LogicFlow 图数据
|
|
804
|
+
* 输入:bpmnData:XML 字符串或对象
|
|
805
|
+
* 步骤:
|
|
806
|
+
* 1) 若为字符串,先对 name 属性进行预处理转义;
|
|
807
|
+
* 2) 使用 lfXml2Json 转换为 BPMN JSON;
|
|
808
|
+
* 3) 调用基础 adapterIn 转换为 GraphData。
|
|
809
|
+
*/
|
|
584
810
|
adapterXmlIn = (bpmnData) => {
|
|
585
|
-
const
|
|
811
|
+
const xmlStr =
|
|
812
|
+
typeof bpmnData === 'string'
|
|
813
|
+
? this.sanitizeNameAttributes(bpmnData)
|
|
814
|
+
: bpmnData
|
|
815
|
+
const json = lfXml2Json(xmlStr)
|
|
586
816
|
return this.adapterIn(json)
|
|
587
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* adapterXmlOut:将 LogicFlow 图数据转换为 BPMN XML
|
|
820
|
+
* 输入:
|
|
821
|
+
* - data:GraphData
|
|
822
|
+
* - retainedFields:保留属性字段
|
|
823
|
+
* 步骤:
|
|
824
|
+
* 1) 调用基础 adapterOut 生成 BPMN JSON;
|
|
825
|
+
* 2) 使用 lfJson2Xml 转为合法的 XML 字符串(包含属性与文本的转义)。
|
|
826
|
+
*/
|
|
588
827
|
adapterXmlOut = (data, retainedFields?: string[]) => {
|
|
589
828
|
const outData = this.adapterOut(data, retainedFields)
|
|
590
829
|
return lfJson2Xml(outData)
|
|
@@ -26,6 +26,31 @@ function handleAttributes(o: any) {
|
|
|
26
26
|
return t
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 将普通文本中的一些特殊字符进行转移,保障文本安全地嵌入 XML:
|
|
31
|
+
* - 空值(`null/undefined`)返回空字符串,避免输出非法字面量;
|
|
32
|
+
* - 按顺序转义 XML 保留字符:`&`, `<`, `>`, `"`, `'`;
|
|
33
|
+
* 注意优先转义 `&`,避免后续生成的实体被再次转义。
|
|
34
|
+
* @param text 原始文本
|
|
35
|
+
* @returns 已完成 XML 转义的字符串
|
|
36
|
+
*/
|
|
37
|
+
function escapeXml(text: string) {
|
|
38
|
+
// 空值直接返回空字符串,防止在 XML 中出现 "null"/"undefined"
|
|
39
|
+
if (text == null) return ''
|
|
40
|
+
return (
|
|
41
|
+
text
|
|
42
|
+
.toString()
|
|
43
|
+
// & 必须先转义,避免影响后续 < > " ' 的实体
|
|
44
|
+
.replace(/&/g, '&')
|
|
45
|
+
// 小于号与大于号,用于标签边界
|
|
46
|
+
.replace(/</g, '<')
|
|
47
|
+
.replace(/>/g, '>')
|
|
48
|
+
// 双引号与单引号,用于属性值的包裹
|
|
49
|
+
.replace(/"/g, '"')
|
|
50
|
+
.replace(/'/g, ''')
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
29
54
|
function getAttributes(obj: any) {
|
|
30
55
|
let tmp = obj
|
|
31
56
|
try {
|
|
@@ -35,7 +60,8 @@ function getAttributes(obj: any) {
|
|
|
35
60
|
} catch (error) {
|
|
36
61
|
tmp = JSON.stringify(handleAttributes(obj)).replace(/"/g, "'")
|
|
37
62
|
}
|
|
38
|
-
|
|
63
|
+
// 确保属性值中的特殊字符被正确转义
|
|
64
|
+
return escapeXml(String(tmp))
|
|
39
65
|
}
|
|
40
66
|
|
|
41
67
|
const tn = '\t\n'
|
|
@@ -51,7 +77,7 @@ function toXml(obj: string | any[] | Object, name: string, depth: number) {
|
|
|
51
77
|
|
|
52
78
|
let str = ''
|
|
53
79
|
if (name === '#text') {
|
|
54
|
-
return tn + frontSpace + obj
|
|
80
|
+
return tn + frontSpace + escapeXml(String(obj))
|
|
55
81
|
} else if (name === '#cdata-section') {
|
|
56
82
|
return tn + frontSpace + '<![CDATA[' + obj + ']]>'
|
|
57
83
|
} else if (name === '#comment') {
|
|
@@ -78,7 +104,7 @@ function toXml(obj: string | any[] | Object, name: string, depth: number) {
|
|
|
78
104
|
attributes +
|
|
79
105
|
(children !== '' ? `>${children}${tn + frontSpace}</${name}>` : ' />')
|
|
80
106
|
} else {
|
|
81
|
-
str += tn + frontSpace + `<${name}>${obj
|
|
107
|
+
str += tn + frontSpace + `<${name}>${escapeXml(String(obj))}</${name}>`
|
|
82
108
|
}
|
|
83
109
|
}
|
|
84
110
|
|
|
@@ -98,4 +124,4 @@ function lfJson2Xml(o: Object) {
|
|
|
98
124
|
return xmlStr
|
|
99
125
|
}
|
|
100
126
|
|
|
101
|
-
export { lfJson2Xml, handleAttributes }
|
|
127
|
+
export { lfJson2Xml, handleAttributes, escapeXml }
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ========================================================================
|
|
4
4
|
// XML.ObjTree -- XML source code from/to JavaScript object like E4X
|
|
5
5
|
// ========================================================================
|
|
6
|
+
import { escapeXml } from './json2xml'
|
|
6
7
|
|
|
7
8
|
let XML = function () {}
|
|
8
9
|
|
|
@@ -282,13 +283,7 @@ XML.ObjTree.prototype.scalar_to_xml = function (name, text) {
|
|
|
282
283
|
|
|
283
284
|
// method: xml_escape( text )
|
|
284
285
|
|
|
285
|
-
XML.ObjTree.prototype.xml_escape =
|
|
286
|
-
return text
|
|
287
|
-
.replace(/&/g, '&')
|
|
288
|
-
.replace(/</g, '<')
|
|
289
|
-
.replace(/>/g, '>')
|
|
290
|
-
.replace(/"/g, '"')
|
|
291
|
-
}
|
|
286
|
+
XML.ObjTree.prototype.xml_escape = escapeXml
|
|
292
287
|
|
|
293
288
|
/*
|
|
294
289
|
// ========================================================================
|