scjson 0.3.3 → 0.4.0
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.
- checksums.yaml +4 -4
- data/LEGAL.md +5 -0
- data/LICENSE +21 -0
- data/README.md +37 -0
- data/lib/scjson/cli.rb +86 -2
- data/lib/scjson/engine/context.rb +1597 -0
- data/lib/scjson/engine.rb +187 -0
- data/lib/scjson/types.rb +1964 -0
- data/lib/scjson/version.rb +1 -1
- data/lib/scjson.rb +252 -39
- metadata +19 -6
data/lib/scjson/version.rb
CHANGED
data/lib/scjson.rb
CHANGED
|
@@ -7,13 +7,22 @@
|
|
|
7
7
|
# Licensed under the BSD 1-Clause License.
|
|
8
8
|
|
|
9
9
|
require 'json'
|
|
10
|
-
|
|
10
|
+
begin
|
|
11
|
+
require 'nokogiri'
|
|
12
|
+
NOKOGIRI_AVAILABLE = true
|
|
13
|
+
rescue LoadError
|
|
14
|
+
NOKOGIRI_AVAILABLE = false
|
|
15
|
+
end
|
|
16
|
+
require 'shellwords'
|
|
11
17
|
|
|
12
18
|
require_relative 'scjson/version'
|
|
19
|
+
require_relative 'scjson/types'
|
|
13
20
|
|
|
14
21
|
# Canonical SCXML <-> scjson conversion for the Ruby agent.
|
|
15
22
|
module Scjson
|
|
16
23
|
XMLNS = 'http://www.w3.org/2005/07/scxml'.freeze
|
|
24
|
+
XINCLUDE_NS = 'http://www.w3.org/2001/XInclude'.freeze
|
|
25
|
+
XINCLUDE_CLARK_INCLUDE = "{#{XINCLUDE_NS}}include".freeze
|
|
17
26
|
|
|
18
27
|
ATTRIBUTE_MAP = {
|
|
19
28
|
'datamodel' => 'datamodel_attribute',
|
|
@@ -30,6 +39,8 @@ module Scjson
|
|
|
30
39
|
else content donedata initial
|
|
31
40
|
].freeze
|
|
32
41
|
|
|
42
|
+
SOURCE_BODY_TAGS = %w[script data].freeze
|
|
43
|
+
|
|
33
44
|
STRUCTURAL_FIELDS = %w[
|
|
34
45
|
state parallel final history transition invoke finalize datamodel data
|
|
35
46
|
onentry onexit log send cancel raise assign script foreach param if_value
|
|
@@ -45,14 +56,42 @@ module Scjson
|
|
|
45
56
|
# @param [Boolean] omit_empty Remove empty containers when true.
|
|
46
57
|
# @return [String] Canonical scjson output.
|
|
47
58
|
def xml_to_json(xml_str, omit_empty = true)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
if NOKOGIRI_AVAILABLE
|
|
60
|
+
doc = Nokogiri::XML(xml_str) { |cfg| cfg.strict.nonet }
|
|
61
|
+
root = locate_root(doc)
|
|
62
|
+
raise ArgumentError, 'Document missing <scxml> root element' unless root
|
|
63
|
+
|
|
64
|
+
map = element_to_hash(root)
|
|
65
|
+
attach_root_sibling_comments(doc, root, map)
|
|
66
|
+
collapse_whitespace(map)
|
|
67
|
+
remove_empty(map) if omit_empty
|
|
68
|
+
return JSON.pretty_generate(map)
|
|
69
|
+
end
|
|
70
|
+
# Fallback: use Python CLI converter when Nokogiri is unavailable.
|
|
71
|
+
begin
|
|
72
|
+
require 'tmpdir'
|
|
73
|
+
Dir.mktmpdir('scjson-rb-xml2json') do |dir|
|
|
74
|
+
in_path = File.join(dir, 'in.scxml')
|
|
75
|
+
out_path = File.join(dir, 'out.scjson')
|
|
76
|
+
File.write(in_path, xml_str)
|
|
77
|
+
py_candidates = [ENV['PYTHON'], 'python3', 'python'].compact.uniq
|
|
78
|
+
ok = false
|
|
79
|
+
py_candidates.each do |py|
|
|
80
|
+
# Try package entrypoint; add repo-local 'py' to PYTHONPATH for import
|
|
81
|
+
repo_py = File.expand_path('../../py', __dir__)
|
|
82
|
+
env = {}
|
|
83
|
+
current_pp = ENV['PYTHONPATH']
|
|
84
|
+
env['PYTHONPATH'] = current_pp ? (repo_py + File::PATH_SEPARATOR + current_pp) : repo_py
|
|
85
|
+
cmd = [py, '-m', 'scjson.cli', 'json', in_path, '-o', out_path]
|
|
86
|
+
ok = system(env, *cmd, out: File::NULL, err: File::NULL) && File.file?(out_path)
|
|
87
|
+
break if ok
|
|
88
|
+
end
|
|
89
|
+
raise 'python converter failed' unless ok
|
|
90
|
+
return File.read(out_path)
|
|
91
|
+
end
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
raise LoadError, "SCXML->JSON conversion unavailable: Nokogiri missing and external converter failed (#{e})"
|
|
94
|
+
end
|
|
56
95
|
end
|
|
57
96
|
|
|
58
97
|
##
|
|
@@ -61,13 +100,40 @@ module Scjson
|
|
|
61
100
|
# @param [String] json_str Canonical scjson input.
|
|
62
101
|
# @return [String] XML document encoded as UTF-8.
|
|
63
102
|
def json_to_xml(json_str)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
103
|
+
if NOKOGIRI_AVAILABLE
|
|
104
|
+
data = JSON.parse(json_str)
|
|
105
|
+
remove_empty(data)
|
|
106
|
+
doc = Nokogiri::XML::Document.new
|
|
107
|
+
doc.encoding = 'utf-8'
|
|
108
|
+
root = build_element(doc, 'scxml', data)
|
|
109
|
+
doc.root = root
|
|
110
|
+
add_preceding_help_text_comments(doc, root, data)
|
|
111
|
+
return doc.to_xml
|
|
112
|
+
end
|
|
113
|
+
# Fallback: use Python CLI converter when Nokogiri is unavailable.
|
|
114
|
+
begin
|
|
115
|
+
require 'tmpdir'
|
|
116
|
+
Dir.mktmpdir('scjson-rb-json2xml') do |dir|
|
|
117
|
+
in_path = File.join(dir, 'in.scjson')
|
|
118
|
+
out_path = File.join(dir, 'out.scxml')
|
|
119
|
+
File.write(in_path, json_str)
|
|
120
|
+
py_candidates = [ENV['PYTHON'], 'python3', 'python'].compact.uniq
|
|
121
|
+
ok = false
|
|
122
|
+
py_candidates.each do |py|
|
|
123
|
+
repo_py = File.expand_path('../../py', __dir__)
|
|
124
|
+
env = {}
|
|
125
|
+
current_pp = ENV['PYTHONPATH']
|
|
126
|
+
env['PYTHONPATH'] = current_pp ? (repo_py + File::PATH_SEPARATOR + current_pp) : repo_py
|
|
127
|
+
cmd = [py, '-m', 'scjson.cli', 'xml', in_path, '-o', out_path]
|
|
128
|
+
ok = system(env, *cmd, out: File::NULL, err: File::NULL) && File.file?(out_path)
|
|
129
|
+
break if ok
|
|
130
|
+
end
|
|
131
|
+
raise 'python converter failed' unless ok
|
|
132
|
+
return File.read(out_path)
|
|
133
|
+
end
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
raise LoadError, "JSON->SCXML conversion unavailable: Nokogiri missing and external converter failed (#{e})"
|
|
136
|
+
end
|
|
71
137
|
end
|
|
72
138
|
|
|
73
139
|
# ----------------------------
|
|
@@ -84,6 +150,36 @@ module Scjson
|
|
|
84
150
|
end
|
|
85
151
|
private_class_method :local_name
|
|
86
152
|
|
|
153
|
+
def scxml_element?(node)
|
|
154
|
+
ns = node.namespace&.href
|
|
155
|
+
SCXML_ELEMENTS.include?(local_name(node)) && (ns.nil? || ns.empty? || ns == XMLNS)
|
|
156
|
+
end
|
|
157
|
+
private_class_method :scxml_element?
|
|
158
|
+
|
|
159
|
+
def extension_element?(node)
|
|
160
|
+
ns = node.namespace&.href
|
|
161
|
+
!ns.nil? && !ns.empty? && ns != XMLNS
|
|
162
|
+
end
|
|
163
|
+
private_class_method :extension_element?
|
|
164
|
+
|
|
165
|
+
def comment_node?(node)
|
|
166
|
+
node.respond_to?(:comment?) && node.comment?
|
|
167
|
+
end
|
|
168
|
+
private_class_method :comment_node?
|
|
169
|
+
|
|
170
|
+
def processing_instruction_node?(node)
|
|
171
|
+
node.respond_to?(:processing_instruction?) && node.processing_instruction?
|
|
172
|
+
end
|
|
173
|
+
private_class_method :processing_instruction_node?
|
|
174
|
+
|
|
175
|
+
def clark_name(node)
|
|
176
|
+
ns = node.namespace&.href
|
|
177
|
+
return node.name if ns.nil? || ns.empty?
|
|
178
|
+
|
|
179
|
+
"{#{ns}}#{local_name(node)}"
|
|
180
|
+
end
|
|
181
|
+
private_class_method :clark_name
|
|
182
|
+
|
|
87
183
|
def append_child(hash, key, value)
|
|
88
184
|
if hash.key?(key)
|
|
89
185
|
existing = hash[key]
|
|
@@ -104,8 +200,81 @@ module Scjson
|
|
|
104
200
|
end
|
|
105
201
|
private_class_method :wrap_list
|
|
106
202
|
|
|
203
|
+
def repair_comment_text(raw)
|
|
204
|
+
text = raw.to_s
|
|
205
|
+
return text.strip unless text.include?("\n")
|
|
206
|
+
|
|
207
|
+
lines = text.lines.map { |line| line.chomp("\n") }
|
|
208
|
+
lines.shift while !lines.empty? && lines.first.strip.empty?
|
|
209
|
+
lines.pop while !lines.empty? && lines.last.strip.empty?
|
|
210
|
+
return '' if lines.empty?
|
|
211
|
+
|
|
212
|
+
indents = lines.map do |line|
|
|
213
|
+
next if line.strip.empty?
|
|
214
|
+
|
|
215
|
+
line.length - line.sub(/\A[ \t]+/, '').length
|
|
216
|
+
end.compact
|
|
217
|
+
common = indents.empty? ? 0 : indents.min
|
|
218
|
+
lines = lines.map { |line| line.length >= common ? line[common, line.length] : line } if common.positive?
|
|
219
|
+
lines.join("\n")
|
|
220
|
+
end
|
|
221
|
+
private_class_method :repair_comment_text
|
|
222
|
+
|
|
223
|
+
def emit_safe_comment_text(text)
|
|
224
|
+
safe = text.to_s.gsub('--', '- -')
|
|
225
|
+
safe = "#{safe} " if safe.end_with?('-')
|
|
226
|
+
safe
|
|
227
|
+
end
|
|
228
|
+
private_class_method :emit_safe_comment_text
|
|
229
|
+
|
|
230
|
+
def append_help_text(map, comments, prepend: false)
|
|
231
|
+
repaired = comments.map(&:to_s)
|
|
232
|
+
return if repaired.empty?
|
|
233
|
+
|
|
234
|
+
existing = map['help_text']
|
|
235
|
+
if existing
|
|
236
|
+
existing = wrap_list(existing)
|
|
237
|
+
map['help_text'] = prepend ? repaired + existing : existing + repaired
|
|
238
|
+
else
|
|
239
|
+
map['help_text'] = repaired
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
private_class_method :append_help_text
|
|
243
|
+
|
|
244
|
+
def add_preceding_help_text_comments(doc, element, map)
|
|
245
|
+
return unless map.is_a?(Hash)
|
|
246
|
+
|
|
247
|
+
wrap_list(map['help_text']).each do |text|
|
|
248
|
+
element.add_previous_sibling(Nokogiri::XML::Comment.new(doc, emit_safe_comment_text(text)))
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
private_class_method :add_preceding_help_text_comments
|
|
252
|
+
|
|
253
|
+
def add_child_element(doc, parent, child_name, child_map)
|
|
254
|
+
child = build_element(doc, child_name, child_map)
|
|
255
|
+
if child_map.is_a?(Hash)
|
|
256
|
+
wrap_list(child_map['help_text']).each do |text|
|
|
257
|
+
parent.add_child(Nokogiri::XML::Comment.new(doc, emit_safe_comment_text(text)))
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
parent.add_child(child)
|
|
261
|
+
end
|
|
262
|
+
private_class_method :add_child_element
|
|
263
|
+
|
|
264
|
+
def attach_root_sibling_comments(doc, root, map)
|
|
265
|
+
comments = []
|
|
266
|
+
doc.children.each do |child|
|
|
267
|
+
next if child.equal?(root)
|
|
268
|
+
next unless comment_node?(child)
|
|
269
|
+
|
|
270
|
+
comments << repair_comment_text(child.text || '')
|
|
271
|
+
end
|
|
272
|
+
append_help_text(map, comments) unless comments.empty?
|
|
273
|
+
end
|
|
274
|
+
private_class_method :attach_root_sibling_comments
|
|
275
|
+
|
|
107
276
|
def any_element_to_hash(node)
|
|
108
|
-
result = { 'qname' => node
|
|
277
|
+
result = { 'qname' => clark_name(node) }
|
|
109
278
|
text = node.text
|
|
110
279
|
result['text'] = text.to_s if text
|
|
111
280
|
unless node.attribute_nodes.empty?
|
|
@@ -123,9 +292,11 @@ module Scjson
|
|
|
123
292
|
end
|
|
124
293
|
private_class_method :any_element_to_hash
|
|
125
294
|
|
|
126
|
-
def element_to_hash(node)
|
|
295
|
+
def element_to_hash(node, inside_source_body = false, inside_extension = false)
|
|
127
296
|
map = {}
|
|
128
297
|
local = local_name(node)
|
|
298
|
+
elem_in_source = inside_source_body || SOURCE_BODY_TAGS.include?(local)
|
|
299
|
+
elem_in_extension = inside_extension || (!SCXML_ELEMENTS.include?(local) && local != 'scxml')
|
|
129
300
|
|
|
130
301
|
node.attribute_nodes.each do |attr|
|
|
131
302
|
name = local_name(attr)
|
|
@@ -179,17 +350,32 @@ module Scjson
|
|
|
179
350
|
end
|
|
180
351
|
|
|
181
352
|
text_items = []
|
|
353
|
+
pending_comments = []
|
|
182
354
|
node.children.each do |child|
|
|
183
|
-
if child
|
|
355
|
+
if comment_node?(child)
|
|
356
|
+
pending_comments << repair_comment_text(child.text || '')
|
|
357
|
+
elsif processing_instruction_node?(child)
|
|
358
|
+
next
|
|
359
|
+
elsif child.element?
|
|
184
360
|
child_local = local_name(child)
|
|
185
|
-
if
|
|
361
|
+
if scxml_element?(child)
|
|
186
362
|
key = case child_local
|
|
187
363
|
when 'if' then 'if_value'
|
|
188
364
|
when 'else' then 'else_value'
|
|
189
365
|
when 'raise' then 'raise_value'
|
|
190
366
|
else child_local
|
|
191
367
|
end
|
|
192
|
-
child_map = element_to_hash(child)
|
|
368
|
+
child_map = element_to_hash(child, elem_in_source || local == 'content', elem_in_extension)
|
|
369
|
+
target_eligible = SCXML_ELEMENTS.include?(child_local) && !elem_in_source && !elem_in_extension
|
|
370
|
+
if !pending_comments.empty? && local == 'content' && child_local == 'scxml'
|
|
371
|
+
# Comments inside an inline <content> payload are payload-local,
|
|
372
|
+
# not authoring metadata for the nested machine.
|
|
373
|
+
elsif !pending_comments.empty? && target_eligible
|
|
374
|
+
append_help_text(child_map, pending_comments, prepend: true)
|
|
375
|
+
elsif !pending_comments.empty? && SCXML_ELEMENTS.include?(local) && !elem_in_source && !elem_in_extension
|
|
376
|
+
append_help_text(map, pending_comments)
|
|
377
|
+
end
|
|
378
|
+
pending_comments = []
|
|
193
379
|
target_key = if child_local == 'scxml' && local != 'scxml'
|
|
194
380
|
'content'
|
|
195
381
|
elsif local == 'content' && child_local == 'scxml'
|
|
@@ -203,14 +389,27 @@ module Scjson
|
|
|
203
389
|
append_child(map, target_key, child_map)
|
|
204
390
|
end
|
|
205
391
|
else
|
|
206
|
-
|
|
392
|
+
if !pending_comments.empty? && SCXML_ELEMENTS.include?(local) && !elem_in_source && !elem_in_extension
|
|
393
|
+
append_help_text(map, pending_comments)
|
|
394
|
+
end
|
|
395
|
+
pending_comments = []
|
|
396
|
+
target_key = extension_element?(child) ? 'other_element' : 'content'
|
|
397
|
+
append_child(map, target_key, any_element_to_hash(child))
|
|
207
398
|
end
|
|
208
399
|
elsif child.text?
|
|
209
400
|
value = child.text
|
|
401
|
+
if value && !value.strip.empty? && !pending_comments.empty?
|
|
402
|
+
append_help_text(map, pending_comments) if SCXML_ELEMENTS.include?(local) && !elem_in_source && !elem_in_extension
|
|
403
|
+
pending_comments = []
|
|
404
|
+
end
|
|
210
405
|
text_items << value if value && !value.strip.empty?
|
|
211
406
|
end
|
|
212
407
|
end
|
|
213
408
|
|
|
409
|
+
if !pending_comments.empty? && SCXML_ELEMENTS.include?(local) && !elem_in_source && !elem_in_extension
|
|
410
|
+
append_help_text(map, pending_comments)
|
|
411
|
+
end
|
|
412
|
+
|
|
214
413
|
text_items.each { |text| append_child(map, 'content', text) }
|
|
215
414
|
|
|
216
415
|
if local == 'scxml'
|
|
@@ -318,6 +517,11 @@ module Scjson
|
|
|
318
517
|
raise ArgumentError, 'Expected object for element construction' unless map.is_a?(Hash)
|
|
319
518
|
|
|
320
519
|
element_name = map['qname'] || name
|
|
520
|
+
attrs = map['attributes'].is_a?(Hash) ? map['attributes'].dup : {}
|
|
521
|
+
if element_name == XINCLUDE_CLARK_INCLUDE
|
|
522
|
+
element_name = 'xi:include'
|
|
523
|
+
attrs['xmlns:xi'] ||= XINCLUDE_NS
|
|
524
|
+
end
|
|
321
525
|
element = Nokogiri::XML::Element.new(element_name, doc)
|
|
322
526
|
|
|
323
527
|
if name == 'scxml'
|
|
@@ -330,23 +534,28 @@ module Scjson
|
|
|
330
534
|
element.add_child(Nokogiri::XML::Text.new(map['text'], doc))
|
|
331
535
|
end
|
|
332
536
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
element[attr_name] = attr_value if attr_value
|
|
336
|
-
end
|
|
537
|
+
attrs.each do |attr_name, attr_value|
|
|
538
|
+
element[attr_name] = attr_value if attr_value
|
|
337
539
|
end
|
|
338
540
|
|
|
339
541
|
map.each do |key, value|
|
|
340
|
-
next if %w[qname text attributes].include?(key)
|
|
542
|
+
next if %w[qname text attributes help_text].include?(key)
|
|
341
543
|
|
|
342
544
|
case key
|
|
343
545
|
when 'content'
|
|
344
546
|
handle_content_nodes(doc, element, value, element_name)
|
|
547
|
+
when 'other_element'
|
|
548
|
+
wrap_list(value).each do |child_map|
|
|
549
|
+
next unless child_map.is_a?(Hash)
|
|
550
|
+
|
|
551
|
+
child_name = child_map['qname'] || 'content'
|
|
552
|
+
add_child_element(doc, element, child_name, child_map)
|
|
553
|
+
end
|
|
345
554
|
when 'children'
|
|
346
555
|
wrap_list(value).each do |child_map|
|
|
347
556
|
next unless child_map.is_a?(Hash)
|
|
348
557
|
child_name = child_map['qname'] || 'content'
|
|
349
|
-
|
|
558
|
+
add_child_element(doc, element, child_name, child_map)
|
|
350
559
|
end
|
|
351
560
|
when 'other_attributes'
|
|
352
561
|
next unless value.is_a?(Hash)
|
|
@@ -375,7 +584,7 @@ module Scjson
|
|
|
375
584
|
element['initial'] = joined
|
|
376
585
|
else
|
|
377
586
|
wrap_list(value).each do |child|
|
|
378
|
-
|
|
587
|
+
add_child_element(doc, element, 'initial', child)
|
|
379
588
|
end
|
|
380
589
|
next
|
|
381
590
|
end
|
|
@@ -391,12 +600,12 @@ module Scjson
|
|
|
391
600
|
|
|
392
601
|
if STRUCTURAL_FIELDS.include?(key) || %w[if_value else_value raise_value].include?(key)
|
|
393
602
|
wrap_list(value).each do |child|
|
|
394
|
-
|
|
603
|
+
add_child_element(doc, element, child_name, child)
|
|
395
604
|
end
|
|
396
605
|
elsif value.is_a?(Array) && value.all? { |item| !item.is_a?(Hash) }
|
|
397
606
|
element[key] = join_tokens(value)
|
|
398
607
|
elsif value.is_a?(Hash)
|
|
399
|
-
|
|
608
|
+
add_child_element(doc, element, child_name, value)
|
|
400
609
|
elsif !value.nil?
|
|
401
610
|
element[key] = value.to_s
|
|
402
611
|
end
|
|
@@ -425,26 +634,32 @@ module Scjson
|
|
|
425
634
|
|
|
426
635
|
next unless item.is_a?(Hash)
|
|
427
636
|
|
|
428
|
-
if parent_name == 'send' && item.keys == ['content']
|
|
637
|
+
if parent_name == 'send' && (item.keys - ['help_text']) == ['content']
|
|
638
|
+
wrap_list(item['help_text']).each do |text|
|
|
639
|
+
element.add_child(Nokogiri::XML::Comment.new(doc, emit_safe_comment_text(text)))
|
|
640
|
+
end
|
|
429
641
|
wrap_list(item['content']).each do |inner|
|
|
430
642
|
content_element = Nokogiri::XML::Element.new('content', doc)
|
|
431
643
|
if inner.is_a?(String)
|
|
432
644
|
content_element.add_child(Nokogiri::XML::Text.new(inner, doc))
|
|
433
645
|
elsif inner.is_a?(Hash)
|
|
434
|
-
|
|
646
|
+
add_child_element(doc, content_element, 'content', inner)
|
|
435
647
|
end
|
|
436
648
|
element.add_child(content_element)
|
|
437
649
|
end
|
|
438
650
|
next
|
|
439
651
|
end
|
|
440
652
|
|
|
441
|
-
if parent_name == 'donedata' && item.keys == ['content']
|
|
653
|
+
if parent_name == 'donedata' && (item.keys - ['help_text']) == ['content']
|
|
654
|
+
wrap_list(item['help_text']).each do |text|
|
|
655
|
+
element.add_child(Nokogiri::XML::Comment.new(doc, emit_safe_comment_text(text)))
|
|
656
|
+
end
|
|
442
657
|
content_element = Nokogiri::XML::Element.new('content', doc)
|
|
443
658
|
wrap_list(item['content']).each do |inner|
|
|
444
659
|
if inner.is_a?(String)
|
|
445
660
|
content_element.add_child(Nokogiri::XML::Text.new(inner, doc))
|
|
446
661
|
elsif inner.is_a?(Hash)
|
|
447
|
-
|
|
662
|
+
add_child_element(doc, content_element, 'content', inner)
|
|
448
663
|
end
|
|
449
664
|
end
|
|
450
665
|
element.add_child(content_element)
|
|
@@ -452,8 +667,7 @@ module Scjson
|
|
|
452
667
|
end
|
|
453
668
|
|
|
454
669
|
if item.key?('qname')
|
|
455
|
-
|
|
456
|
-
element.add_child(child)
|
|
670
|
+
add_child_element(doc, element, item['qname'], item)
|
|
457
671
|
next
|
|
458
672
|
end
|
|
459
673
|
|
|
@@ -468,8 +682,7 @@ module Scjson
|
|
|
468
682
|
if parent_name == 'data' && child_name == 'content'
|
|
469
683
|
element.add_child(Nokogiri::XML::Text.new(item['content'].to_s, doc))
|
|
470
684
|
else
|
|
471
|
-
|
|
472
|
-
element.add_child(child_element)
|
|
685
|
+
add_child_element(doc, element, child_name, item)
|
|
473
686
|
end
|
|
474
687
|
end
|
|
475
688
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scjson
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Softoboros Technology Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-05-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -24,7 +24,9 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
-
description:
|
|
27
|
+
description: 'scjson: SCXML ↔ JSON converter, validator, and execution trace interface.
|
|
28
|
+
Provides CLI tools for conversion, validation, and emitting deterministic traces
|
|
29
|
+
compatible with SCION semantics.'
|
|
28
30
|
email:
|
|
29
31
|
- info@softoboros.com
|
|
30
32
|
executables:
|
|
@@ -32,14 +34,25 @@ executables:
|
|
|
32
34
|
extensions: []
|
|
33
35
|
extra_rdoc_files: []
|
|
34
36
|
files:
|
|
37
|
+
- LEGAL.md
|
|
38
|
+
- LICENSE
|
|
39
|
+
- README.md
|
|
35
40
|
- bin/scjson
|
|
36
41
|
- lib/scjson.rb
|
|
37
42
|
- lib/scjson/cli.rb
|
|
43
|
+
- lib/scjson/engine.rb
|
|
44
|
+
- lib/scjson/engine/context.rb
|
|
45
|
+
- lib/scjson/types.rb
|
|
38
46
|
- lib/scjson/version.rb
|
|
39
|
-
homepage:
|
|
47
|
+
homepage: https://github.com/SoftOboros/scjson
|
|
40
48
|
licenses:
|
|
41
49
|
- BSD-1-Clause
|
|
42
|
-
metadata:
|
|
50
|
+
metadata:
|
|
51
|
+
source_code_uri: https://github.com/SoftOboros/scjson
|
|
52
|
+
documentation_uri: https://github.com/SoftOboros/scjson/tree/main/docs
|
|
53
|
+
changelog_uri: https://github.com/SoftOboros/scjson/releases
|
|
54
|
+
homepage_uri: https://github.com/SoftOboros/scjson
|
|
55
|
+
keywords: scxml,statecharts,state-machine,scjson,scml,execution
|
|
43
56
|
post_install_message:
|
|
44
57
|
rdoc_options: []
|
|
45
58
|
require_paths:
|
|
@@ -58,5 +71,5 @@ requirements: []
|
|
|
58
71
|
rubygems_version: 3.4.19
|
|
59
72
|
signing_key:
|
|
60
73
|
specification_version: 4
|
|
61
|
-
summary: SCXML <-> scjson converter and validator
|
|
74
|
+
summary: SCXML/SCML execution, SCXML <-> scjson converter and validator
|
|
62
75
|
test_files: []
|