openehr 2.1.0 → 2.3.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 +19 -6
- 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 +264 -0
- data/lib/openehr/parser/xml_domain_type_parsing.rb +128 -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
|
@@ -6,20 +6,35 @@ require_relative 'base'
|
|
|
6
6
|
|
|
7
7
|
module OpenEHR
|
|
8
8
|
module Serializer
|
|
9
|
+
# Emits the canonical openEHR ITS-XML AOM 1.4 shape for the parts
|
|
10
|
+
# that have a verifiable ground truth: <occurrences> (plural),
|
|
11
|
+
# xsi:type dispatch on <attributes>/<children>, nested
|
|
12
|
+
# terminology_id/code_list, and real ASSERTION expression trees.
|
|
13
|
+
# This gem's own OPTParser (lib/openehr/parser/opt_parser.rb) reads
|
|
14
|
+
# exactly this shape for the <definition> constraint tree - verified
|
|
15
|
+
# against real Ocean Template Designer-generated .opt fixtures in
|
|
16
|
+
# spec/lib/openehr/opt_parser/ - so the two no longer contradict
|
|
17
|
+
# each other. <ontology>'s attribute-style term_definitions follows
|
|
18
|
+
# the same confirmed-from-.opt convention; term_bindings/
|
|
19
|
+
# constraint_bindings and the header extras (uid/translations/etc.)
|
|
20
|
+
# have no directly-comparable real fixture in this repo, so they
|
|
21
|
+
# follow the same attribute-style spirit for internal consistency -
|
|
22
|
+
# XMLArchetypeParser (lib/openehr/parser/xml_archetype_parser.rb) is
|
|
23
|
+
# the authority for reading them back.
|
|
9
24
|
class XMLSerializer < BaseSerializer
|
|
10
25
|
def header
|
|
11
26
|
header = +''
|
|
12
27
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => header)
|
|
13
|
-
xml.archetype_id
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
xml.archetype_id { xml.value @archetype.archetype_id.value }
|
|
29
|
+
xml.uid { xml.value @archetype.uid.value } if @archetype.uid
|
|
30
|
+
xml.adl_version @archetype.adl_version if @archetype.adl_version
|
|
31
|
+
xml.parent_archetype_id { xml.value @archetype.parent_archetype_id.value } if @archetype.parent_archetype_id
|
|
16
32
|
xml.concept @archetype.concept
|
|
17
33
|
xml.original_language do
|
|
18
|
-
xml.terminology_id
|
|
19
|
-
xml.value @archetype.original_language.terminology_id.value
|
|
20
|
-
end
|
|
34
|
+
xml.terminology_id { xml.value @archetype.original_language.terminology_id.value }
|
|
21
35
|
xml.code_string @archetype.original_language.code_string
|
|
22
36
|
end
|
|
37
|
+
emit_translations(xml, @archetype.translations)
|
|
23
38
|
return header
|
|
24
39
|
end
|
|
25
40
|
|
|
@@ -29,38 +44,15 @@ module OpenEHR
|
|
|
29
44
|
ad = @archetype.description
|
|
30
45
|
if ad
|
|
31
46
|
xml.description do
|
|
32
|
-
ad.original_author.each
|
|
33
|
-
|
|
34
|
-
end
|
|
35
|
-
if ad.other_contributors
|
|
36
|
-
ad.other_contributors.each do |co|
|
|
37
|
-
xml.other_contributors co
|
|
38
|
-
end
|
|
39
|
-
end
|
|
47
|
+
ad.original_author.each { |key, value| xml.original_author(value, 'id' => key) }
|
|
48
|
+
(ad.other_contributors || []).each { |co| xml.other_contributors co }
|
|
40
49
|
xml.lifecycle_state ad.lifecycle_state
|
|
41
50
|
xml.details do
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
xml.code_string lang
|
|
48
|
-
end
|
|
49
|
-
xml.purpose item.purpose
|
|
50
|
-
if item.keywords then
|
|
51
|
-
item.keywords.each do |word|
|
|
52
|
-
xml.keywords word
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
xml.use item.use if item.use
|
|
56
|
-
xml.misuse item.misuse if item.misuse
|
|
57
|
-
xml.copyright item.copyright if item.copyright
|
|
58
|
-
if ad.other_details
|
|
59
|
-
ad.other_details.each do |key,value|
|
|
60
|
-
xml.other_details(value, "id"=>key)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
51
|
+
# Each language's fields are wrapped in its own <detail
|
|
52
|
+
# language="..."> - without this, multiple languages would
|
|
53
|
+
# flatten into one <details> with no way to tell which
|
|
54
|
+
# purpose/keywords/etc. belong to which language.
|
|
55
|
+
ad.details.each { |lang, item| xml.tag!('detail', 'language' => lang) { emit_description_detail(xml, lang, item) } }
|
|
64
56
|
end
|
|
65
57
|
end
|
|
66
58
|
end
|
|
@@ -70,9 +62,7 @@ module OpenEHR
|
|
|
70
62
|
def definition
|
|
71
63
|
definition = +''
|
|
72
64
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => definition)
|
|
73
|
-
xml.definition
|
|
74
|
-
emit_c_object_body(xml, @archetype.definition)
|
|
75
|
-
end
|
|
65
|
+
xml.definition { emit_c_object_body(xml, @archetype.definition) }
|
|
76
66
|
return definition
|
|
77
67
|
end
|
|
78
68
|
|
|
@@ -81,52 +71,97 @@ module OpenEHR
|
|
|
81
71
|
ao = @archetype.ontology
|
|
82
72
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => ontology)
|
|
83
73
|
xml.ontology do
|
|
74
|
+
xml.primary_language ao.primary_language if ao.primary_language
|
|
84
75
|
xml.specialisation_depth ao.specialisation_depth
|
|
85
76
|
(ao.languages_available || []).each { |lang| xml.languages_available lang }
|
|
86
77
|
(ao.terminologies_available || []).each { |t| xml.terminologies_available t }
|
|
87
|
-
xml.term_definitions
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
terms.each do |code, term|
|
|
92
|
-
xml.code code
|
|
93
|
-
xml.items do
|
|
94
|
-
term.items.each do |key, value|
|
|
95
|
-
xml.item do
|
|
96
|
-
xml.key key
|
|
97
|
-
xml.value value
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
emit_term_bindings(xml, ao.term_bindings)
|
|
78
|
+
emit_term_definitions(xml, 'term_definitions', ao.term_definitions)
|
|
79
|
+
emit_term_definitions(xml, 'constraint_definitions', ao.constraint_definitions) if ao.constraint_definitions
|
|
80
|
+
emit_bindings(xml, 'term_bindings', ao.term_bindings) { |cp| "#{cp.terminology_id.value}::#{cp.code_string}" }
|
|
81
|
+
emit_bindings(xml, 'constraint_bindings', ao.constraint_bindings) { |uri| uri.value }
|
|
106
82
|
end
|
|
107
83
|
end
|
|
108
84
|
|
|
109
85
|
def merge
|
|
110
86
|
archetype = "<?xml version='1.0' encoding='UTF-8'?>" + NL +
|
|
111
87
|
"<archetype xmlns=\"http://schemas.openehr.org/v1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" + NL +
|
|
112
|
-
header + description + definition +
|
|
113
|
-
ontology + '</archetype>'
|
|
88
|
+
header + description + definition + emit_invariants + ontology + '</archetype>'
|
|
114
89
|
return archetype
|
|
115
90
|
end
|
|
116
91
|
|
|
117
92
|
include OpenEHR::AM::Archetype::ConstraintModel
|
|
118
93
|
Primitive = OpenEHR::AM::Archetype::ConstraintModel::Primitive
|
|
94
|
+
AssertionModel = OpenEHR::AM::Archetype::Assertion
|
|
119
95
|
OpenEHRProfile = OpenEHR::AM::OpenEHRProfile
|
|
120
96
|
|
|
97
|
+
PRIMITIVE_XSI_TYPES = {
|
|
98
|
+
Primitive::CBoolean => 'C_BOOLEAN',
|
|
99
|
+
Primitive::CString => 'C_STRING',
|
|
100
|
+
Primitive::CInteger => 'C_INTEGER',
|
|
101
|
+
Primitive::CReal => 'C_REAL',
|
|
102
|
+
Primitive::CDate => 'C_DATE',
|
|
103
|
+
Primitive::CDateTime => 'C_DATE_TIME',
|
|
104
|
+
Primitive::CTime => 'C_TIME',
|
|
105
|
+
Primitive::CDuration => 'C_DURATION'
|
|
106
|
+
}.freeze
|
|
107
|
+
|
|
121
108
|
private
|
|
122
109
|
|
|
123
|
-
|
|
110
|
+
def emit_translations(xml, translations)
|
|
111
|
+
return if translations.nil? || translations.empty?
|
|
112
|
+
|
|
113
|
+
xml.translations do
|
|
114
|
+
translations.each { |code, details| emit_translation_item(xml, code, details) }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def emit_translation_item(xml, code, details)
|
|
119
|
+
xml.tag!('translation', 'language' => code) do
|
|
120
|
+
xml.language do
|
|
121
|
+
xml.terminology_id { xml.value details.language.terminology_id.value }
|
|
122
|
+
xml.code_string details.language.code_string
|
|
123
|
+
end
|
|
124
|
+
(details.author || {}).each { |k, v| xml.author(v, 'id' => k) }
|
|
125
|
+
xml.accreditation details.accreditation if details.accreditation
|
|
126
|
+
(details.other_details || {}).each { |k, v| xml.other_details(v, 'id' => k) }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def emit_description_detail(xml, lang, item)
|
|
131
|
+
xml.language do
|
|
132
|
+
xml.terminology_id { xml.value item.language.terminology_id.value }
|
|
133
|
+
xml.code_string lang
|
|
134
|
+
end
|
|
135
|
+
xml.purpose item.purpose
|
|
136
|
+
(item.keywords || []).each { |word| xml.keywords word }
|
|
137
|
+
xml.use item.use if item.use
|
|
138
|
+
xml.misuse item.misuse if item.misuse
|
|
139
|
+
xml.copyright item.copyright if item.copyright
|
|
140
|
+
(item.original_resource_uri || {}).each { |k, v| xml.original_resource_uri(v, 'id' => k) }
|
|
141
|
+
(item.other_details || {}).each { |k, v| xml.other_details(v, 'id' => k) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def emit_invariants
|
|
145
|
+
invariants = @archetype.respond_to?(:invariants) ? @archetype.invariants : nil
|
|
146
|
+
return '' if invariants.nil? || invariants.empty?
|
|
147
|
+
|
|
148
|
+
text = +''
|
|
149
|
+
xml = Builder::XmlMarkup.new(:indent => 2, :target => text)
|
|
150
|
+
# Each assertion is wrapped in its own <invariant> - without a
|
|
151
|
+
# per-item boundary, multiple invariants' tag/string_expression/
|
|
152
|
+
# expression triplets would flatten into indistinguishable siblings.
|
|
153
|
+
xml.invariants { invariants.each { |assertion| xml.tag!('invariant') { emit_assertion(xml, assertion) } } }
|
|
154
|
+
text
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Emits a C_OBJECT's own fields (rm_type_name/occurrences/node_id
|
|
124
158
|
# plus, unless any_allowed?, its attributes) directly into the
|
|
125
159
|
# current xml element - used both for the root <definition> and,
|
|
126
|
-
# via emit_child, for every nested C_COMPLEX_OBJECT.
|
|
160
|
+
# via emit_child, for every nested C_COMPLEX_OBJECT. Matches
|
|
161
|
+
# OPTParser#c_archetype_root/#c_complex_object exactly.
|
|
127
162
|
def emit_c_object_body(xml, node)
|
|
128
163
|
xml.rm_type_name node.rm_type_name
|
|
129
|
-
xml.
|
|
164
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
130
165
|
xml.node_id node.node_id
|
|
131
166
|
return if node.any_allowed?
|
|
132
167
|
|
|
@@ -138,12 +173,18 @@ module OpenEHR
|
|
|
138
173
|
xml.upper_included interval.upper_included? unless interval.upper_included?.nil?
|
|
139
174
|
xml.lower_unbounded interval.lower_unbounded?
|
|
140
175
|
xml.upper_unbounded interval.upper_unbounded?
|
|
141
|
-
xml.lower interval.lower
|
|
142
|
-
xml.upper interval.upper
|
|
176
|
+
xml.lower interval.lower unless interval.lower_unbounded?
|
|
177
|
+
xml.upper interval.upper unless interval.upper_unbounded?
|
|
143
178
|
end
|
|
144
179
|
|
|
180
|
+
# <attributes> itself carries the xsi:type discriminator
|
|
181
|
+
# (C_SINGLE_ATTRIBUTE/C_MULTIPLE_ATTRIBUTE) - OPTParser#attributes
|
|
182
|
+
# dispatches on exactly this attribute to pick c_single_attribute
|
|
183
|
+
# vs c_multiple_attribute; without it, this gem's own OPTParser
|
|
184
|
+
# cannot read its own XMLSerializer output back at all.
|
|
145
185
|
def emit_attribute(xml, attribute)
|
|
146
|
-
|
|
186
|
+
xsi_type = attribute.is_a?(CMultipleAttribute) ? 'C_MULTIPLE_ATTRIBUTE' : 'C_SINGLE_ATTRIBUTE'
|
|
187
|
+
xml.attributes('xsi:type' => xsi_type) do
|
|
147
188
|
xml.rm_attribute_name attribute.rm_attribute_name
|
|
148
189
|
xml.existence { emit_interval(xml, attribute.existence) } if attribute.existence
|
|
149
190
|
emit_cardinality(xml, attribute.cardinality) if attribute.is_a?(CMultipleAttribute)
|
|
@@ -164,19 +205,32 @@ module OpenEHR
|
|
|
164
205
|
# Dispatches an attribute's child node, tagging each <children>
|
|
165
206
|
# element with an xsi:type discriminator (the AOM node's class,
|
|
166
207
|
# in openEHR's own naming) since the children of one attribute
|
|
167
|
-
# can be a mix of concrete constraint node types.
|
|
208
|
+
# can be a mix of concrete constraint node types. Matches
|
|
209
|
+
# OPTParser#children's dispatch exactly.
|
|
168
210
|
def emit_child(xml, node)
|
|
169
211
|
case node
|
|
170
212
|
when ArchetypeSlot
|
|
171
213
|
xml.children('xsi:type' => 'ARCHETYPE_SLOT') { emit_archetype_slot_body(xml, node) }
|
|
172
214
|
when ArchetypeInternalRef
|
|
173
|
-
xml.children('xsi:type' => 'ARCHETYPE_INTERNAL_REF')
|
|
215
|
+
xml.children('xsi:type' => 'ARCHETYPE_INTERNAL_REF') do
|
|
216
|
+
xml.rm_type_name node.rm_type_name
|
|
217
|
+
xml.occurrences { emit_interval(xml, node.occurrences) } if node.occurrences
|
|
218
|
+
xml.target_path node.target_path
|
|
219
|
+
end
|
|
174
220
|
when ConstraintRef
|
|
175
|
-
xml.children('xsi:type' => 'CONSTRAINT_REF')
|
|
221
|
+
xml.children('xsi:type' => 'CONSTRAINT_REF') do
|
|
222
|
+
xml.rm_type_name node.rm_type_name
|
|
223
|
+
xml.occurrences { emit_interval(xml, node.occurrences) } if node.occurrences
|
|
224
|
+
xml.reference node.reference
|
|
225
|
+
end
|
|
176
226
|
when CPrimitiveObject
|
|
177
|
-
xml.children('xsi:type' => 'C_PRIMITIVE_OBJECT') {
|
|
227
|
+
xml.children('xsi:type' => 'C_PRIMITIVE_OBJECT') { emit_primitive_object_body(xml, node) }
|
|
178
228
|
when OpenEHRProfile::DataTypes::Quantity::CDvOrdinal
|
|
179
229
|
xml.children('xsi:type' => 'C_DV_ORDINAL') { emit_ordinal_body(xml, node) }
|
|
230
|
+
when OpenEHRProfile::DataTypes::Quantity::CDvScale
|
|
231
|
+
xml.children('xsi:type' => 'C_DV_SCALE') { emit_scale_body(xml, node) }
|
|
232
|
+
when OpenEHRProfile::DataTypes::Quantity::CDvQuantity
|
|
233
|
+
xml.children('xsi:type' => 'C_DV_QUANTITY') { emit_dv_quantity_body(xml, node) }
|
|
180
234
|
when OpenEHRProfile::DataTypes::Text::CCodePhrase
|
|
181
235
|
xml.children('xsi:type' => 'C_CODE_PHRASE') { emit_code_phrase_body(xml, node) }
|
|
182
236
|
when CComplexObject
|
|
@@ -188,7 +242,7 @@ module OpenEHR
|
|
|
188
242
|
|
|
189
243
|
def emit_archetype_slot_body(xml, node)
|
|
190
244
|
xml.rm_type_name node.rm_type_name
|
|
191
|
-
xml.
|
|
245
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
192
246
|
xml.node_id node.node_id if node.node_id
|
|
193
247
|
emit_assertions(xml, 'includes', node.includes)
|
|
194
248
|
emit_assertions(xml, 'excludes', node.excludes)
|
|
@@ -197,66 +251,258 @@ module OpenEHR
|
|
|
197
251
|
def emit_assertions(xml, tag, assertions)
|
|
198
252
|
return if assertions.nil?
|
|
199
253
|
|
|
200
|
-
xml.tag!(tag) { assertions.each { |a| xml
|
|
254
|
+
xml.tag!(tag) { assertions.each { |a| emit_assertion(xml, a) } }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# ASSERTION: tag (optional) + string_expression (optional,
|
|
258
|
+
# round-trip convenience - OPTParser doesn't require it) + a real
|
|
259
|
+
# expression tree, matching OPTParser#assertions/#expr_leaf/
|
|
260
|
+
# #expr_binary_operator (rather than the previous flat
|
|
261
|
+
# <assertion>text</assertion>, which Assertion#expression='s
|
|
262
|
+
# non-nil invariant made impossible to read back at all).
|
|
263
|
+
def emit_assertion(xml, assertion)
|
|
264
|
+
xml.tag assertion.tag if assertion.tag
|
|
265
|
+
xml.string_expression assertion.string_expression if assertion.string_expression
|
|
266
|
+
emit_expression(xml, assertion.expression)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def emit_expression(xml, expression)
|
|
270
|
+
case expression
|
|
271
|
+
when AssertionModel::ExprBinaryOperator
|
|
272
|
+
xml.expression('xsi:type' => 'EXPR_BINARY_OPERATOR') { emit_binary_operator_body(xml, expression) }
|
|
273
|
+
when AssertionModel::ExprUnaryOperator
|
|
274
|
+
xml.expression('xsi:type' => 'EXPR_UNARY_OPERATOR') { emit_unary_operator_body(xml, expression) }
|
|
275
|
+
when AssertionModel::ExprLeaf
|
|
276
|
+
xml.expression('xsi:type' => 'EXPR_LEAF') { emit_expr_leaf_body(xml, expression) }
|
|
277
|
+
else
|
|
278
|
+
raise ArgumentError, "XMLSerializer cannot emit a #{expression.class} expression"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def emit_binary_operator_body(xml, expression)
|
|
283
|
+
xml.type expression.type
|
|
284
|
+
xml.operator operator_value(expression.operator)
|
|
285
|
+
xml.precedence_overridden expression.precedence_overridden
|
|
286
|
+
emit_operand(xml, 'left_operand', expression.left_operand)
|
|
287
|
+
emit_operand(xml, 'right_operand', expression.right_operand)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def emit_unary_operator_body(xml, expression)
|
|
291
|
+
xml.type expression.type
|
|
292
|
+
xml.operator operator_value(expression.operator)
|
|
293
|
+
xml.precedence_overridden expression.precedence_overridden
|
|
294
|
+
emit_operand(xml, 'operand', expression.operand)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# ADLParser and OPTParser disagree on how they build
|
|
298
|
+
# ExprOperator#operator: the ADL grammar assigns a bare
|
|
299
|
+
# OperatorKind::OP_* Integer constant directly, while OPTParser's
|
|
300
|
+
# reader wraps it in an OperatorKind object - accept either.
|
|
301
|
+
def operator_value(operator)
|
|
302
|
+
operator.respond_to?(:value) ? operator.value : operator
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# left_operand/right_operand/operand elements carry the same
|
|
306
|
+
# xsi:type-tagged shape as a top-level <expression> - reuse the
|
|
307
|
+
# same dispatch/body emission, just under a different tag name.
|
|
308
|
+
def emit_operand(xml, tag, operand)
|
|
309
|
+
case operand
|
|
310
|
+
when AssertionModel::ExprBinaryOperator
|
|
311
|
+
xml.tag!(tag, 'xsi:type' => 'EXPR_BINARY_OPERATOR') { emit_binary_operator_body(xml, operand) }
|
|
312
|
+
when AssertionModel::ExprUnaryOperator
|
|
313
|
+
xml.tag!(tag, 'xsi:type' => 'EXPR_UNARY_OPERATOR') { emit_unary_operator_body(xml, operand) }
|
|
314
|
+
when AssertionModel::ExprLeaf
|
|
315
|
+
xml.tag!(tag, 'xsi:type' => 'EXPR_LEAF') { emit_expr_leaf_body(xml, operand) }
|
|
316
|
+
else
|
|
317
|
+
raise ArgumentError, "XMLSerializer cannot emit a #{operand.class} expression"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# OPTParser#expr_leaf dispatches on <type> (not the <item>'s own
|
|
322
|
+
# xsi:type) via `send type.downcase, item_leaf`: a bare "String"
|
|
323
|
+
# reference (a path, e.g. "archetype_id/value") reads back via
|
|
324
|
+
# its #string method (plain text); any C_* type reads back via
|
|
325
|
+
# the matching c_* primitive-item reader.
|
|
326
|
+
def emit_expr_leaf_body(xml, leaf)
|
|
327
|
+
xml.type leaf.type
|
|
328
|
+
if leaf.item.is_a?(Primitive::CPrimitive)
|
|
329
|
+
emit_primitive_item(xml, leaf.item)
|
|
330
|
+
else
|
|
331
|
+
xml.item(leaf.item.to_s, 'xsi:type' => 'xsd:string')
|
|
332
|
+
end
|
|
333
|
+
xml.reference_type leaf.reference_type
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def emit_primitive_object_body(xml, node)
|
|
337
|
+
xml.rm_type_name node.rm_type_name
|
|
338
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
339
|
+
xml.node_id node.node_id if node.node_id
|
|
340
|
+
emit_primitive_item(xml, node.item)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def emit_primitive_item(xml, item)
|
|
344
|
+
xml.item('xsi:type' => primitive_xsi_type(item)) { emit_primitive_item_body(xml, item) }
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def primitive_xsi_type(item)
|
|
348
|
+
PRIMITIVE_XSI_TYPES.fetch(item.class) { raise ArgumentError, "XMLSerializer cannot emit a #{item.class} primitive" }
|
|
201
349
|
end
|
|
202
350
|
|
|
203
|
-
def
|
|
204
|
-
xml.primitive_type item.type
|
|
351
|
+
def emit_primitive_item_body(xml, item)
|
|
205
352
|
if item.is_a?(Primitive::CBoolean)
|
|
206
|
-
xml
|
|
207
|
-
|
|
353
|
+
emit_boolean_item_body(xml, item)
|
|
354
|
+
elsif item.is_a?(Primitive::CDuration)
|
|
355
|
+
emit_duration_item_body(xml, item)
|
|
208
356
|
elsif item.respond_to?(:pattern) && item.pattern
|
|
209
357
|
xml.pattern item.pattern
|
|
358
|
+
emit_assumed_value(xml, item)
|
|
210
359
|
elsif item.list
|
|
211
|
-
item.list.each { |v| xml.
|
|
360
|
+
item.list.each { |v| xml.list literal(v) }
|
|
361
|
+
emit_assumed_value(xml, item)
|
|
212
362
|
elsif item.range
|
|
213
|
-
xml.range {
|
|
363
|
+
xml.range { emit_interval(xml, item.range) }
|
|
364
|
+
emit_assumed_value(xml, item)
|
|
365
|
+
else
|
|
366
|
+
emit_assumed_value(xml, item)
|
|
214
367
|
end
|
|
215
|
-
xml.assumed_value literal(item.assumed_value) if item.has_assumed_value?
|
|
216
368
|
end
|
|
217
369
|
|
|
218
|
-
def
|
|
219
|
-
xml.
|
|
220
|
-
xml.
|
|
370
|
+
def emit_boolean_item_body(xml, item)
|
|
371
|
+
xml.true_valid item.true_valid unless item.true_valid.nil?
|
|
372
|
+
xml.false_valid item.false_valid unless item.false_valid.nil?
|
|
373
|
+
xml.assumed_value item.assumed_value unless item.assumed_value.nil?
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# C_DURATION's range bounds are ISO8601 duration strings (DV_
|
|
377
|
+
# DURATION), not plain numbers, so it can't reuse emit_interval;
|
|
378
|
+
# matches OPTParser#duration_range/#duration_bound exactly.
|
|
379
|
+
def emit_duration_item_body(xml, item)
|
|
380
|
+
if item.pattern
|
|
381
|
+
xml.pattern item.pattern
|
|
382
|
+
elsif item.list
|
|
383
|
+
item.list.each { |v| xml.list v }
|
|
384
|
+
elsif item.range
|
|
385
|
+
xml.range { emit_duration_range(xml, item.range) }
|
|
386
|
+
end
|
|
387
|
+
emit_assumed_value(xml, item)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def emit_duration_range(xml, range)
|
|
391
|
+
unless range.lower_unbounded?
|
|
392
|
+
xml.lower range.lower.value
|
|
393
|
+
xml.lower_included range.lower_included?
|
|
394
|
+
end
|
|
395
|
+
xml.lower_unbounded range.lower_unbounded?
|
|
396
|
+
unless range.upper_unbounded?
|
|
397
|
+
xml.upper range.upper.value
|
|
398
|
+
xml.upper_included range.upper_included?
|
|
399
|
+
end
|
|
400
|
+
xml.upper_unbounded range.upper_unbounded?
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def emit_assumed_value(xml, item)
|
|
404
|
+
xml.assumed_value literal(item.assumed_value) if item.has_assumed_value?
|
|
221
405
|
end
|
|
222
406
|
|
|
223
407
|
def literal(value)
|
|
224
408
|
value.respond_to?(:value) ? value.value.to_s : value.to_s
|
|
225
409
|
end
|
|
226
410
|
|
|
411
|
+
# DV_ORDINAL.symbol is spec'd as DV_CODED_TEXT (code lives at
|
|
412
|
+
# symbol.defining_code, not symbol itself) - matches
|
|
413
|
+
# OPTParser#dv_ordinal_item's nested symbol/defining_code shape.
|
|
227
414
|
def emit_ordinal_body(xml, node)
|
|
415
|
+
xml.rm_type_name node.rm_type_name
|
|
416
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
417
|
+
xml.node_id node.node_id if node.node_id
|
|
228
418
|
return if node.any_allowed?
|
|
229
419
|
|
|
230
|
-
node.list.each
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
420
|
+
node.list.each { |o| emit_ordinal_item(xml, o) }
|
|
421
|
+
emit_assumed_value(xml, node)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def emit_ordinal_item(xml, item)
|
|
425
|
+
xml.list do
|
|
426
|
+
xml.value item.value
|
|
427
|
+
xml.symbol { xml.defining_code { emit_code_phrase_fields(xml, item.symbol.defining_code) } }
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# RM 1.1.0: same shape as C_DV_ORDINAL, but DV_SCALE.value is Real.
|
|
432
|
+
def emit_scale_body(xml, node)
|
|
433
|
+
xml.rm_type_name node.rm_type_name
|
|
434
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
435
|
+
xml.node_id node.node_id if node.node_id
|
|
436
|
+
return if node.any_allowed?
|
|
437
|
+
|
|
438
|
+
node.list.each { |o| emit_ordinal_item(xml, o) }
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def emit_dv_quantity_body(xml, node)
|
|
442
|
+
xml.rm_type_name node.rm_type_name
|
|
443
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
444
|
+
xml.node_id node.node_id if node.node_id
|
|
445
|
+
return if node.any_allowed?
|
|
446
|
+
|
|
447
|
+
xml.property { emit_code_phrase_fields(xml, node.property) } if node.property
|
|
448
|
+
(node.list || []).each { |item| emit_quantity_item(xml, item) }
|
|
449
|
+
emit_dv_quantity_assumed_value(xml, node.assumed_value) if node.assumed_value
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def emit_quantity_item(xml, item)
|
|
453
|
+
xml.list do
|
|
454
|
+
xml.units item.units
|
|
455
|
+
xml.magnitude { emit_interval(xml, item.magnitude) } if item.magnitude
|
|
456
|
+
xml.precision { emit_interval(xml, item.precision) } unless item.precision_unconstrained?
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# assumed_value here is a real DV_QUANTITY (plain magnitude/
|
|
461
|
+
# precision, not a range) - distinct from CQuantityItem's ranged
|
|
462
|
+
# list items above.
|
|
463
|
+
def emit_dv_quantity_assumed_value(xml, assumed_value)
|
|
464
|
+
xml.assumed_value do
|
|
465
|
+
xml.units assumed_value.units
|
|
466
|
+
xml.magnitude assumed_value.magnitude
|
|
467
|
+
xml.precision assumed_value.precision unless assumed_value.precision.nil?
|
|
235
468
|
end
|
|
236
|
-
xml.assumed_value literal(node.assumed_value) if node.has_assumed_value?
|
|
237
469
|
end
|
|
238
470
|
|
|
239
471
|
def emit_code_phrase_body(xml, node)
|
|
472
|
+
xml.rm_type_name node.rm_type_name
|
|
473
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
474
|
+
xml.node_id node.node_id if node.node_id
|
|
240
475
|
return if node.any_allowed?
|
|
241
476
|
|
|
242
|
-
xml.terminology_id node.terminology_id.value
|
|
243
|
-
node.code_list.each { |c| xml.
|
|
477
|
+
xml.terminology_id { xml.value node.terminology_id.value }
|
|
478
|
+
node.code_list.each { |c| xml.code_list c }
|
|
244
479
|
end
|
|
245
480
|
|
|
246
|
-
def
|
|
247
|
-
|
|
481
|
+
def emit_code_phrase_fields(xml, code_phrase)
|
|
482
|
+
xml.terminology_id { xml.value code_phrase.terminology_id.value }
|
|
483
|
+
xml.code_string code_phrase.code_string
|
|
484
|
+
end
|
|
248
485
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
xml.
|
|
253
|
-
xml.
|
|
254
|
-
xml.code code
|
|
255
|
-
xml.value "#{code_phrase.terminology_id.value}::#{code_phrase.code_string}"
|
|
486
|
+
def emit_term_definitions(xml, keyword, term_definitions)
|
|
487
|
+
term_definitions.each do |lang, terms|
|
|
488
|
+
terms.each do |code, term|
|
|
489
|
+
xml.tag!(keyword, 'language' => lang, 'code' => code) do
|
|
490
|
+
term.items.each { |key, value| xml.items(value, 'id' => key) }
|
|
256
491
|
end
|
|
257
492
|
end
|
|
258
493
|
end
|
|
259
494
|
end
|
|
495
|
+
|
|
496
|
+
def emit_bindings(xml, keyword, bindings)
|
|
497
|
+
return if bindings.nil?
|
|
498
|
+
|
|
499
|
+
bindings.each do |terminology, codes|
|
|
500
|
+
codes.each do |code, binding|
|
|
501
|
+
value_object = Array(binding).first
|
|
502
|
+
xml.tag!(keyword, yield(value_object), 'terminology' => terminology, 'code' => code)
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
260
506
|
end
|
|
261
507
|
end
|
|
262
508
|
end
|
data/lib/openehr/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openehr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinji KOBAYASHI
|
|
@@ -190,6 +190,10 @@ files:
|
|
|
190
190
|
- lib/openehr/parser/archetype_validator.rb
|
|
191
191
|
- lib/openehr/parser/exception.rb
|
|
192
192
|
- lib/openehr/parser/opt_parser.rb
|
|
193
|
+
- lib/openehr/parser/xml_archetype_parser.rb
|
|
194
|
+
- lib/openehr/parser/xml_constraint_parsing.rb
|
|
195
|
+
- lib/openehr/parser/xml_domain_type_parsing.rb
|
|
196
|
+
- lib/openehr/parser/xml_primitive_parsing.rb
|
|
193
197
|
- lib/openehr/path.rb
|
|
194
198
|
- lib/openehr/rm.rb
|
|
195
199
|
- lib/openehr/rm/common/archetyped.rb
|
|
@@ -242,7 +246,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
242
246
|
requirements:
|
|
243
247
|
- - ">="
|
|
244
248
|
- !ruby/object:Gem::Version
|
|
245
|
-
version: '3.
|
|
249
|
+
version: '3.3'
|
|
246
250
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
247
251
|
requirements:
|
|
248
252
|
- - ">="
|