scjson 0.3.0 → 0.3.1
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/dist/converters.d.ts +52 -13
- package/dist/converters.js +256 -17
- package/package.json +1 -1
package/dist/converters.d.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Convert an SCXML string to scjson.
|
|
3
|
-
*
|
|
4
|
-
* @param {string} xmlStr - XML input.
|
|
5
|
-
* @param {boolean} [omitEmpty=true] - Remove empty values when true.
|
|
6
|
-
* @returns {{result: string, valid: boolean, errors: object[]|null}} Conversion outcome.
|
|
7
|
-
*
|
|
8
|
-
* Removes the XML namespace attribute and injects default values
|
|
9
|
-
* expected by the schema.
|
|
10
|
-
*/
|
|
11
|
-
export function xmlToJson(xmlStr: string, omitEmpty?: boolean): {
|
|
1
|
+
export function xmlToJson(xmlStr: any, omitEmpty?: boolean): {
|
|
12
2
|
result: string;
|
|
13
|
-
valid:
|
|
14
|
-
errors:
|
|
3
|
+
valid: any;
|
|
4
|
+
errors: any;
|
|
15
5
|
};
|
|
16
6
|
/**
|
|
17
7
|
* Convert a scjson string to SCXML.
|
|
@@ -120,6 +110,18 @@ export function fixSendContent(value: object | any[]): void;
|
|
|
120
110
|
* @param {object|Array} value - Parsed object to adjust in place.
|
|
121
111
|
*/
|
|
122
112
|
export function fixDonedataContent(value: object | any[]): void;
|
|
113
|
+
export function fixOtherAttributes(value: any): void;
|
|
114
|
+
/**
|
|
115
|
+
* Decode HTML entities in string values.
|
|
116
|
+
*
|
|
117
|
+
* Fast XML parser leaves character references intact. This helper matches the
|
|
118
|
+
* Python implementation by converting entities like ``
`` to their literal
|
|
119
|
+
* characters.
|
|
120
|
+
*
|
|
121
|
+
* @param {object|Array|string} value - Parsed value to normalise.
|
|
122
|
+
* @returns {object|Array|string} Normalised value.
|
|
123
|
+
*/
|
|
124
|
+
export function decodeEntities(value: object | any[] | string): object | any[] | string;
|
|
123
125
|
/**
|
|
124
126
|
* Convert a canonical content object back into XML element format.
|
|
125
127
|
*
|
|
@@ -162,6 +164,16 @@ export function splitTokenAttrs(value: object | any[], parent: any): void;
|
|
|
162
164
|
* @param {object|Array} value - Parsed object to adjust in place.
|
|
163
165
|
*/
|
|
164
166
|
export function fixEmptyElse(value: object | any[]): void;
|
|
167
|
+
/**
|
|
168
|
+
* Normalise empty ``onentry`` and ``onexit`` elements.
|
|
169
|
+
*
|
|
170
|
+
* The XML parser represents empty tags as an empty string. The Python
|
|
171
|
+
* reference output preserves these elements as empty objects so they
|
|
172
|
+
* survive subsequent cleaning steps. This helper mirrors that behaviour.
|
|
173
|
+
*
|
|
174
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
175
|
+
*/
|
|
176
|
+
export function fixEmptyOnentry(value: object | any[]): void;
|
|
165
177
|
/**
|
|
166
178
|
* Remove transition elements directly under the <scxml> root.
|
|
167
179
|
*
|
|
@@ -189,3 +201,30 @@ export function stripQnameNs(value: object | any[]): void;
|
|
|
189
201
|
* @param {object|Array} value - Parsed object to adjust in place.
|
|
190
202
|
*/
|
|
191
203
|
export function reorderScxml(value: object | any[]): void;
|
|
204
|
+
/**
|
|
205
|
+
* Convert an SCXML string to scjson.
|
|
206
|
+
*
|
|
207
|
+
* @param {string} xmlStr - XML input.
|
|
208
|
+
* @param {boolean} [omitEmpty=true] - Remove empty values when true.
|
|
209
|
+
* @returns {{result: string, valid: boolean, errors: object[]|null}} Conversion outcome.
|
|
210
|
+
*
|
|
211
|
+
* Removes the XML namespace attribute and injects default values
|
|
212
|
+
* expected by the schema.
|
|
213
|
+
*/
|
|
214
|
+
/**
|
|
215
|
+
* Recursively strip default attributes from nested data nodes.
|
|
216
|
+
*
|
|
217
|
+
* Any object with a ``qname`` property other than ``scxml`` may have
|
|
218
|
+
* ``version`` or ``datamodel_attribute`` inserted during validation.
|
|
219
|
+
* This helper removes those keys so that nested structures match the
|
|
220
|
+
* canonical Python output.
|
|
221
|
+
*
|
|
222
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
223
|
+
*/
|
|
224
|
+
export function stripNestedDataAttrs(value: object | any[]): void;
|
|
225
|
+
/**
|
|
226
|
+
* Recursively remove ``xmlns`` attributes from nested objects.
|
|
227
|
+
*
|
|
228
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
229
|
+
*/
|
|
230
|
+
export function stripXmlns(value: object | any[]): void;
|
package/dist/converters.js
CHANGED
|
@@ -59,7 +59,6 @@ const STRUCTURAL_FIELDS = new Set([
|
|
|
59
59
|
* Recursively convert an XML Element to SCJSON-compliant JS object.
|
|
60
60
|
*/
|
|
61
61
|
function convert(element) {
|
|
62
|
-
var _a;
|
|
63
62
|
const result = {
|
|
64
63
|
tag: element.tagName,
|
|
65
64
|
...Object.fromEntries(Array.from(element.attributes).map(attr => [attr.name, attr.value]))
|
|
@@ -85,16 +84,16 @@ function convert(element) {
|
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
// Handle text content if present
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
result.content = [
|
|
87
|
+
const rawText = element.textContent;
|
|
88
|
+
if (rawText && element.children.length === 0 && rawText.trim() !== '') {
|
|
89
|
+
result.content = [rawText];
|
|
91
90
|
}
|
|
92
91
|
return result;
|
|
93
92
|
}
|
|
94
93
|
/**
|
|
95
94
|
* Keys that should never be pruned even when empty.
|
|
96
95
|
*/
|
|
97
|
-
const ALWAYS_KEEP = new Set(['else_value', 'else', 'final']);
|
|
96
|
+
const ALWAYS_KEEP = new Set(['else_value', 'else', 'final', 'onentry']);
|
|
98
97
|
/**
|
|
99
98
|
* Remove transition elements directly under the <scxml> root.
|
|
100
99
|
*
|
|
@@ -145,7 +144,12 @@ function collapseWhitespace(value) {
|
|
|
145
144
|
if (value && typeof value === 'object') {
|
|
146
145
|
for (const [k, v] of Object.entries(value)) {
|
|
147
146
|
if ((k.endsWith('_attribute') || COLLAPSE_ATTRS.has(k)) && typeof v === 'string') {
|
|
148
|
-
|
|
147
|
+
if (v.startsWith('\n')) {
|
|
148
|
+
value[k] = '\n' + v.slice(1).replace(/[\n\r\t]/g, ' ');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
value[k] = v.replace(/[\n\r\t]/g, ' ');
|
|
152
|
+
}
|
|
149
153
|
}
|
|
150
154
|
else {
|
|
151
155
|
value[k] = collapseWhitespace(v);
|
|
@@ -175,7 +179,7 @@ function splitTokenAttrs(value, parent) {
|
|
|
175
179
|
continue;
|
|
176
180
|
}
|
|
177
181
|
if (k === 'transition') {
|
|
178
|
-
if (parent !== 'history') {
|
|
182
|
+
if (parent !== 'history' && parent !== 'initial') {
|
|
179
183
|
const arr = Array.isArray(v) ? v : [v];
|
|
180
184
|
arr.forEach(tr => {
|
|
181
185
|
if (typeof tr.target === 'string')
|
|
@@ -311,7 +315,7 @@ function ensureArrays(obj, parent) {
|
|
|
311
315
|
continue;
|
|
312
316
|
}
|
|
313
317
|
if (k === 'transition' && v && typeof v === 'object') {
|
|
314
|
-
if (parent !== 'history') {
|
|
318
|
+
if (parent !== 'history' && parent !== 'initial') {
|
|
315
319
|
const arr = Array.isArray(v) ? v : [v];
|
|
316
320
|
arr.forEach(tr => {
|
|
317
321
|
if (tr.target !== undefined && !Array.isArray(tr.target))
|
|
@@ -355,6 +359,66 @@ function fixEmptyElse(value) {
|
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Normalise empty ``onentry`` and ``onexit`` elements.
|
|
364
|
+
*
|
|
365
|
+
* The XML parser represents empty tags as an empty string. The Python
|
|
366
|
+
* reference output preserves these elements as empty objects so they
|
|
367
|
+
* survive subsequent cleaning steps. This helper mirrors that behaviour.
|
|
368
|
+
*
|
|
369
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
370
|
+
*/
|
|
371
|
+
function fixEmptyOnentry(value) {
|
|
372
|
+
if (Array.isArray(value)) {
|
|
373
|
+
value.forEach(fixEmptyOnentry);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (value && typeof value === 'object') {
|
|
377
|
+
for (const [k, v] of Object.entries(value)) {
|
|
378
|
+
if ((k === 'onentry' || k === 'onexit') &&
|
|
379
|
+
Array.isArray(v) &&
|
|
380
|
+
v.length === 1 &&
|
|
381
|
+
typeof v[0] === 'string' &&
|
|
382
|
+
v[0].trim() === '') {
|
|
383
|
+
value[k] = [{}];
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
fixEmptyOnentry(v);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Decode HTML entities in string values.
|
|
392
|
+
*
|
|
393
|
+
* Fast XML parser leaves character references intact. This helper matches the
|
|
394
|
+
* Python implementation by converting entities like ``
`` to their literal
|
|
395
|
+
* characters.
|
|
396
|
+
*
|
|
397
|
+
* @param {object|Array|string} value - Parsed value to normalise.
|
|
398
|
+
* @returns {object|Array|string} Normalised value.
|
|
399
|
+
*/
|
|
400
|
+
function decodeEntities(value) {
|
|
401
|
+
if (Array.isArray(value)) {
|
|
402
|
+
return value.map(decodeEntities);
|
|
403
|
+
}
|
|
404
|
+
if (value && typeof value === 'object') {
|
|
405
|
+
for (const [k, v] of Object.entries(value)) {
|
|
406
|
+
value[k] = decodeEntities(v);
|
|
407
|
+
}
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
if (typeof value === 'string') {
|
|
411
|
+
return value
|
|
412
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCharCode(parseInt(h, 16)))
|
|
413
|
+
.replace(/&#([0-9]+);/g, (_, d) => String.fromCharCode(parseInt(d, 10)))
|
|
414
|
+
.replace(/"/g, '"')
|
|
415
|
+
.replace(/'/g, "'")
|
|
416
|
+
.replace(/&/g, '&')
|
|
417
|
+
.replace(/</g, '<')
|
|
418
|
+
.replace(/>/g, '>');
|
|
419
|
+
}
|
|
420
|
+
return value;
|
|
421
|
+
}
|
|
358
422
|
/**
|
|
359
423
|
* Normalise script elements after parsing.
|
|
360
424
|
*
|
|
@@ -470,6 +534,52 @@ function fixAssignDefaults(value) {
|
|
|
470
534
|
}
|
|
471
535
|
}
|
|
472
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Hoist unexpected attributes into ``other_attributes``.
|
|
539
|
+
*
|
|
540
|
+
* Handles the ``id`` attribute on ``assign`` elements and the
|
|
541
|
+
* misspelled ``intial`` attribute on ``state`` elements so that
|
|
542
|
+
* generated scjson matches the reference Python output.
|
|
543
|
+
*
|
|
544
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
545
|
+
*/
|
|
546
|
+
// Avoid infinite recursion on cyclic structures
|
|
547
|
+
const VISITED_FLAG = Symbol('fixOtherAttributesVisited');
|
|
548
|
+
function fixOtherAttributes(value) {
|
|
549
|
+
if (Array.isArray(value)) {
|
|
550
|
+
value.forEach(fixOtherAttributes);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (value && typeof value === 'object') {
|
|
554
|
+
if (value[VISITED_FLAG]) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
value[VISITED_FLAG] = true;
|
|
558
|
+
if (Object.prototype.hasOwnProperty.call(value, 'assign')) {
|
|
559
|
+
const arr = Array.isArray(value.assign) ? value.assign : [value.assign];
|
|
560
|
+
arr.forEach(a => {
|
|
561
|
+
if (a.id !== undefined) {
|
|
562
|
+
a.other_attributes = a.other_attributes || {};
|
|
563
|
+
a.other_attributes.id = a.id;
|
|
564
|
+
delete a.id;
|
|
565
|
+
}
|
|
566
|
+
fixOtherAttributes(a);
|
|
567
|
+
});
|
|
568
|
+
value.assign = arr;
|
|
569
|
+
}
|
|
570
|
+
if (value.intial !== undefined) {
|
|
571
|
+
value.other_attributes = value.other_attributes || {};
|
|
572
|
+
value.other_attributes.intial = value.intial;
|
|
573
|
+
delete value.intial;
|
|
574
|
+
}
|
|
575
|
+
for (const [k, v] of Object.entries(value)) {
|
|
576
|
+
if (v === value || k === 'other_attributes')
|
|
577
|
+
continue;
|
|
578
|
+
fixOtherAttributes(v);
|
|
579
|
+
}
|
|
580
|
+
delete value[VISITED_FLAG];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
473
583
|
/**
|
|
474
584
|
* Apply default values for send elements.
|
|
475
585
|
*
|
|
@@ -521,6 +631,14 @@ function fixSendContent(value) {
|
|
|
521
631
|
return;
|
|
522
632
|
}
|
|
523
633
|
if (value && typeof value === 'object') {
|
|
634
|
+
if (Object.prototype.hasOwnProperty.call(value, 'qname')) {
|
|
635
|
+
if (Object.prototype.hasOwnProperty.call(value, 'version')) {
|
|
636
|
+
delete value.version;
|
|
637
|
+
}
|
|
638
|
+
if (Object.prototype.hasOwnProperty.call(value, 'datamodel_attribute')) {
|
|
639
|
+
delete value.datamodel_attribute;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
524
642
|
if (Object.prototype.hasOwnProperty.call(value, 'send')) {
|
|
525
643
|
const arr = Array.isArray(value.send) ? value.send : [value.send];
|
|
526
644
|
arr.forEach(s => {
|
|
@@ -528,8 +646,10 @@ function fixSendContent(value) {
|
|
|
528
646
|
const cArr = Array.isArray(s.content) ? s.content : [s.content];
|
|
529
647
|
const mapped = cArr.map(c => {
|
|
530
648
|
if (typeof c !== 'object') {
|
|
531
|
-
const
|
|
532
|
-
|
|
649
|
+
const raw = String(c);
|
|
650
|
+
if (raw.trim() === '')
|
|
651
|
+
return null;
|
|
652
|
+
return { content: [{ content: [raw] }] };
|
|
533
653
|
}
|
|
534
654
|
if (c && typeof c === 'object') {
|
|
535
655
|
if (typeof c.content === 'string' || typeof c.content === 'number' || typeof c.content === 'boolean') {
|
|
@@ -537,8 +657,8 @@ function fixSendContent(value) {
|
|
|
537
657
|
}
|
|
538
658
|
if (Array.isArray(c.content)) {
|
|
539
659
|
c.content = c.content
|
|
540
|
-
.map(i => (typeof i === 'string' ? i
|
|
541
|
-
.filter(i => i
|
|
660
|
+
.map(i => (typeof i === 'string' ? String(i) : i))
|
|
661
|
+
.filter(i => !(typeof i === 'string' && i.trim() === '') && i !== null && i !== undefined);
|
|
542
662
|
if (c.content.length === 0)
|
|
543
663
|
delete c.content;
|
|
544
664
|
}
|
|
@@ -550,6 +670,10 @@ function fixSendContent(value) {
|
|
|
550
670
|
else {
|
|
551
671
|
fixSendContent(c);
|
|
552
672
|
}
|
|
673
|
+
if (c.qname && c.version !== undefined)
|
|
674
|
+
delete c.version;
|
|
675
|
+
if (c.qname && c.datamodel_attribute !== undefined)
|
|
676
|
+
delete c.datamodel_attribute;
|
|
553
677
|
return c;
|
|
554
678
|
}
|
|
555
679
|
return null;
|
|
@@ -587,8 +711,10 @@ function fixDonedataContent(value) {
|
|
|
587
711
|
const cArr = Array.isArray(d.content) ? d.content : [d.content];
|
|
588
712
|
const mapped = cArr.map(c => {
|
|
589
713
|
if (typeof c !== 'object') {
|
|
590
|
-
const
|
|
591
|
-
|
|
714
|
+
const raw = String(c);
|
|
715
|
+
if (raw.trim() === '')
|
|
716
|
+
return null;
|
|
717
|
+
return { content: [raw] };
|
|
592
718
|
}
|
|
593
719
|
if (c && typeof c === 'object') {
|
|
594
720
|
if (typeof c.content === 'string' ||
|
|
@@ -604,6 +730,10 @@ function fixDonedataContent(value) {
|
|
|
604
730
|
else {
|
|
605
731
|
fixDonedataContent(c);
|
|
606
732
|
}
|
|
733
|
+
if (c.qname && c.version !== undefined)
|
|
734
|
+
delete c.version;
|
|
735
|
+
if (c.qname && c.datamodel_attribute !== undefined)
|
|
736
|
+
delete c.datamodel_attribute;
|
|
607
737
|
return c;
|
|
608
738
|
}
|
|
609
739
|
return null;
|
|
@@ -776,6 +906,27 @@ function stripQnameNs(value) {
|
|
|
776
906
|
}
|
|
777
907
|
}
|
|
778
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Recursively remove ``xmlns`` attributes from nested objects.
|
|
911
|
+
*
|
|
912
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
913
|
+
*/
|
|
914
|
+
function stripXmlns(value) {
|
|
915
|
+
if (Array.isArray(value)) {
|
|
916
|
+
value.forEach(stripXmlns);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (value && typeof value === 'object') {
|
|
920
|
+
for (const k of Object.keys(value)) {
|
|
921
|
+
if (k === '@_xmlns' || k.startsWith('xmlns')) {
|
|
922
|
+
delete value[k];
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
stripXmlns(value[k]);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
779
930
|
/**
|
|
780
931
|
* Collapse nested ``content`` wrappers created during parsing.
|
|
781
932
|
*
|
|
@@ -800,7 +951,8 @@ function flattenContent(value) {
|
|
|
800
951
|
Array.isArray(value.content[0].content) &&
|
|
801
952
|
value.content[0].content.length === 1 &&
|
|
802
953
|
value.content[0].content[0] &&
|
|
803
|
-
typeof value.content[0].content[0] === 'object'
|
|
954
|
+
typeof value.content[0].content[0] === 'object' &&
|
|
955
|
+
!Object.prototype.hasOwnProperty.call(value.content[0].content[0], 'qname')) {
|
|
804
956
|
value.content = [value.content[0].content[0]];
|
|
805
957
|
}
|
|
806
958
|
for (const v of Object.values(value)) {
|
|
@@ -822,7 +974,10 @@ function flattenContent(value) {
|
|
|
822
974
|
function removeEmpty(value, key) {
|
|
823
975
|
if (Array.isArray(value)) {
|
|
824
976
|
const arr = value.map(v => removeEmpty(v, key)).filter(v => v !== undefined);
|
|
825
|
-
|
|
977
|
+
if (arr.length > 0 || ALWAYS_KEEP.has(key)) {
|
|
978
|
+
return arr;
|
|
979
|
+
}
|
|
980
|
+
return undefined;
|
|
826
981
|
}
|
|
827
982
|
if (value && typeof value === 'object') {
|
|
828
983
|
const obj = {};
|
|
@@ -844,7 +999,7 @@ function removeEmpty(value, key) {
|
|
|
844
999
|
const base = key.startsWith('@_') ? key.slice(2) : key;
|
|
845
1000
|
if (base.endsWith('_attribute') ||
|
|
846
1001
|
base.endsWith('_value') ||
|
|
847
|
-
['expr', 'cond', 'event', 'target', 'id', 'name', 'label', 'text'].includes(
|
|
1002
|
+
['expr', 'cond', 'event', 'target', 'id', 'name', 'label', 'text'].includes(base) ||
|
|
848
1003
|
key === '@_xmlns') {
|
|
849
1004
|
return '';
|
|
850
1005
|
}
|
|
@@ -865,6 +1020,32 @@ const validate = ajv.compile(schema);
|
|
|
865
1020
|
* Removes the XML namespace attribute and injects default values
|
|
866
1021
|
* expected by the schema.
|
|
867
1022
|
*/
|
|
1023
|
+
/**
|
|
1024
|
+
* Recursively strip default attributes from nested data nodes.
|
|
1025
|
+
*
|
|
1026
|
+
* Any object with a ``qname`` property other than ``scxml`` may have
|
|
1027
|
+
* ``version`` or ``datamodel_attribute`` inserted during validation.
|
|
1028
|
+
* This helper removes those keys so that nested structures match the
|
|
1029
|
+
* canonical Python output.
|
|
1030
|
+
*
|
|
1031
|
+
* @param {object|Array} value - Parsed object to adjust in place.
|
|
1032
|
+
*/
|
|
1033
|
+
function stripNestedDataAttrs(value) {
|
|
1034
|
+
if (Array.isArray(value)) {
|
|
1035
|
+
value.forEach(stripNestedDataAttrs);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (value && typeof value === 'object') {
|
|
1039
|
+
if (Object.prototype.hasOwnProperty.call(value, 'qname') &&
|
|
1040
|
+
value.qname !== 'scxml') {
|
|
1041
|
+
delete value.version;
|
|
1042
|
+
delete value.datamodel_attribute;
|
|
1043
|
+
}
|
|
1044
|
+
for (const v of Object.values(value)) {
|
|
1045
|
+
stripNestedDataAttrs(v);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
868
1049
|
function xmlToJson(xmlStr, omitEmpty = true) {
|
|
869
1050
|
const parser = new XMLParser({
|
|
870
1051
|
ignoreAttributes: false,
|
|
@@ -876,17 +1057,21 @@ function xmlToJson(xmlStr, omitEmpty = true) {
|
|
|
876
1057
|
obj = obj.scxml;
|
|
877
1058
|
}
|
|
878
1059
|
obj = normaliseKeys(obj);
|
|
1060
|
+
obj = decodeEntities(obj);
|
|
879
1061
|
fixNestedScxml(obj);
|
|
880
1062
|
fixEmptyElse(obj);
|
|
881
1063
|
obj = collapseWhitespace(obj);
|
|
882
1064
|
splitTokenAttrs(obj);
|
|
883
1065
|
ensureArrays(obj);
|
|
1066
|
+
fixOtherAttributes(obj);
|
|
884
1067
|
fixScripts(obj);
|
|
885
1068
|
fixAssignDefaults(obj);
|
|
886
1069
|
fixSendDefaults(obj);
|
|
887
1070
|
fixSendContent(obj);
|
|
888
1071
|
fixDonedataContent(obj);
|
|
889
1072
|
fixDataContent(obj);
|
|
1073
|
+
fixEmptyOnentry(obj);
|
|
1074
|
+
fixSendContent(obj);
|
|
890
1075
|
flattenContent(obj);
|
|
891
1076
|
stripRootTransitions(obj);
|
|
892
1077
|
obj = collapseWhitespace(obj);
|
|
@@ -927,12 +1112,16 @@ function xmlToJson(xmlStr, omitEmpty = true) {
|
|
|
927
1112
|
}
|
|
928
1113
|
stripQnameNs(obj);
|
|
929
1114
|
reorderScxml(obj);
|
|
1115
|
+
stripNestedDataAttrs(obj);
|
|
1116
|
+
stripXmlns(obj);
|
|
930
1117
|
const valid = validate(obj);
|
|
931
1118
|
const errors = valid ? null : validate.errors;
|
|
932
1119
|
if (omitEmpty) {
|
|
933
1120
|
obj = removeEmpty(obj) || {};
|
|
934
1121
|
fixDataContent(obj);
|
|
935
1122
|
stripQnameNs(obj);
|
|
1123
|
+
stripNestedDataAttrs(obj);
|
|
1124
|
+
stripXmlns(obj);
|
|
936
1125
|
}
|
|
937
1126
|
let out = JSON.stringify(obj, null, 2);
|
|
938
1127
|
out = out.replace(/"version": 1(?=[,\n])/g, '"version": 1.0');
|
|
@@ -961,6 +1150,15 @@ function jsonToXml(jsonStr) {
|
|
|
961
1150
|
obj = removeEmpty(obj) || {};
|
|
962
1151
|
const valid = validate(obj);
|
|
963
1152
|
const errors = valid ? null : validate.errors;
|
|
1153
|
+
// Remove defaults injected by validation that would misidentify
|
|
1154
|
+
// arbitrary XML content blocks as nested SCXML documents. Ajv
|
|
1155
|
+
// populates ``version`` and ``datamodel_attribute`` for objects
|
|
1156
|
+
// matching the ``Scxml`` schema. When the original JSON only
|
|
1157
|
+
// contains a ``qname`` field these defaults lead to erroneous
|
|
1158
|
+
// ``<scxml>`` wrappers being generated on output. Stripping the
|
|
1159
|
+
// fields prior to conversion preserves parity with the Python
|
|
1160
|
+
// implementation.
|
|
1161
|
+
stripNestedDataAttrs(obj);
|
|
964
1162
|
function restoreKeys(value) {
|
|
965
1163
|
if (Array.isArray(value)) {
|
|
966
1164
|
return value.map(restoreKeys);
|
|
@@ -1000,6 +1198,14 @@ function jsonToXml(jsonStr) {
|
|
|
1000
1198
|
else if (k === 'else_value') {
|
|
1001
1199
|
nk = 'else';
|
|
1002
1200
|
}
|
|
1201
|
+
if (nk === 'other_attributes') {
|
|
1202
|
+
if (v && typeof v === 'object') {
|
|
1203
|
+
for (const [ak, av] of Object.entries(v)) {
|
|
1204
|
+
out[`@_${ak}`] = av;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1003
1209
|
for (const [attr, prop] of Object.entries(ATTRIBUTE_MAP)) {
|
|
1004
1210
|
if (prop === nk) {
|
|
1005
1211
|
nk = `@_${attr}`;
|
|
@@ -1024,6 +1230,34 @@ function jsonToXml(jsonStr) {
|
|
|
1024
1230
|
}
|
|
1025
1231
|
else if (nk === 'content') {
|
|
1026
1232
|
if (Array.isArray(v)) {
|
|
1233
|
+
if (v.every(item => item && typeof item === 'object' && Object.prototype.hasOwnProperty.call(item, 'qname'))) {
|
|
1234
|
+
v.forEach(item => {
|
|
1235
|
+
const r = restoreDataNode(item);
|
|
1236
|
+
const [ck, cv] = Object.entries(r)[0];
|
|
1237
|
+
if (out[ck]) {
|
|
1238
|
+
if (Array.isArray(out[ck])) {
|
|
1239
|
+
out[ck].push(cv);
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
out[ck] = [out[ck], cv];
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
out[ck] = cv;
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
if (value.location !== undefined &&
|
|
1252
|
+
v.length === 1 &&
|
|
1253
|
+
v[0] &&
|
|
1254
|
+
typeof v[0] === 'object' &&
|
|
1255
|
+
(v[0].state || v[0].parallel || v[0].final || v[0].datamodel ||
|
|
1256
|
+
v[0].datamodel_attribute !== undefined)) {
|
|
1257
|
+
const cv = restoreKeys(v[0]);
|
|
1258
|
+
out.scxml = cv;
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1027
1261
|
out[nk] = v.map(item => {
|
|
1028
1262
|
if (item &&
|
|
1029
1263
|
typeof item === 'object' &&
|
|
@@ -1123,11 +1357,16 @@ module.exports = {
|
|
|
1123
1357
|
fixSendDefaults,
|
|
1124
1358
|
fixSendContent,
|
|
1125
1359
|
fixDonedataContent,
|
|
1360
|
+
fixOtherAttributes,
|
|
1361
|
+
decodeEntities,
|
|
1126
1362
|
restoreDataNode,
|
|
1127
1363
|
flattenContent,
|
|
1128
1364
|
splitTokenAttrs,
|
|
1129
1365
|
fixEmptyElse,
|
|
1366
|
+
fixEmptyOnentry,
|
|
1130
1367
|
stripRootTransitions,
|
|
1131
1368
|
stripQnameNs,
|
|
1132
1369
|
reorderScxml,
|
|
1370
|
+
stripNestedDataAttrs,
|
|
1371
|
+
stripXmlns,
|
|
1133
1372
|
};
|