@@ -24,7 +24,7 @@ def resolve_path(elements, path)
2424 elements = Array . wrap ( elements )
2525 return elements if path . blank?
2626
27- paths = path . split ( /(?<!hl7) \. / )
27+ paths = path_segments ( path )
2828 segment = paths . first
2929 remaining_path = paths . drop ( 1 ) . join ( '.' )
3030
@@ -49,9 +49,9 @@ def find_a_value_at(given_element, path, include_dar: false, &block)
4949 elements = Array . wrap ( given_element )
5050 return find_in_elements ( elements , include_dar :, &block ) if path . empty?
5151
52- path_segments = path . split ( /(?<!hl7) \. / )
52+ path_segments = path_segments ( path )
5353
54- segment = path_segments . shift . delete_suffix ( '[x]' ) . gsub ( /^class$/ , 'local_class' ) . gsub ( '[x]:' , ':' ) . to_sym
54+ segment = path_segments . shift
5555
5656 remaining_path = path_segments . join ( '.' )
5757 elements . each do |element |
@@ -78,25 +78,75 @@ def find_in_elements(elements, include_dar: false, &)
7878
7979 # @private
8080 def get_next_value ( element , property )
81+ property = property . to_s
8182 extension_url = property [ /(?<=where\( url=').*(?='\) )/ ]
82- if extension_url . present?
83- element . url == extension_url ? element : nil
84- elsif property . to_s . include? ( ':' ) && ! property . to_s . include? ( 'url' )
85- find_slice_via_discriminator ( element , property )
83+ return extension_filter_value ( element , extension_url ) if extension_url . present?
84+ return typed_choice_value ( element , property ) if explicit_choice_path? ( property )
85+ return populated_choice_value ( element , property ) if implicit_choice_path? ( property )
86+ return find_slice_via_discriminator ( element , property ) if slice_path? ( property )
8687
87- else
88- local_name = local_field_name ( property )
89- value = element . send ( local_name )
90- primitive_value = get_primitive_type_value ( element , property , value )
91- primitive_value . present? ? primitive_value : value
92- end
88+ field_value ( element , property )
9389 rescue NoMethodError
9490 nil
9591 end
9692
93+ # @private
94+ def extension_filter_value ( element , extension_url )
95+ element . url == extension_url ? element : nil
96+ end
97+
98+ # @private
99+ def explicit_choice_path? ( property )
100+ property . include? ( '[x]:' )
101+ end
102+
103+ # @private
104+ def implicit_choice_path? ( property )
105+ property . end_with? ( '[x]' )
106+ end
107+
108+ # @private
109+ def slice_path? ( property )
110+ property . include? ( ':' ) && !property . include? ( 'url' )
111+ end
112+
113+ # @private
114+ def typed_choice_value ( element , property )
115+ _choice_path , typed_field = property . split ( ':' , 2 )
116+ field_value ( element , typed_field )
117+ end
118+
119+ # @private
120+ def populated_choice_value ( element , property )
121+ choice_prefix = property . delete_suffix ( '[x]' )
122+ populated_field =
123+ Array . wrap ( element . to_hash &.keys )
124+ . map ( &:to_s )
125+ . find do |field_name |
126+ field_name . start_with? ( choice_prefix ) && value_present? ( field_value ( element , field_name ) )
127+ end
128+
129+ return nil if populated_field . blank?
130+
131+ field_value ( element , populated_field )
132+ end
133+
134+ # @private
135+ def field_value ( element , field_name )
136+ local_name = local_field_name ( field_name )
137+ value = element . send ( local_name )
138+ primitive_value = get_primitive_type_value ( element , field_name , value )
139+ primitive_value . present? ? primitive_value : value
140+ end
141+
97142 # @private
98143 def get_primitive_type_value ( element , property , value )
99- source_value = element . source_hash [ "_#{ property } " ]
144+ return nil unless element . respond_to? ( :source_hash )
145+
146+ source_hash = element . source_hash
147+ return nil unless source_hash . present?
148+
149+ source_value = source_hash [ "_#{ property } " ]
100150
101151 return nil unless source_value . present?
102152
@@ -116,6 +166,47 @@ def local_field_name(field_name)
116166 end
117167 end
118168
169+ # @private
170+ def path_segments ( path )
171+ state = { current_segment : +'' , segments : [ ] , parentheses_depth : 0 , in_quotes : false }
172+ path . each_char { |char | update_path_segment_state ( state , char ) }
173+ state [ :segments ] << state [ :current_segment ] unless state [ :current_segment ] . empty?
174+ state [ :segments ]
175+ end
176+
177+ # @private
178+ def update_path_segment_state ( state , char )
179+ case char
180+ when "'"
181+ state [ :current_segment ] << char
182+ state [ :in_quotes ] = !state [ :in_quotes ]
183+ when '('
184+ append_path_character ( state , char , depth_change : 1 )
185+ when ')'
186+ append_path_character ( state , char , depth_change : -1 )
187+ when '.'
188+ split_path_segment_or_append ( state , char )
189+ else
190+ state [ :current_segment ] << char
191+ end
192+ end
193+
194+ # @private
195+ def append_path_character ( state , char , depth_change :)
196+ state [ :current_segment ] << char
197+ state [ :parentheses_depth ] += depth_change unless state [ :in_quotes ]
198+ end
199+
200+ # @private
201+ def split_path_segment_or_append ( state , char )
202+ if state [ :parentheses_depth ] . zero? && !state [ :in_quotes ]
203+ state [ :segments ] << state [ :current_segment ] . dup
204+ state [ :current_segment ] . clear
205+ else
206+ state [ :current_segment ] << char
207+ end
208+ end
209+
119210 # @private
120211 def find_slice_via_discriminator ( element , property )
121212 return unless metadata . present?
@@ -170,7 +261,7 @@ def matching_pattern_identifier_slice?(slice, discriminator)
170261
171262 # @private
172263 def matching_value_slice? ( slice , discriminator )
173- values = discriminator [ :values ] . map { |value | value . merge ( path : value [ :path ] . split ( '.' ) ) }
264+ values = discriminator [ :values ] . map { |value | value . merge ( path : path_segments ( value [ :path ] ) ) }
174265 verify_slice_by_values ( slice , values )
175266 end
176267
@@ -200,15 +291,31 @@ def matching_type_slice?(slice, discriminator)
200291
201292 # @private
202293 def matching_required_binding_slice? ( slice , discriminator )
203- slice_coding = discriminator [ :path ] . present? ? slice . send ( ( discriminator [ :path ] ) . to_s ) . coding : slice . coding
204- slice_coding . any? do |coding |
205- discriminator [ :values ] . any? do |value |
206- case value
207- when String
208- value == coding . code
209- when Hash
210- value [ :system ] == coding . system && value [ :code ] == coding . code
211- end
294+ slice_coding = required_binding_codings ( slice , discriminator )
295+ return slice_coding . present? if discriminator [ :values ] . blank?
296+
297+ slice_coding . any? { |coding | required_binding_value_match? ( coding , discriminator [ :values ] ) }
298+ end
299+
300+ # @private
301+ def required_binding_codings ( slice , discriminator )
302+ if discriminator [ :path ] . present?
303+ Array . wrap ( resolve_path ( slice , discriminator [ :path ] ) ) . flat_map { |value | Array . wrap ( value &.coding ) }
304+ elsif slice . is_a? ( FHIR ::Coding )
305+ [ slice ]
306+ else
307+ Array . wrap ( slice . coding )
308+ end
309+ end
310+
311+ # @private
312+ def required_binding_value_match? ( coding , values )
313+ values . any? do |value |
314+ case value
315+ when String
316+ value == coding . code
317+ when Hash
318+ value [ :system ] == coding . system && value [ :code ] == coding . code
212319 end
213320 end
214321 end
@@ -221,7 +328,7 @@ def verify_slice_by_values(element, value_definitions)
221328 value_definitions
222329 . select { |value_definition | value_definition [ :path ] . first == path_prefix }
223330 . each { |value_definition | value_definition [ :path ] . shift }
224- find_a_value_at ( element , path_prefix ) do |el_found |
331+ value_at_path_matches? ( element , path_prefix ) do |el_found |
225332 current_and_child_values_match? ( el_found , value_definitions_for_path )
226333 end
227334 end
@@ -245,6 +352,17 @@ def current_and_child_values_match?(el_found, value_definitions_for_path)
245352 current_element_values_match && child_element_values_match
246353 end
247354
355+ # @private
356+ def value_at_path_matches? ( element , path , include_dar : false , &)
357+ value_found = find_a_value_at ( element , path , include_dar :, &)
358+ value_present? ( value_found )
359+ end
360+
361+ # @private
362+ def value_present? ( value )
363+ value . present? || value == false
364+ end
365+
248366 # @private
249367 def flatten_bundles ( resources )
250368 resources . flat_map do |resource |
0 commit comments