openehr 2.1.0 → 2.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.
- checksums.yaml +4 -4
- data/README.rdoc +19 -6
- data/lib/openehr/am/openehr_profile/data_types/text.rb +21 -0
- data/lib/openehr/aql/engine/contains_resolver.rb +98 -7
- data/lib/openehr/aql/engine/dataset.rb +3 -1
- data/lib/openehr/aql/engine/predicate_evaluator.rb +70 -3
- data/lib/openehr/aql/engine.rb +45 -12
- data/lib/openehr/parser/opt_parser.rb +12 -425
- data/lib/openehr/parser/xml_archetype_parser.rb +255 -0
- data/lib/openehr/parser/xml_constraint_parsing.rb +275 -0
- data/lib/openehr/parser/xml_domain_type_parsing.rb +142 -0
- data/lib/openehr/parser/xml_primitive_parsing.rb +145 -0
- data/lib/openehr/parser.rb +1 -0
- data/lib/openehr/rm/common/generic.rb +4 -7
- data/lib/openehr/rm/composition/content/entry.rb +4 -1
- data/lib/openehr/rm/data_structures/item_structure/representation.rb +12 -2
- data/lib/openehr/rm/data_types/basic.rb +8 -9
- data/lib/openehr/rm/data_types/encapsulated.rb +16 -0
- data/lib/openehr/rm/data_types/quantity/date_time.rb +168 -0
- data/lib/openehr/rm/data_types/quantity.rb +64 -9
- data/lib/openehr/rm/ehr.rb +6 -6
- data/lib/openehr/rm/factory.rb +4 -0
- data/lib/openehr/serializer/adl_serializer.rb +151 -14
- data/lib/openehr/serializer/xml_serializer.rb +345 -99
- data/lib/openehr/version.rb +1 -1
- metadata +6 -2
|
@@ -7,14 +7,14 @@ module OpenEHR
|
|
|
7
7
|
class ADLSerializer < BaseSerializer
|
|
8
8
|
def header
|
|
9
9
|
hd = +'archetype'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
hd << (NL+INDENT
|
|
14
|
-
hd << ('concept'+NL+ INDENT+"[#{@archetype.concept}]"+NL)
|
|
10
|
+
hd << archetype_meta_data_clause
|
|
11
|
+
hd << (NL+INDENT + "#{@archetype.archetype_id.value}"+NL)
|
|
12
|
+
hd << specialise_section
|
|
13
|
+
hd << (NL+'concept'+NL+ INDENT+"[#{@archetype.concept}]"+NL)
|
|
15
14
|
hd << (NL+'language'+NL+INDENT+'original_language = <['+
|
|
16
15
|
@archetype.original_language.terminology_id.value+'::'+
|
|
17
16
|
@archetype.original_language.code_string+']>'+NL)
|
|
17
|
+
hd << translations_block(@archetype.translations)
|
|
18
18
|
return hd
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -45,15 +45,18 @@ module OpenEHR
|
|
|
45
45
|
def ontology
|
|
46
46
|
ao = @archetype.ontology
|
|
47
47
|
ontology = 'ontology'+NL
|
|
48
|
+
ontology << primary_language_line(ao.primary_language)
|
|
48
49
|
ontology << string_list_line('languages_available', ao.languages_available)
|
|
49
50
|
ontology << string_list_line('terminologies_available', ao.terminologies_available)
|
|
50
51
|
ontology << term_definitions_block(ao.term_definitions)
|
|
52
|
+
ontology << term_definitions_block(ao.constraint_definitions, 'constraint_definitions') if ao.constraint_definitions
|
|
51
53
|
ontology << term_bindings_block(ao.term_bindings)
|
|
54
|
+
ontology << constraint_bindings_block(ao.constraint_bindings)
|
|
52
55
|
ontology
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
def merge
|
|
56
|
-
return header + NL + description + NL + definition + NL + ontology
|
|
59
|
+
return header + NL + description + NL + definition + NL + invariant_section + ontology
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
include OpenEHR::AM::Archetype::ConstraintModel
|
|
@@ -68,6 +71,56 @@ module OpenEHR
|
|
|
68
71
|
INDENT + "#{keyword} = <" + values.map { |v| "\"#{v}\"" }.join(', ') + '>' + NL
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
# archetype (adl_version = ...; uid = ...) - a ';'-separated
|
|
75
|
+
# metadata clause. Empty when neither is present, so callers that
|
|
76
|
+
# never set uid see byte-identical output to before uid support.
|
|
77
|
+
def archetype_meta_data_clause
|
|
78
|
+
items = []
|
|
79
|
+
items << "adl_version = #{@archetype.adl_version}" if @archetype.adl_version
|
|
80
|
+
items << "uid = #{@archetype.uid.value}" if @archetype.uid
|
|
81
|
+
return '' if items.empty?
|
|
82
|
+
|
|
83
|
+
" (#{items.join('; ')})"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def specialise_section
|
|
87
|
+
return '' if @archetype.parent_archetype_id.nil?
|
|
88
|
+
|
|
89
|
+
'specialise' + NL + INDENT + @archetype.parent_archetype_id.value + NL
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def translations_block(translations)
|
|
93
|
+
return '' if translations.nil? || translations.empty?
|
|
94
|
+
|
|
95
|
+
block = INDENT + 'translations = <' + NL
|
|
96
|
+
translations.each { |code, details| block << translation_item_block(code, details) }
|
|
97
|
+
block << (INDENT + '>' + NL)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def translation_item_block(code, details)
|
|
101
|
+
block = (INDENT*2) + "[\"#{code}\"] = <" + NL
|
|
102
|
+
block << ((INDENT*3) + "language = <[#{details.language.terminology_id.value}::#{details.language.code_string}]>" + NL)
|
|
103
|
+
block << keyed_map_block('author', details.author) if details.author
|
|
104
|
+
block << ((INDENT*3) + "accreditation = <\"#{details.accreditation}\">" + NL) if details.accreditation
|
|
105
|
+
block << keyed_map_block('other_details', details.other_details) if details.other_details
|
|
106
|
+
block << ((INDENT*2) + '>' + NL)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def invariant_section
|
|
110
|
+
invariants = @archetype.invariants
|
|
111
|
+
return '' if invariants.nil? || invariants.empty?
|
|
112
|
+
|
|
113
|
+
block = 'invariant' + NL
|
|
114
|
+
invariants.each { |assertion| block << (INDENT + assertion.string_expression + NL) }
|
|
115
|
+
block << NL
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def primary_language_line(primary_language)
|
|
119
|
+
return '' if primary_language.nil?
|
|
120
|
+
|
|
121
|
+
INDENT + "primary_language = <\"#{primary_language}\">" + NL
|
|
122
|
+
end
|
|
123
|
+
|
|
71
124
|
def description_details_item(lang, item)
|
|
72
125
|
block = (INDENT*2)+'["'+lang+'"] = <'+NL
|
|
73
126
|
block << description_language_line(item.language)
|
|
@@ -109,8 +162,8 @@ module OpenEHR
|
|
|
109
162
|
block << ((INDENT*3)+'>'+NL)
|
|
110
163
|
end
|
|
111
164
|
|
|
112
|
-
def term_definitions_block(term_definitions)
|
|
113
|
-
block = INDENT +
|
|
165
|
+
def term_definitions_block(term_definitions, keyword = 'term_definitions')
|
|
166
|
+
block = INDENT + "#{keyword} = <" + NL
|
|
114
167
|
term_definitions.each do |lang, items|
|
|
115
168
|
block << ((INDENT*2) + "[\"#{lang}\"] = <" + NL)
|
|
116
169
|
block << ((INDENT*3) + 'items = <' + NL)
|
|
@@ -144,9 +197,28 @@ module OpenEHR
|
|
|
144
197
|
block << (INDENT + '>' + NL)
|
|
145
198
|
end
|
|
146
199
|
|
|
200
|
+
# constraint_bindings mirrors term_bindings' 3-level nesting
|
|
201
|
+
# (terminology -> items -> code), but each value is a bare/unquoted
|
|
202
|
+
# URI (a DvUri) rather than a qualified term code reference.
|
|
203
|
+
def constraint_bindings_block(constraint_bindings)
|
|
204
|
+
return '' if constraint_bindings.nil? || constraint_bindings.empty?
|
|
205
|
+
|
|
206
|
+
block = INDENT + 'constraint_bindings = <' + NL
|
|
207
|
+
constraint_bindings.each do |terminology, codes|
|
|
208
|
+
block << ((INDENT*2) + "[\"#{terminology}\"] = <" + NL)
|
|
209
|
+
block << ((INDENT*3) + 'items = <' + NL)
|
|
210
|
+
codes.each do |code, uri|
|
|
211
|
+
block << ((INDENT*4) + "[\"#{code}\"] = <#{uri.value}>" + NL)
|
|
212
|
+
end
|
|
213
|
+
block << ((INDENT*3) + '>' + NL)
|
|
214
|
+
block << ((INDENT*2) + '>' + NL)
|
|
215
|
+
end
|
|
216
|
+
block << (INDENT + '>' + NL)
|
|
217
|
+
end
|
|
218
|
+
|
|
147
219
|
# Recursive cADL emitter for a single C_OBJECT node, dispatching
|
|
148
|
-
# on its concrete class.
|
|
149
|
-
#
|
|
220
|
+
# on its concrete class. Domain types with no ADL 1.4 grammar rule
|
|
221
|
+
# (C_DV_SCALE, C_DV_STATE - both RM 1.1.0+ additions) deliberately
|
|
150
222
|
# raise rather than emit a guess at their syntax.
|
|
151
223
|
def emit_c_object(node, depth)
|
|
152
224
|
case node
|
|
@@ -158,6 +230,14 @@ module OpenEHR
|
|
|
158
230
|
emit_constraint_ref(node, depth)
|
|
159
231
|
when CComplexObject
|
|
160
232
|
emit_complex_object(node, depth)
|
|
233
|
+
when OpenEHRProfile::DataTypes::Quantity::CDvScale
|
|
234
|
+
raise ArgumentError,
|
|
235
|
+
'ADLSerializer cannot emit C_DV_SCALE in cADL - the ADL 1.4 grammar has no C_DV_SCALE rule ' \
|
|
236
|
+
'(RM 1.1.0 addition); use the XML serializer instead'
|
|
237
|
+
when OpenEHRProfile::DataTypes::Basic::CDvState
|
|
238
|
+
raise ArgumentError,
|
|
239
|
+
'ADLSerializer cannot emit C_DV_STATE in cADL - the ADL 1.4 grammar has no C_DV_STATE rule; ' \
|
|
240
|
+
'use the XML serializer instead'
|
|
161
241
|
else
|
|
162
242
|
raise ArgumentError, "ADLSerializer cannot emit a #{node.class} node"
|
|
163
243
|
end
|
|
@@ -192,7 +272,7 @@ module OpenEHR
|
|
|
192
272
|
head = attribute_head(attribute, depth)
|
|
193
273
|
children = attribute.children || []
|
|
194
274
|
if children.size == 1 && inline?(children.first)
|
|
195
|
-
head + " matches {#{inline_body(children.first)}}" + NL
|
|
275
|
+
head + " matches {#{inline_body(children.first, depth)}}" + NL
|
|
196
276
|
else
|
|
197
277
|
body = children.map { |c| emit_c_object(c, depth + 1) }.join
|
|
198
278
|
head + ' matches {' + NL + body + (INDENT*depth) + '}' + NL
|
|
@@ -209,10 +289,11 @@ module OpenEHR
|
|
|
209
289
|
def inline?(node)
|
|
210
290
|
node.is_a?(CPrimitiveObject) || node.is_a?(ConstraintRef) ||
|
|
211
291
|
node.is_a?(OpenEHRProfile::DataTypes::Quantity::CDvOrdinal) ||
|
|
212
|
-
node.is_a?(OpenEHRProfile::DataTypes::Text::CCodePhrase)
|
|
292
|
+
node.is_a?(OpenEHRProfile::DataTypes::Text::CCodePhrase) ||
|
|
293
|
+
node.is_a?(OpenEHRProfile::DataTypes::Quantity::CDvQuantity)
|
|
213
294
|
end
|
|
214
295
|
|
|
215
|
-
def inline_body(node)
|
|
296
|
+
def inline_body(node, depth)
|
|
216
297
|
case node
|
|
217
298
|
when CPrimitiveObject
|
|
218
299
|
primitive_body(node.item)
|
|
@@ -222,9 +303,56 @@ module OpenEHR
|
|
|
222
303
|
ordinal_body(node)
|
|
223
304
|
when OpenEHRProfile::DataTypes::Text::CCodePhrase
|
|
224
305
|
code_phrase_body(node)
|
|
306
|
+
when OpenEHRProfile::DataTypes::Quantity::CDvQuantity
|
|
307
|
+
cdv_quantity_body(node, depth)
|
|
225
308
|
end
|
|
226
309
|
end
|
|
227
310
|
|
|
311
|
+
# cADL syntax for C_DV_QUANTITY is a dADL-style block, not a short
|
|
312
|
+
# inline literal like the other inline? types above - see the
|
|
313
|
+
# adl_grammar.tt c_dv_quantity rule and CDvQuantityItems#value
|
|
314
|
+
# (adl_helper.rb) for the exact shape this mirrors: property is a
|
|
315
|
+
# qualified term code reference, list is an array of CQuantityItem
|
|
316
|
+
# keyed by 1-based position (the key itself isn't stored on
|
|
317
|
+
# CQuantityItem, so round-tripping only needs *a* stable key, not
|
|
318
|
+
# the original one), and assumed_value is a real DV_QUANTITY (plain
|
|
319
|
+
# magnitude/precision, not a range).
|
|
320
|
+
def cdv_quantity_body(node, depth)
|
|
321
|
+
return 'C_DV_QUANTITY < >' if node.any_allowed?
|
|
322
|
+
|
|
323
|
+
body = 'C_DV_QUANTITY <' + NL
|
|
324
|
+
body << quantity_property_line(node.property, depth + 1) if node.property
|
|
325
|
+
body << quantity_list_block(node.list, depth + 1) if node.list
|
|
326
|
+
body << assumed_quantity_block(node.assumed_value, depth + 1) if node.assumed_value
|
|
327
|
+
body << ((INDENT*depth) + '>')
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def quantity_property_line(property, depth)
|
|
331
|
+
(INDENT*depth) + "property = <[#{property.terminology_id.value}::#{property.code_string}]>" + NL
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def quantity_list_block(list, depth)
|
|
335
|
+
block = (INDENT*depth) + 'list = <' + NL
|
|
336
|
+
list.each_with_index { |item, index| block << quantity_item_block(item, index + 1, depth + 1) }
|
|
337
|
+
block << ((INDENT*depth) + '>' + NL)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def quantity_item_block(item, key, depth)
|
|
341
|
+
block = (INDENT*depth) + "[\"#{key}\"] = <" + NL
|
|
342
|
+
block << ((INDENT*(depth+1)) + "units = <\"#{item.units}\">" + NL)
|
|
343
|
+
block << ((INDENT*(depth+1)) + "magnitude = <|#{range_literal(item.magnitude)}|>" + NL) if item.magnitude
|
|
344
|
+
block << ((INDENT*(depth+1)) + "precision = <|#{range_literal(item.precision)}|>" + NL) if item.precision
|
|
345
|
+
block << ((INDENT*depth) + '>' + NL)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def assumed_quantity_block(assumed_value, depth)
|
|
349
|
+
block = (INDENT*depth) + 'assumed_value = <' + NL
|
|
350
|
+
block << ((INDENT*(depth+1)) + "units = <\"#{assumed_value.units}\">" + NL)
|
|
351
|
+
block << ((INDENT*(depth+1)) + "magnitude = <#{assumed_value.magnitude}>" + NL)
|
|
352
|
+
block << ((INDENT*(depth+1)) + "precision = <#{assumed_value.precision}>" + NL) unless assumed_value.precision.nil?
|
|
353
|
+
block << ((INDENT*depth) + '>' + NL)
|
|
354
|
+
end
|
|
355
|
+
|
|
228
356
|
def emit_archetype_slot(node, depth)
|
|
229
357
|
head = (INDENT*depth) + 'allow_archetype ' + node.rm_type_name
|
|
230
358
|
head += "[#{node.node_id}]" if node.node_id
|
|
@@ -319,10 +447,19 @@ module OpenEHR
|
|
|
319
447
|
def ordinal_body(node)
|
|
320
448
|
return 'C_DV_ORDINAL < >' if node.any_allowed?
|
|
321
449
|
|
|
322
|
-
body = node.list.map { |o| "#{o.value}|[#{o.symbol
|
|
450
|
+
body = node.list.map { |o| "#{o.value}|[#{ordinal_symbol_reference(o.symbol)}]" }.join(', ')
|
|
323
451
|
with_assumed_value(body, node)
|
|
324
452
|
end
|
|
325
453
|
|
|
454
|
+
# The c_ordinal grammar rule builds symbol as a DV_CODED_TEXT
|
|
455
|
+
# (code lives at symbol.defining_code, not symbol itself) - a bare
|
|
456
|
+
# CODE_PHRASE-like symbol is also accepted, matching the dual
|
|
457
|
+
# shape CDvOrdinal#valid_value? already tolerates.
|
|
458
|
+
def ordinal_symbol_reference(symbol)
|
|
459
|
+
code_phrase = symbol.respond_to?(:defining_code) ? symbol.defining_code : symbol
|
|
460
|
+
"#{code_phrase.terminology_id.value}::#{code_phrase.code_string}"
|
|
461
|
+
end
|
|
462
|
+
|
|
326
463
|
def code_phrase_body(node)
|
|
327
464
|
return '*' if node.any_allowed?
|
|
328
465
|
|