openehr 2.0.2 → 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/am/archetype/constraint_model.rb +1 -1
- 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/assumed_library_types.rb +20 -17
- data/lib/openehr/parser/adl_parser.rb +0 -4
- data/lib/openehr/parser/opt_parser.rb +12 -429
- 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 +6 -9
- data/lib/openehr/rm/common/resource.rb +0 -1
- 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_structures/item_structure.rb +0 -1
- data/lib/openehr/rm/data_types/basic.rb +11 -21
- data/lib/openehr/rm/data_types/encapsulated.rb +18 -2
- data/lib/openehr/rm/data_types/quantity/date_time.rb +203 -28
- data/lib/openehr/rm/data_types/quantity.rb +66 -10
- data/lib/openehr/rm/data_types/time_specification.rb +101 -27
- data/lib/openehr/rm/data_types/uri.rb +0 -8
- data/lib/openehr/rm/ehr.rb +6 -6
- data/lib/openehr/rm/factory.rb +22 -0
- data/lib/openehr/rm/security.rb +3 -1
- data/lib/openehr/rm/type_name.rb +0 -1
- data/lib/openehr/serializer/adl_serializer.rb +239 -84
- data/lib/openehr/serializer/base.rb +2 -0
- data/lib/openehr/serializer/rm_json_serializer.rb +0 -1
- data/lib/openehr/serializer/xml_serializer.rb +351 -103
- data/lib/openehr/version.rb +1 -1
- metadata +9 -144
|
@@ -1,64 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rexml/document'
|
|
2
4
|
require 'builder'
|
|
3
5
|
require_relative 'base'
|
|
4
6
|
|
|
5
7
|
module OpenEHR
|
|
6
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.
|
|
7
24
|
class XMLSerializer < BaseSerializer
|
|
8
25
|
def header
|
|
9
|
-
header = ''
|
|
26
|
+
header = +''
|
|
10
27
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => header)
|
|
11
|
-
xml.archetype_id
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
14
32
|
xml.concept @archetype.concept
|
|
15
33
|
xml.original_language do
|
|
16
|
-
xml.terminology_id
|
|
17
|
-
xml.value @archetype.original_language.terminology_id.value
|
|
18
|
-
end
|
|
34
|
+
xml.terminology_id { xml.value @archetype.original_language.terminology_id.value }
|
|
19
35
|
xml.code_string @archetype.original_language.code_string
|
|
20
36
|
end
|
|
37
|
+
emit_translations(xml, @archetype.translations)
|
|
21
38
|
return header
|
|
22
39
|
end
|
|
23
40
|
|
|
24
41
|
def description
|
|
25
|
-
desc = ''
|
|
42
|
+
desc = +''
|
|
26
43
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => desc)
|
|
27
44
|
ad = @archetype.description
|
|
28
45
|
if ad
|
|
29
46
|
xml.description do
|
|
30
|
-
ad.original_author.each
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
if ad.other_contributors
|
|
34
|
-
ad.other_contributors.each do |co|
|
|
35
|
-
xml.other_contributors co
|
|
36
|
-
end
|
|
37
|
-
end
|
|
47
|
+
ad.original_author.each { |key, value| xml.original_author(value, 'id' => key) }
|
|
48
|
+
(ad.other_contributors || []).each { |co| xml.other_contributors co }
|
|
38
49
|
xml.lifecycle_state ad.lifecycle_state
|
|
39
50
|
xml.details do
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
xml.code_string lang
|
|
46
|
-
end
|
|
47
|
-
xml.purpose item.purpose
|
|
48
|
-
if item.keywords then
|
|
49
|
-
item.keywords.each do |word|
|
|
50
|
-
xml.keywords word
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
xml.use item.use if item.use
|
|
54
|
-
xml.misuse item.misuse if item.misuse
|
|
55
|
-
xml.copyright item.copyright if item.copyright
|
|
56
|
-
if ad.other_details
|
|
57
|
-
ad.other_details.each do |key,value|
|
|
58
|
-
xml.other_details(value, "id"=>key)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
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) } }
|
|
62
56
|
end
|
|
63
57
|
end
|
|
64
58
|
end
|
|
@@ -66,65 +60,108 @@ module OpenEHR
|
|
|
66
60
|
end
|
|
67
61
|
|
|
68
62
|
def definition
|
|
69
|
-
definition = ''
|
|
63
|
+
definition = +''
|
|
70
64
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => definition)
|
|
71
|
-
xml.definition
|
|
72
|
-
emit_c_object_body(xml, @archetype.definition)
|
|
73
|
-
end
|
|
65
|
+
xml.definition { emit_c_object_body(xml, @archetype.definition) }
|
|
74
66
|
return definition
|
|
75
67
|
end
|
|
76
68
|
|
|
77
69
|
def ontology
|
|
78
|
-
ontology = ''
|
|
70
|
+
ontology = +''
|
|
79
71
|
ao = @archetype.ontology
|
|
80
72
|
xml = Builder::XmlMarkup.new(:indent => 2, :target => ontology)
|
|
81
73
|
xml.ontology do
|
|
74
|
+
xml.primary_language ao.primary_language if ao.primary_language
|
|
82
75
|
xml.specialisation_depth ao.specialisation_depth
|
|
83
76
|
(ao.languages_available || []).each { |lang| xml.languages_available lang }
|
|
84
77
|
(ao.terminologies_available || []).each { |t| xml.terminologies_available t }
|
|
85
|
-
xml.term_definitions
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
terms.each do |code, term|
|
|
90
|
-
xml.code code
|
|
91
|
-
xml.items do
|
|
92
|
-
term.items.each do |key, value|
|
|
93
|
-
xml.item do
|
|
94
|
-
xml.key key
|
|
95
|
-
xml.value value
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
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 }
|
|
104
82
|
end
|
|
105
83
|
end
|
|
106
84
|
|
|
107
85
|
def merge
|
|
108
86
|
archetype = "<?xml version='1.0' encoding='UTF-8'?>" + NL +
|
|
109
87
|
"<archetype xmlns=\"http://schemas.openehr.org/v1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" + NL +
|
|
110
|
-
header + description + definition +
|
|
111
|
-
ontology + '</archetype>'
|
|
88
|
+
header + description + definition + emit_invariants + ontology + '</archetype>'
|
|
112
89
|
return archetype
|
|
113
90
|
end
|
|
114
91
|
|
|
115
92
|
include OpenEHR::AM::Archetype::ConstraintModel
|
|
116
93
|
Primitive = OpenEHR::AM::Archetype::ConstraintModel::Primitive
|
|
94
|
+
AssertionModel = OpenEHR::AM::Archetype::Assertion
|
|
117
95
|
OpenEHRProfile = OpenEHR::AM::OpenEHRProfile
|
|
118
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
|
+
|
|
119
108
|
private
|
|
120
109
|
|
|
121
|
-
|
|
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
|
|
122
158
|
# plus, unless any_allowed?, its attributes) directly into the
|
|
123
159
|
# current xml element - used both for the root <definition> and,
|
|
124
|
-
# 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.
|
|
125
162
|
def emit_c_object_body(xml, node)
|
|
126
163
|
xml.rm_type_name node.rm_type_name
|
|
127
|
-
xml.
|
|
164
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
128
165
|
xml.node_id node.node_id
|
|
129
166
|
return if node.any_allowed?
|
|
130
167
|
|
|
@@ -136,12 +173,18 @@ module OpenEHR
|
|
|
136
173
|
xml.upper_included interval.upper_included? unless interval.upper_included?.nil?
|
|
137
174
|
xml.lower_unbounded interval.lower_unbounded?
|
|
138
175
|
xml.upper_unbounded interval.upper_unbounded?
|
|
139
|
-
xml.lower interval.lower
|
|
140
|
-
xml.upper interval.upper
|
|
176
|
+
xml.lower interval.lower unless interval.lower_unbounded?
|
|
177
|
+
xml.upper interval.upper unless interval.upper_unbounded?
|
|
141
178
|
end
|
|
142
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.
|
|
143
185
|
def emit_attribute(xml, attribute)
|
|
144
|
-
|
|
186
|
+
xsi_type = attribute.is_a?(CMultipleAttribute) ? 'C_MULTIPLE_ATTRIBUTE' : 'C_SINGLE_ATTRIBUTE'
|
|
187
|
+
xml.attributes('xsi:type' => xsi_type) do
|
|
145
188
|
xml.rm_attribute_name attribute.rm_attribute_name
|
|
146
189
|
xml.existence { emit_interval(xml, attribute.existence) } if attribute.existence
|
|
147
190
|
emit_cardinality(xml, attribute.cardinality) if attribute.is_a?(CMultipleAttribute)
|
|
@@ -162,19 +205,32 @@ module OpenEHR
|
|
|
162
205
|
# Dispatches an attribute's child node, tagging each <children>
|
|
163
206
|
# element with an xsi:type discriminator (the AOM node's class,
|
|
164
207
|
# in openEHR's own naming) since the children of one attribute
|
|
165
|
-
# 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.
|
|
166
210
|
def emit_child(xml, node)
|
|
167
211
|
case node
|
|
168
212
|
when ArchetypeSlot
|
|
169
213
|
xml.children('xsi:type' => 'ARCHETYPE_SLOT') { emit_archetype_slot_body(xml, node) }
|
|
170
214
|
when ArchetypeInternalRef
|
|
171
|
-
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
|
|
172
220
|
when ConstraintRef
|
|
173
|
-
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
|
|
174
226
|
when CPrimitiveObject
|
|
175
|
-
xml.children('xsi:type' => 'C_PRIMITIVE_OBJECT') {
|
|
227
|
+
xml.children('xsi:type' => 'C_PRIMITIVE_OBJECT') { emit_primitive_object_body(xml, node) }
|
|
176
228
|
when OpenEHRProfile::DataTypes::Quantity::CDvOrdinal
|
|
177
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) }
|
|
178
234
|
when OpenEHRProfile::DataTypes::Text::CCodePhrase
|
|
179
235
|
xml.children('xsi:type' => 'C_CODE_PHRASE') { emit_code_phrase_body(xml, node) }
|
|
180
236
|
when CComplexObject
|
|
@@ -186,7 +242,7 @@ module OpenEHR
|
|
|
186
242
|
|
|
187
243
|
def emit_archetype_slot_body(xml, node)
|
|
188
244
|
xml.rm_type_name node.rm_type_name
|
|
189
|
-
xml.
|
|
245
|
+
xml.occurrences { emit_interval(xml, node.occurrences) }
|
|
190
246
|
xml.node_id node.node_id if node.node_id
|
|
191
247
|
emit_assertions(xml, 'includes', node.includes)
|
|
192
248
|
emit_assertions(xml, 'excludes', node.excludes)
|
|
@@ -195,66 +251,258 @@ module OpenEHR
|
|
|
195
251
|
def emit_assertions(xml, tag, assertions)
|
|
196
252
|
return if assertions.nil?
|
|
197
253
|
|
|
198
|
-
xml.tag!(tag) { assertions.each { |a| xml
|
|
254
|
+
xml.tag!(tag) { assertions.each { |a| emit_assertion(xml, a) } }
|
|
199
255
|
end
|
|
200
256
|
|
|
201
|
-
|
|
202
|
-
|
|
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" }
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def emit_primitive_item_body(xml, item)
|
|
203
352
|
if item.is_a?(Primitive::CBoolean)
|
|
204
|
-
xml
|
|
205
|
-
|
|
353
|
+
emit_boolean_item_body(xml, item)
|
|
354
|
+
elsif item.is_a?(Primitive::CDuration)
|
|
355
|
+
emit_duration_item_body(xml, item)
|
|
206
356
|
elsif item.respond_to?(:pattern) && item.pattern
|
|
207
357
|
xml.pattern item.pattern
|
|
358
|
+
emit_assumed_value(xml, item)
|
|
208
359
|
elsif item.list
|
|
209
|
-
item.list.each { |v| xml.
|
|
360
|
+
item.list.each { |v| xml.list literal(v) }
|
|
361
|
+
emit_assumed_value(xml, item)
|
|
210
362
|
elsif item.range
|
|
211
|
-
xml.range {
|
|
363
|
+
xml.range { emit_interval(xml, item.range) }
|
|
364
|
+
emit_assumed_value(xml, item)
|
|
365
|
+
else
|
|
366
|
+
emit_assumed_value(xml, item)
|
|
212
367
|
end
|
|
213
|
-
xml.assumed_value literal(item.assumed_value) if item.has_assumed_value?
|
|
214
368
|
end
|
|
215
369
|
|
|
216
|
-
def
|
|
217
|
-
xml.
|
|
218
|
-
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?
|
|
219
405
|
end
|
|
220
406
|
|
|
221
407
|
def literal(value)
|
|
222
408
|
value.respond_to?(:value) ? value.value.to_s : value.to_s
|
|
223
409
|
end
|
|
224
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.
|
|
225
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
|
|
226
418
|
return if node.any_allowed?
|
|
227
419
|
|
|
228
|
-
node.list.each
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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?
|
|
233
468
|
end
|
|
234
|
-
xml.assumed_value literal(node.assumed_value) if node.has_assumed_value?
|
|
235
469
|
end
|
|
236
470
|
|
|
237
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
|
|
238
475
|
return if node.any_allowed?
|
|
239
476
|
|
|
240
|
-
xml.terminology_id node.terminology_id.value
|
|
241
|
-
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 }
|
|
242
479
|
end
|
|
243
480
|
|
|
244
|
-
def
|
|
245
|
-
|
|
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
|
|
246
485
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
xml.
|
|
251
|
-
xml.
|
|
252
|
-
xml.code code
|
|
253
|
-
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) }
|
|
254
491
|
end
|
|
255
492
|
end
|
|
256
493
|
end
|
|
257
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
|
|
258
506
|
end
|
|
259
507
|
end
|
|
260
508
|
end
|
data/lib/openehr/version.rb
CHANGED