openehr 1.3.0 → 2.0.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/README.rdoc +93 -12
- data/lib/openehr/am/archetype/constraint_model/primitive.rb +204 -6
- data/lib/openehr/am/archetype/constraint_model.rb +228 -4
- data/lib/openehr/am/archetype/ontology.rb +2 -2
- data/lib/openehr/am/archetype.rb +101 -2
- data/lib/openehr/am/openehr_profile/data_types/basic.rb +28 -0
- data/lib/openehr/am/openehr_profile/data_types/quantity.rb +88 -0
- data/lib/openehr/am/openehr_profile/data_types/text.rb +23 -0
- data/lib/openehr/am/template.rb +25 -3
- data/lib/openehr/aql/engine/binding.rb +13 -0
- data/lib/openehr/aql/engine/contains_resolver.rb +161 -0
- data/lib/openehr/aql/engine/dataset.rb +122 -0
- data/lib/openehr/aql/engine/path_evaluator.rb +137 -0
- data/lib/openehr/aql/engine/predicate_evaluator.rb +65 -0
- data/lib/openehr/aql/engine.rb +119 -0
- data/lib/openehr/aql/errors.rb +27 -0
- data/lib/openehr/aql/lexer.rb +295 -0
- data/lib/openehr/aql/model/containment.rb +45 -0
- data/lib/openehr/aql/model/from_clause.rb +35 -0
- data/lib/openehr/aql/model/function_call.rb +56 -0
- data/lib/openehr/aql/model/identified_path.rb +20 -0
- data/lib/openehr/aql/model/literal.rb +15 -0
- data/lib/openehr/aql/model/object_path.rb +34 -0
- data/lib/openehr/aql/model/order_by_and_limit.rb +50 -0
- data/lib/openehr/aql/model/predicate.rb +84 -0
- data/lib/openehr/aql/model/query.rb +24 -0
- data/lib/openehr/aql/model/select_clause.rb +29 -0
- data/lib/openehr/aql/model/where_clause.rb +105 -0
- data/lib/openehr/aql/model.rb +14 -0
- data/lib/openehr/aql/parser.rb +484 -0
- data/lib/openehr/aql/result_set.rb +54 -0
- data/lib/openehr/aql.rb +28 -0
- data/lib/openehr/assumed_library_types.rb +68 -17
- data/lib/openehr/parser/adl_grammar.tt +29 -13
- data/lib/openehr/parser/adl_parser.rb +18 -4
- data/lib/openehr/parser/archetype_validator.rb +126 -0
- data/lib/openehr/parser/exception.rb +13 -0
- data/lib/openehr/parser/opt_parser.rb +162 -9
- data/lib/openehr/parser.rb +2 -0
- data/lib/openehr/path.rb +195 -0
- data/lib/openehr/rm/common/archetyped.rb +116 -6
- data/lib/openehr/rm/common/change_control.rb +3 -8
- data/lib/openehr/rm/common/directory.rb +4 -0
- data/lib/openehr/rm/common/generic.rb +24 -10
- data/lib/openehr/rm/composition/content/entry.rb +24 -5
- data/lib/openehr/rm/composition/content/navigation.rb +2 -1
- data/lib/openehr/rm/composition.rb +19 -2
- data/lib/openehr/rm/data_structures/history.rb +3 -0
- data/lib/openehr/rm/data_structures/item_structure/representation.rb +13 -9
- data/lib/openehr/rm/data_structures/item_structure.rb +28 -15
- data/lib/openehr/rm/data_types/encapsulated.rb +23 -3
- data/lib/openehr/rm/data_types/quantity/date_time.rb +9 -1
- data/lib/openehr/rm/data_types/quantity.rb +66 -14
- data/lib/openehr/rm/data_types/text.rb +8 -0
- data/lib/openehr/rm/data_types/time_specification.rb +5 -4
- data/lib/openehr/rm/demographic.rb +4 -3
- data/lib/openehr/rm/ehr.rb +20 -1
- data/lib/openehr/rm/factory.rb +234 -6
- data/lib/openehr/rm/integration.rb +1 -0
- data/lib/openehr/rm/support/identification.rb +37 -8
- data/lib/openehr/rm/support/measurement.rb +32 -0
- data/lib/openehr/rm/type_name.rb +90 -0
- data/lib/openehr/rm.rb +3 -0
- data/lib/openehr/serializer/adl_serializer.rb +335 -0
- data/lib/openehr/serializer/base.rb +20 -0
- data/lib/openehr/serializer/opt_serializer.rb +49 -0
- data/lib/openehr/serializer/rm_json_serializer.rb +62 -0
- data/lib/openehr/serializer/xml_serializer.rb +260 -0
- data/lib/openehr/serializer.rb +5 -291
- data/lib/openehr/terminology_service.rb +44 -0
- data/lib/openehr/version.rb +1 -1
- data/lib/openehr.rb +5 -2
- metadata +35 -7
- data/lib/openehr/parser/validator.rb +0 -18
- data/lib/openehr/parser/xml_parser.rb +0 -13
- data/lib/openehr/rm/data_types/charset_extract.rb +0 -24
- data/lib/openehr/writer.rb +0 -12
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module OpenEHR
|
|
4
|
+
module Serializer
|
|
5
|
+
class ADLSerializer < BaseSerializer
|
|
6
|
+
def header
|
|
7
|
+
hd = 'archetype'
|
|
8
|
+
unless @archetype.adl_version.nil?
|
|
9
|
+
hd << " (adl_version = #{@archetype.adl_version})"
|
|
10
|
+
end
|
|
11
|
+
hd << NL+INDENT + "#{@archetype.archetype_id.value}"+NL*2
|
|
12
|
+
hd << 'concept'+NL+ INDENT+"[#{@archetype.concept}]"+NL
|
|
13
|
+
hd << NL+'language'+NL+INDENT+'original_language = <['+
|
|
14
|
+
@archetype.original_language.terminology_id.value+'::'+
|
|
15
|
+
@archetype.original_language.code_string+']>'+NL
|
|
16
|
+
return hd
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description
|
|
20
|
+
desc = ''
|
|
21
|
+
if @archetype.description
|
|
22
|
+
ad = @archetype.description
|
|
23
|
+
desc << 'description' + NL
|
|
24
|
+
desc << INDENT + 'original_author = <' + NL
|
|
25
|
+
ad.original_author.each do |k,v|
|
|
26
|
+
desc << INDENT+INDENT+'["'+k+'"] = <"'+v+'">'+NL
|
|
27
|
+
end
|
|
28
|
+
desc << INDENT+'>'+NL
|
|
29
|
+
desc << INDENT+'lifecycle_state = <"'+ad.lifecycle_state+'">'+NL
|
|
30
|
+
desc << INDENT+'details = <'+NL
|
|
31
|
+
ad.details.each do |lang,item|
|
|
32
|
+
desc << INDENT*2+'["'+lang+'"] = <'+NL
|
|
33
|
+
desc << INDENT*3+'language = <['+
|
|
34
|
+
item.language.terminology_id.value+'::'+
|
|
35
|
+
item.language.code_string+']>'+NL
|
|
36
|
+
desc << INDENT*3+'purpose = <"'+item.purpose+'">'+NL
|
|
37
|
+
if item.keywords then
|
|
38
|
+
desc << INDENT*3+'keywords = <'
|
|
39
|
+
item.keywords.each do |word|
|
|
40
|
+
desc << '"'+word+'",'
|
|
41
|
+
end
|
|
42
|
+
desc.chop! << '>'+NL
|
|
43
|
+
end
|
|
44
|
+
desc << INDENT*3+'use = <"'+item.use+'">'+NL if item.use
|
|
45
|
+
desc << INDENT*3+'misuse = <"'+item.misuse+'">'+NL if item.misuse
|
|
46
|
+
desc << INDENT*3+'copyright = <"'+item.copyright+'">'+NL if item.copyright
|
|
47
|
+
if item.original_resource_uri
|
|
48
|
+
desc << INDENT*3 + 'original_resource_uri = <'
|
|
49
|
+
item.original_resource_uri.each do |k,v|
|
|
50
|
+
desc << INDENT*4+'["'+k+'"] = <"'+v+'">'+NL
|
|
51
|
+
end
|
|
52
|
+
desc << INDENT*3+'>'+NL
|
|
53
|
+
end
|
|
54
|
+
if item.other_details
|
|
55
|
+
desc << INDENT*3 + 'other_details = <'
|
|
56
|
+
item.other_details.each do |k,v|
|
|
57
|
+
desc << INDENT*4+'["'+k+'"] = <"'+v+'">'+NL
|
|
58
|
+
end
|
|
59
|
+
desc << INDENT*3+'>'+NL
|
|
60
|
+
end
|
|
61
|
+
desc << INDENT*2+'>'+NL
|
|
62
|
+
end
|
|
63
|
+
desc << INDENT+'>'+NL
|
|
64
|
+
end
|
|
65
|
+
return desc
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def definition
|
|
69
|
+
'definition' + NL + emit_c_object(@archetype.definition, 1)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ontology
|
|
73
|
+
ao = @archetype.ontology
|
|
74
|
+
ontology = 'ontology'+NL
|
|
75
|
+
ontology << string_list_line('languages_available', ao.languages_available)
|
|
76
|
+
ontology << string_list_line('terminologies_available', ao.terminologies_available)
|
|
77
|
+
ontology << INDENT + 'term_definitions = <' + NL
|
|
78
|
+
ao.term_definitions.each do |lang, items|
|
|
79
|
+
ontology << INDENT*2 + "[\"#{lang}\"] = <" + NL
|
|
80
|
+
ontology << INDENT*3 + 'items = <' + NL
|
|
81
|
+
items.each do |code, item|
|
|
82
|
+
ontology << INDENT*4 + "[\"#{code}\"] = <" + NL
|
|
83
|
+
item.items.each do |name, desc|
|
|
84
|
+
ontology << INDENT*5 + "#{name} = <\"#{desc}\">" +NL
|
|
85
|
+
end
|
|
86
|
+
ontology << INDENT*4 + '>'+NL
|
|
87
|
+
end
|
|
88
|
+
ontology << INDENT*3 + '>' + NL
|
|
89
|
+
ontology << INDENT*2 + '>' + NL
|
|
90
|
+
end
|
|
91
|
+
ontology << INDENT + '>' + NL
|
|
92
|
+
ontology << term_bindings_block(ao.term_bindings)
|
|
93
|
+
ontology
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def merge
|
|
97
|
+
return header + NL + description + NL + definition + NL + ontology
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
include OpenEHR::AM::Archetype::ConstraintModel
|
|
101
|
+
Primitive = OpenEHR::AM::Archetype::ConstraintModel::Primitive
|
|
102
|
+
OpenEHRProfile = OpenEHR::AM::OpenEHRProfile
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def string_list_line(keyword, values)
|
|
107
|
+
return '' if values.nil? || values.empty?
|
|
108
|
+
|
|
109
|
+
INDENT + "#{keyword} = <" + values.map { |v| "\"#{v}\"" }.join(', ') + '>' + NL
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def term_bindings_block(term_bindings)
|
|
113
|
+
return '' if term_bindings.nil? || term_bindings.empty?
|
|
114
|
+
|
|
115
|
+
block = INDENT + 'term_bindings = <' + NL
|
|
116
|
+
term_bindings.each do |terminology, codes|
|
|
117
|
+
block << INDENT*2 + "[\"#{terminology}\"] = <" + NL
|
|
118
|
+
block << INDENT*3 + 'items = <' + NL
|
|
119
|
+
codes.each do |code, bindings|
|
|
120
|
+
code_phrase = Array(bindings).first
|
|
121
|
+
block << INDENT*4 + "[\"#{code}\"] = <[#{code_phrase.terminology_id.value}::#{code_phrase.code_string}]>" + NL
|
|
122
|
+
end
|
|
123
|
+
block << INDENT*3 + '>' + NL
|
|
124
|
+
block << INDENT*2 + '>' + NL
|
|
125
|
+
end
|
|
126
|
+
block << INDENT + '>' + NL
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Recursive cADL emitter for a single C_OBJECT node, dispatching
|
|
130
|
+
# on its concrete class. Unsupported domain types (e.g.
|
|
131
|
+
# C_DV_QUANTITY's dADL block syntax, C_DV_STATE) deliberately
|
|
132
|
+
# raise rather than emit a guess at their syntax.
|
|
133
|
+
def emit_c_object(node, depth)
|
|
134
|
+
case node
|
|
135
|
+
when ArchetypeSlot
|
|
136
|
+
emit_archetype_slot(node, depth)
|
|
137
|
+
when ArchetypeInternalRef
|
|
138
|
+
emit_internal_ref(node, depth)
|
|
139
|
+
when ConstraintRef
|
|
140
|
+
emit_constraint_ref(node, depth)
|
|
141
|
+
when CComplexObject
|
|
142
|
+
emit_complex_object(node, depth)
|
|
143
|
+
else
|
|
144
|
+
raise ArgumentError, "ADLSerializer cannot emit a #{node.class} node"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def emit_complex_object(node, depth)
|
|
149
|
+
head = complex_object_head(node, depth)
|
|
150
|
+
if node.any_allowed?
|
|
151
|
+
head + ' matches {*}' + NL
|
|
152
|
+
else
|
|
153
|
+
body = node.attributes.map { |a| emit_c_attribute(a, depth + 1) }.join
|
|
154
|
+
head + ' matches {' + NL + body + INDENT*depth + '}' + NL
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def complex_object_head(node, depth)
|
|
159
|
+
head = INDENT*depth + node.rm_type_name
|
|
160
|
+
head += "[#{node.node_id}]" if node.node_id
|
|
161
|
+
head += " occurrences matches {#{interval_literal(node.occurrences)}}" if show_interval?(node.occurrences)
|
|
162
|
+
head
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# A single attribute whose sole child is an inline-only node
|
|
166
|
+
# (C_PRIMITIVE_OBJECT, CONSTRAINT_REF, C_CODE_PHRASE,
|
|
167
|
+
# C_DV_ORDINAL) is flattened - its "matches {...}" IS the
|
|
168
|
+
# attribute's own matches clause, with no nested TYPE wrapper,
|
|
169
|
+
# matching real ADL 1.4 usage (e.g. "value matches {True}", not
|
|
170
|
+
# "value matches { BOOLEAN matches {True} }"). Anything else
|
|
171
|
+
# (C_COMPLEX_OBJECT, ARCHETYPE_SLOT, ARCHETYPE_INTERNAL_REF, or
|
|
172
|
+
# more than one alternative) renders as nested block(s).
|
|
173
|
+
def emit_c_attribute(attribute, depth)
|
|
174
|
+
head = attribute_head(attribute, depth)
|
|
175
|
+
children = attribute.children || []
|
|
176
|
+
if children.size == 1 && inline?(children.first)
|
|
177
|
+
head + " matches {#{inline_body(children.first)}}" + NL
|
|
178
|
+
else
|
|
179
|
+
body = children.map { |c| emit_c_object(c, depth + 1) }.join
|
|
180
|
+
head + ' matches {' + NL + body + INDENT*depth + '}' + NL
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def attribute_head(attribute, depth)
|
|
185
|
+
head = INDENT*depth + attribute.rm_attribute_name
|
|
186
|
+
head += " existence matches {#{interval_literal(attribute.existence)}}" if show_interval?(attribute.existence)
|
|
187
|
+
head += " cardinality matches {#{cardinality_literal(attribute.cardinality)}}" if attribute.is_a?(CMultipleAttribute)
|
|
188
|
+
head
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def inline?(node)
|
|
192
|
+
node.is_a?(CPrimitiveObject) || node.is_a?(ConstraintRef) ||
|
|
193
|
+
node.is_a?(OpenEHRProfile::DataTypes::Quantity::CDvOrdinal) ||
|
|
194
|
+
node.is_a?(OpenEHRProfile::DataTypes::Text::CCodePhrase)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def inline_body(node)
|
|
198
|
+
case node
|
|
199
|
+
when CPrimitiveObject
|
|
200
|
+
primitive_body(node.item)
|
|
201
|
+
when ConstraintRef
|
|
202
|
+
"[#{node.reference}]"
|
|
203
|
+
when OpenEHRProfile::DataTypes::Quantity::CDvOrdinal
|
|
204
|
+
ordinal_body(node)
|
|
205
|
+
when OpenEHRProfile::DataTypes::Text::CCodePhrase
|
|
206
|
+
code_phrase_body(node)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def emit_archetype_slot(node, depth)
|
|
211
|
+
head = INDENT*depth + 'allow_archetype ' + node.rm_type_name
|
|
212
|
+
head += "[#{node.node_id}]" if node.node_id
|
|
213
|
+
head += " occurrences matches {#{interval_literal(node.occurrences)}}" if show_interval?(node.occurrences)
|
|
214
|
+
lines = [head + ' matches {' + NL]
|
|
215
|
+
lines << assertion_block(node.includes, 'include', depth + 1)
|
|
216
|
+
lines << assertion_block(node.excludes, 'exclude', depth + 1)
|
|
217
|
+
lines << INDENT*depth + '}' + NL
|
|
218
|
+
lines.join
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def assertion_block(assertions, keyword, depth)
|
|
222
|
+
return '' if assertions.nil?
|
|
223
|
+
|
|
224
|
+
lines = [INDENT*depth + keyword + NL]
|
|
225
|
+
assertions.each { |a| lines << INDENT*(depth+1) + a.string_expression + NL }
|
|
226
|
+
lines.join
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def emit_internal_ref(node, depth)
|
|
230
|
+
INDENT*depth + "use_node #{node.rm_type_name} #{node.target_path}" + NL
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def emit_constraint_ref(node, depth)
|
|
234
|
+
INDENT*depth + "[#{node.reference}]" + NL
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def primitive_body(item)
|
|
238
|
+
return boolean_body(item) if item.is_a?(Primitive::CBoolean)
|
|
239
|
+
|
|
240
|
+
bounded_primitive_body(item)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def boolean_body(item)
|
|
244
|
+
values = []
|
|
245
|
+
values << 'True' if item.true_valid
|
|
246
|
+
values << 'False' if item.false_valid
|
|
247
|
+
with_assumed_value(values.join(', '), item)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def bounded_primitive_body(item)
|
|
251
|
+
body =
|
|
252
|
+
if item.respond_to?(:pattern) && item.pattern
|
|
253
|
+
item.pattern.to_s
|
|
254
|
+
elsif item.list
|
|
255
|
+
item.list.map { |v| primitive_literal(v) }.join(', ')
|
|
256
|
+
elsif item.range
|
|
257
|
+
"|#{range_literal(item.range)}|"
|
|
258
|
+
else
|
|
259
|
+
return '*'
|
|
260
|
+
end
|
|
261
|
+
with_assumed_value(body, item)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def with_assumed_value(body, item)
|
|
265
|
+
return body unless item.has_assumed_value?
|
|
266
|
+
|
|
267
|
+
"#{body}; #{primitive_literal(item.assumed_value)}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def primitive_literal(value)
|
|
271
|
+
case value
|
|
272
|
+
when String then "\"#{value}\""
|
|
273
|
+
when true then 'True'
|
|
274
|
+
when false then 'False'
|
|
275
|
+
when Numeric then value.to_s
|
|
276
|
+
else
|
|
277
|
+
value.respond_to?(:value) ? value.value.to_s : value.to_s
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def bound_literal(bound)
|
|
282
|
+
return nil if bound.nil?
|
|
283
|
+
|
|
284
|
+
bound.respond_to?(:value) ? bound.value.to_s : bound.to_s
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def range_literal(range)
|
|
288
|
+
lo = bound_literal(range.lower)
|
|
289
|
+
up = bound_literal(range.upper)
|
|
290
|
+
if lo && up
|
|
291
|
+
lo == up ? lo : "#{lo}..#{up}"
|
|
292
|
+
elsif lo
|
|
293
|
+
"#{range.lower_included? ? '>=' : '>'}#{lo}"
|
|
294
|
+
elsif up
|
|
295
|
+
"#{range.upper_included? ? '<=' : '<'}#{up}"
|
|
296
|
+
else
|
|
297
|
+
'*'
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def ordinal_body(node)
|
|
302
|
+
return 'C_DV_ORDINAL < >' if node.any_allowed?
|
|
303
|
+
|
|
304
|
+
body = node.list.map { |o| "#{o.value}|[#{o.symbol.code_string}]" }.join(', ')
|
|
305
|
+
with_assumed_value(body, node)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def code_phrase_body(node)
|
|
309
|
+
return '*' if node.any_allowed?
|
|
310
|
+
|
|
311
|
+
"[#{node.terminology_id.value}::#{node.code_list.join(', ')}]"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def show_interval?(interval)
|
|
315
|
+
return false if interval.nil?
|
|
316
|
+
|
|
317
|
+
!(interval.lower == 1 && interval.upper == 1)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def interval_literal(interval)
|
|
321
|
+
lo = interval.lower_unbounded? ? '0' : interval.lower.to_s
|
|
322
|
+
up = interval.upper_unbounded? ? '*' : interval.upper.to_s
|
|
323
|
+
"#{lo}..#{up}"
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def cardinality_literal(cardinality)
|
|
327
|
+
return '0..*; unordered' if cardinality.nil?
|
|
328
|
+
|
|
329
|
+
flags = [cardinality.is_ordered? ? 'ordered' : 'unordered']
|
|
330
|
+
flags << 'unique' if cardinality.is_unique?
|
|
331
|
+
"#{interval_literal(cardinality.interval)}; #{flags.join('; ')}"
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module OpenEHR
|
|
2
|
+
module Serializer
|
|
3
|
+
NL = "\r\n"
|
|
4
|
+
INDENT = ' '
|
|
5
|
+
|
|
6
|
+
class BaseSerializer
|
|
7
|
+
def initialize(archetype)
|
|
8
|
+
@archetype = archetype
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def serialize
|
|
12
|
+
return self.merge
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
def merge
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require_relative 'base'
|
|
3
|
+
|
|
4
|
+
module OpenEHR
|
|
5
|
+
module Serializer
|
|
6
|
+
class OPTSerializer < BaseSerializer
|
|
7
|
+
def initialize(opt, format:)
|
|
8
|
+
@opt = OpenEHR::Parser::OPTParser.new(opt).parse
|
|
9
|
+
@format = format
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
@opt.definition.archetype_id.concept_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Template-level metadata, as JSON. An operational template
|
|
17
|
+
# carries constraints, not instance data - it has no composer or
|
|
18
|
+
# territory to report - so this is the concept/ids/language the
|
|
19
|
+
# OPT actually captures, rather than a simulated RM COMPOSITION.
|
|
20
|
+
def header
|
|
21
|
+
JSON.generate(
|
|
22
|
+
'concept' => @opt.concept,
|
|
23
|
+
'archetype_id' => @opt.definition.archetype_id.value,
|
|
24
|
+
'template_id' => @opt.template_id.value,
|
|
25
|
+
'language' => @opt.original_language.code_string
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The root archetype's context/content constraint subtrees (what
|
|
30
|
+
# the template actually constrains for those attributes), as
|
|
31
|
+
# JSON via JSONSerializer - the same generic walker
|
|
32
|
+
# RMJSONSerializer uses for RM instances.
|
|
33
|
+
def context
|
|
34
|
+
attribute_json('context')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def content
|
|
38
|
+
attribute_json('content')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def attribute_json(rm_attribute_name)
|
|
44
|
+
attribute = @opt.definition.attributes.find { |a| a.rm_attribute_name == rm_attribute_name }
|
|
45
|
+
JSONSerializer.new(attribute).serialize
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
module OpenEHR
|
|
5
|
+
module Serializer
|
|
6
|
+
# Canonical JSON for an RM instance graph: a generic reflection
|
|
7
|
+
# walker (not a hand-written serializer per RM class), tagging
|
|
8
|
+
# each object with a "_type" discriminator (via
|
|
9
|
+
# OpenEHR::RM.type_name_of) and recursing into every non-nil
|
|
10
|
+
# instance variable except @parent (PATHABLE's back-reference,
|
|
11
|
+
# which would otherwise make every subtree cyclic). A defensive
|
|
12
|
+
# visited-object guard covers any other unexpected back-reference.
|
|
13
|
+
class RMJSONSerializer
|
|
14
|
+
EXCLUDED_IVARS = [:@parent].freeze
|
|
15
|
+
|
|
16
|
+
def initialize(rm_instance)
|
|
17
|
+
@rm_instance = rm_instance
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serialize
|
|
21
|
+
JSON.generate(to_value(@rm_instance, Set.new.compare_by_identity))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def to_value(value, seen)
|
|
27
|
+
case value
|
|
28
|
+
when nil, true, false, Numeric, String
|
|
29
|
+
value
|
|
30
|
+
when Array
|
|
31
|
+
value.map { |v| to_value(v, seen) }
|
|
32
|
+
when Hash
|
|
33
|
+
value.each_with_object({}) { |(k, v), h| h[k.to_s] = to_value(v, seen) }
|
|
34
|
+
else
|
|
35
|
+
object_value(value, seen)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def object_value(value, seen)
|
|
40
|
+
return nil if seen.include?(value)
|
|
41
|
+
|
|
42
|
+
seen << value
|
|
43
|
+
hash = {'_type' => OpenEHR::RM.type_name_of(value)}
|
|
44
|
+
(value.instance_variables - EXCLUDED_IVARS).each do |ivar|
|
|
45
|
+
field = value.instance_variable_get(ivar)
|
|
46
|
+
next if field.nil?
|
|
47
|
+
|
|
48
|
+
hash[ivar.to_s.delete_prefix('@')] = to_value(field, seen)
|
|
49
|
+
end
|
|
50
|
+
hash
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# RMJSONSerializer's reflection walker has no RM-specific logic
|
|
55
|
+
# (it derives "_type" from each object's own class and excludes
|
|
56
|
+
# only the PATHABLE/ArchetypeConstraint @parent back-reference),
|
|
57
|
+
# so the same class already serializes AOM constraint trees
|
|
58
|
+
# (CComplexObject, CAttribute, ...) correctly. Exposed under this
|
|
59
|
+
# name for that use.
|
|
60
|
+
JSONSerializer = RMJSONSerializer
|
|
61
|
+
end
|
|
62
|
+
end
|