Module: Xqsr3::XML::Utilities::Compare::Internal_Compare_

Extended by:
Quality::ParameterChecking
Defined in:
lib/xqsr3/xml/utilities/compare.rb

Constant Summary collapse

DEFAULT_OPTIONS =
{

	debug: false,
#			element_order: false,
	equate_nil_and_empty: false,
	ignore_attributes: false,
	ignore_attribute_order: true,
	ignore_child_node_order: true,
	normalise_whitespace: true,
#			normalize_whitespace: true,
	validate_params: true,
}
ORDER_OPTIONS_SYMBOLS =
[

	:element_order,
	:ignore_attribute_order,
	:ignore_child_node_order,
]
WHITESPACE_OPTIONS_SYMBOLS =
[

	:normalise_whitespace,
	:normalize_whitespace,
]

Class Method Summary collapse

Methods included from Quality::ParameterChecking

check_param, check_parameter, included

Class Method Details

.derive_options_(given_options) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/xqsr3/xml/utilities/compare.rb', line 184

def self.derive_options_ given_options

	default_options	=	DEFAULT_OPTIONS
	derived_options	=	{}.merge given_options


	# sort whitespace

	if WHITESPACE_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }

		default_options	=	default_options.reject { |k, v| WHITESPACE_OPTIONS_SYMBOLS.include? k }
	end

	if given_options.has_key? :normalise_whitespace

		derived_options.delete :normalize_whitespace
	elsif given_options.has_key? :normalize_whitespace

		derived_options[:normalise_whitespace] = given_options[:normalize_whitespace]

		derived_options.delete :normalize_whitespace
	end


	# sort element-order

	if ORDER_OPTIONS_SYMBOLS.any? { |sym| given_options.has_key? sym }

		default_options	=	default_options.reject { |k, v| ORDER_OPTIONS_SYMBOLS.include? k }
	end

	if given_options.has_key? :element_order

		element_order	=	given_options[:element_order]

		derived_options[:ignore_attribute_order]	=	!element_order
		derived_options[:ignore_child_node_order]	=	!element_order
	end

	derived_options[:ignore_attribute_order]	=	given_options[:ignore_attribute_order] if given_options.has_key? :ignore_attribute_order
	derived_options[:ignore_child_node_order]	=	given_options[:ignore_child_node_order] if given_options.has_key? :ignore_child_node_order

	default_options.merge derived_options
end

.one_line_(s) ⇒ Object



229
230
231
232
# File 'lib/xqsr3/xml/utilities/compare.rb', line 229

def self.one_line_ s

	s = s.to_s.gsub(/\s+/, ' ')
end

.xml_compare_(lhs, rhs, options) ⇒ Object

:debug :element_order :equate_nil_and_empty :ignore_attributes :ignore_attribute_order :normalise_whitespace :normalize_whitespace :validate_params



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/xqsr3/xml/utilities/compare.rb', line 245

def self.xml_compare_ lhs, rhs, options

	$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{self.one_line_ lhs}, rhs (#{rhs.class})=#{self.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG

	# validate parameter(s)

	check_parameter options, 'options', type: ::Hash if $DEBUG

	validate_params	=	$DEBUG || options[:debug] || options[:validate_params]

	check_parameter lhs, 'lhs', types: [ ::String, ::Nokogiri::XML::Node ], allow_nil: true if validate_params
	check_parameter rhs, 'rhs', types: [ ::String, ::Nokogiri::XML::Node ], allow_nil: true if validate_params

	options			=	self.derive_options_ options

	# deal with nil(s)

	return Result.same if lhs.nil? && rhs.nil?

	if lhs.nil?

		return Result.same if options[:equate_nil_and_empty] && ::String === rhs && rhs.empty?

		return Result.different :parameter_is_nil
	end

	if rhs.nil?

		return Result.same if options[:equate_nil_and_empty] && ::String === lhs && lhs.empty?

		return Result.different :parameter_is_nil
	end


	# deal with string(s)

	lhs	=	Nokogiri::XML(lhs) if ::String === lhs
	rhs	=	Nokogiri::XML(rhs) if ::String === rhs


	self.xml_compare_nodes_ lhs, rhs, options
end

.xml_compare_nodes_(lhs, rhs, options) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/xqsr3/xml/utilities/compare.rb', line 288

def self.xml_compare_nodes_ lhs, rhs, options

	$stderr.puts "#{self}#{__method__}(lhs (#{lhs.class})=#{self.one_line_ lhs}, rhs (#{rhs.class})=#{self.one_line_ rhs}, options (#{options.class})=#{options})" if $DEBUG


	# Compare:
	#
	# - name
	# - attributes
	# - content
	# - children
	# - 


	# ##########################
	# name

	lhs_name	=	lhs.name
	rhs_name	=	rhs.name

	return Result.different :different_node_names, lhs_node: lhs, rhs_node: rhs if lhs_name != rhs_name


	# ##########################
	# attributes

	unless options[:ignore_attributes]

		lhs_attributes	=	lhs.attribute_nodes
		rhs_attributes	=	rhs.attribute_nodes

		return Result.different :different_attribute_count, lhs_node: lhs, rhs_node: rhs if lhs_attributes.count != rhs_attributes.count


		lhs_attr_list	=	lhs_attributes.map { |attr| [ attr.name, attr.content ] }
		rhs_attr_list	=	rhs_attributes.map { |attr| [ attr.name, attr.content ] }

		if lhs_attr_list != rhs_attr_list

			# do the sort first

			lhs_attr_list.sort! { |l, r| l[0] <=> r[0] }
			rhs_attr_list.sort! { |l, r| l[0] <=> r[0] }

			# Now there are four possibiliies:
			#
			# 1. Different attributes
			# 2. Different attribute order
			# 3. Same (when reordered)

			if lhs_attr_list == rhs_attr_list

				if options[:ignore_attribute_order]

					# 3
				else

					# 2

					return Result.different :different_attribute_order, lhs_node: lhs, rhs_node: rhs
				end
			else

				return Result.different :different_attributes, lhs_node: lhs, rhs_node: rhs
			end
		end
	end

	# ##########################
	# content

	normalise_ws	=	options[:normalise_whitespace]

	lhs_content	=	normalise_ws ? lhs.content.gsub(/\s+/, ' ').strip : lhs.content
	rhs_content	=	normalise_ws ? rhs.content.gsub(/\s+/, ' ').strip : rhs.content

	return Result.different :different_node_contents, lhs_node: lhs, rhs_node: rhs if lhs_content != rhs_content


	# ##########################
	# children (preparation)

	lhs_children		=	lhs.children.to_a
	rhs_children		=	rhs.children.to_a

	lhs_children.reject! { |child| child.text? && child.content.strip.empty? }
	rhs_children.reject! { |child| child.text? && child.content.strip.empty? }


	# ##########################
	# children - count

	lhs_children_count	=	lhs_children.count
	rhs_children_count	=	rhs_children.count

	return Result.different :different_child_node_count, lhs_node: lhs, rhs_node: rhs if lhs_children_count != rhs_children_count


	# ##########################
	# children - names

	lhs_children_names	=	lhs_children.map { |ch| ch.name }
	rhs_children_names	=	rhs_children.map { |ch| ch.name }

	if lhs_children_names != rhs_children_names

		# At this point, the lists of names of child elements are
		# different. This may be because there are different
		# elements or because they are in a different order. Either
		# way, in order to provide detailed reasons for
		# inequivalency, we must do an order-independent comparison

		children_sorted_lhs	=	lhs_children.sort { |x, y| x.name <=> y.name }
		children_sorted_rhs	=	rhs_children.sort { |x, y| x.name <=> y.name }

		ch_names_sorted_lhs	=	children_sorted_lhs.map { |ch| ch.name }
		ch_names_sorted_rhs	=	children_sorted_rhs.map { |ch| ch.name }

		ignore_order		=	options[:ignore_child_node_order]

		if ignore_order

			return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs if ch_names_sorted_lhs != ch_names_sorted_rhs

			# Since they are the same (when reordered), we need to
			# adopt the ordered sequences so that the comparison of
			# the children are meaningful

			lhs_children	=	children_sorted_lhs
			rhs_children	=	children_sorted_rhs
		else

			# failed, so need to determine whether it's due to
			# different nodes or different order

			if ch_names_sorted_lhs == ch_names_sorted_rhs

				return Result.different :different_child_node_order, lhs_node: lhs, rhs_node: rhs
			else

				return Result.different :different_child_nodes, lhs_node: lhs, rhs_node: rhs
			end
		end
	end

	(0 ... lhs_children.count).each do |index|

		ch_lhs	=	lhs_children[index]
		ch_rhs	=	rhs_children[index]

		r = self.xml_compare_nodes_ ch_lhs, ch_rhs, options

		return r unless r.status
	end

	return Result.same
end