lutaml-model 0.8.5 → 0.8.6
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/.github/workflows/dependent-tests.yml +4 -1
- data/.rubocop_todo.yml +97 -22
- data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
- data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
- data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
- data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
- data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
- data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
- data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
- data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
- data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
- data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
- data/lib/lutaml/xml/adapter.rb +0 -1
- data/lib/lutaml/xml/adapter_element.rb +7 -1
- data/lib/lutaml/xml/builder/base.rb +0 -1
- data/lib/lutaml/xml/data_model.rb +9 -1
- data/lib/lutaml/xml/document.rb +3 -1
- data/lib/lutaml/xml/element.rb +13 -10
- data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
- data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
- data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
- data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
- data/lib/lutaml/xml/xml_element.rb +24 -20
- data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
- data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
- data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
- data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
- data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
- metadata +9 -3
- data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../document"
|
|
4
4
|
require_relative "../declaration_handler"
|
|
5
|
+
require_relative "../doctype_extractor"
|
|
5
6
|
require_relative "../polymorphic_value_handler"
|
|
7
|
+
require_relative "xml_parser"
|
|
8
|
+
require_relative "xml_serializer"
|
|
9
|
+
require_relative "plan_based_builder"
|
|
10
|
+
require_relative "namespace_uri_collector"
|
|
6
11
|
|
|
7
12
|
module Lutaml
|
|
8
13
|
module Xml
|
|
@@ -14,56 +19,26 @@ module Lutaml
|
|
|
14
19
|
# consistent behavior across adapters.
|
|
15
20
|
#
|
|
16
21
|
# Subclasses must implement:
|
|
17
|
-
# -
|
|
18
|
-
# -
|
|
22
|
+
# - MOXML_ADAPTER - Moxml adapter implementation for parsing
|
|
23
|
+
# - BUILDER_CLASS - Builder implementation for serialization
|
|
24
|
+
# - PARSED_ELEMENT_CLASS - Adapter element class returned by parsing
|
|
19
25
|
#
|
|
20
26
|
# @abstract Subclass and implement required methods
|
|
21
27
|
class BaseAdapter < Document
|
|
28
|
+
extend DocTypeExtractor
|
|
29
|
+
extend AdapterHelpers
|
|
30
|
+
extend XmlParser
|
|
22
31
|
include DeclarationHandler
|
|
23
32
|
include PolymorphicValueHandler
|
|
33
|
+
include NamespaceUriCollector
|
|
34
|
+
include XmlSerializer
|
|
35
|
+
include PlanBasedBuilder
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# @param element [Object] the element to inspect
|
|
31
|
-
# @return [String] the element's local name
|
|
32
|
-
def self.name_of(element)
|
|
33
|
-
element.name
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Get the prefixed name of an element
|
|
37
|
-
#
|
|
38
|
-
# @param node [Object] the element node
|
|
39
|
-
# @return [String] the prefixed name (prefix:localname)
|
|
40
|
-
def self.prefixed_name_of(node)
|
|
41
|
-
node.prefixed_name
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Get the text content of an element
|
|
45
|
-
#
|
|
46
|
-
# @param element [Object] the element to get text from
|
|
47
|
-
# @return [String] the text content
|
|
48
|
-
def self.text_of(element)
|
|
49
|
-
element.text
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Get the namespaced name of an element
|
|
53
|
-
#
|
|
54
|
-
# @param element [Object] the element to inspect
|
|
55
|
-
# @return [String] the namespaced name
|
|
56
|
-
def self.namespaced_name_of(element)
|
|
57
|
-
element.namespaced_name
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Get the order of child elements
|
|
61
|
-
#
|
|
62
|
-
# @param element [Object] the parent element
|
|
63
|
-
# @return [Array] ordered list of children
|
|
64
|
-
def self.order_of(element)
|
|
65
|
-
element.order
|
|
66
|
-
end
|
|
37
|
+
EMPTY_DOCUMENT_ERROR_MESSAGE = "Document has no root element. " \
|
|
38
|
+
"The XML may be empty, contain only whitespace, " \
|
|
39
|
+
"or consist only of an XML declaration."
|
|
40
|
+
EMPTY_DOCUMENT_ERROR_TYPE = :invalid_format
|
|
41
|
+
PARSE_ERROR_CLASS = nil
|
|
67
42
|
|
|
68
43
|
# Convert a Formal Public Identifier (FPI) to a URN per RFC 3151.
|
|
69
44
|
# FPI examples: "-//OASIS//DTD XML Exchange Table Model 19990315//EN"
|
|
@@ -86,11 +61,6 @@ module Lutaml
|
|
|
86
61
|
uri.is_a?(String) && uri.start_with?("-//", "+//")
|
|
87
62
|
end
|
|
88
63
|
|
|
89
|
-
# Extract processing instructions from a moxml document that appear
|
|
90
|
-
# before the root element.
|
|
91
|
-
#
|
|
92
|
-
# @param moxml_doc [Moxml::Document] the parsed document
|
|
93
|
-
# @return [Array<Lutaml::Xml::DataModel::XmlProcessingInstruction>]
|
|
94
64
|
def self.extract_document_processing_instructions(moxml_doc)
|
|
95
65
|
pis = []
|
|
96
66
|
root = moxml_doc.root
|
|
@@ -105,15 +75,6 @@ module Lutaml
|
|
|
105
75
|
pis
|
|
106
76
|
end
|
|
107
77
|
|
|
108
|
-
# Build a namespaced attribute name
|
|
109
|
-
#
|
|
110
|
-
# @param prefix [String, nil] the namespace prefix
|
|
111
|
-
# @param name [String] the attribute name
|
|
112
|
-
# @return [String] the qualified attribute name
|
|
113
|
-
def self.namespaced_attr_name(prefix, name)
|
|
114
|
-
prefix ? "#{prefix}:#{name}" : name
|
|
115
|
-
end
|
|
116
|
-
|
|
117
78
|
# Build a namespaced element name
|
|
118
79
|
#
|
|
119
80
|
# @param namespace_uri [String, nil] the namespace URI
|
|
@@ -142,6 +103,8 @@ module Lutaml
|
|
|
142
103
|
options[:encoding]
|
|
143
104
|
elsif options.key?(:parse_encoding)
|
|
144
105
|
options[:parse_encoding]
|
|
106
|
+
elsif @encoding && @encoding.to_s.upcase != "ASCII-8BIT"
|
|
107
|
+
@encoding
|
|
145
108
|
else
|
|
146
109
|
"UTF-8"
|
|
147
110
|
end
|
|
@@ -179,6 +142,10 @@ module Lutaml
|
|
|
179
142
|
false
|
|
180
143
|
end
|
|
181
144
|
|
|
145
|
+
def order
|
|
146
|
+
root.order
|
|
147
|
+
end
|
|
148
|
+
|
|
182
149
|
# Get attribute definition for an element and rule
|
|
183
150
|
#
|
|
184
151
|
# @param element [Object] the model instance
|
|
@@ -240,430 +207,53 @@ module Lutaml
|
|
|
240
207
|
def attributes_hash(element)
|
|
241
208
|
result = Lutaml::Model::MappingHash.new
|
|
242
209
|
|
|
243
|
-
element
|
|
244
|
-
if attr
|
|
210
|
+
attribute_values(element) do |attr|
|
|
211
|
+
if schema_location_attribute?(attr)
|
|
245
212
|
result["__schema_location"] = {
|
|
246
213
|
namespace: attr.namespace,
|
|
247
|
-
prefix: attr
|
|
214
|
+
prefix: attribute_namespace_prefix(attr),
|
|
248
215
|
schema_location: attr.value,
|
|
249
216
|
}
|
|
250
217
|
else
|
|
251
|
-
result[attr
|
|
218
|
+
result[attribute_hash_name(attr)] = attr.value
|
|
252
219
|
end
|
|
253
220
|
end
|
|
254
221
|
|
|
255
222
|
result
|
|
256
223
|
end
|
|
257
224
|
|
|
258
|
-
# Add text content to XML builder
|
|
259
|
-
#
|
|
260
|
-
# @param xml [Builder] the XML builder
|
|
261
|
-
# @param value [Object] the value to add
|
|
262
|
-
# @param attribute [Attribute, nil] the attribute definition
|
|
263
|
-
# @param cdata [Boolean] whether to use CDATA
|
|
264
|
-
def add_value(xml, value, attribute, cdata: false)
|
|
265
|
-
if !value.nil?
|
|
266
|
-
if attribute.nil?
|
|
267
|
-
# For delegated attributes where attribute is nil, just use the raw value
|
|
268
|
-
xml.add_text(xml, value.to_s, cdata: cdata)
|
|
269
|
-
elsif attribute.transform.is_a?(Class) && attribute.transform < Lutaml::Model::ValueTransformer
|
|
270
|
-
# Value has already been transformed, use it directly
|
|
271
|
-
xml.add_text(xml, value.to_s, cdata: cdata)
|
|
272
|
-
else
|
|
273
|
-
# Normal serialization through attribute type system
|
|
274
|
-
serialized_value = attribute.serialize(value, :xml, register)
|
|
275
|
-
if attribute.raw?
|
|
276
|
-
xml.add_xml_fragment(xml, value)
|
|
277
|
-
elsif serialized_value.is_a?(Hash)
|
|
278
|
-
serialized_value.each do |key, val|
|
|
279
|
-
xml.create_and_add_element(key) do |element|
|
|
280
|
-
element.text(val)
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
else
|
|
284
|
-
xml.add_text(xml, serialized_value, cdata: cdata)
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Get child plan from parent plan (unified access for both object and hash plans)
|
|
291
|
-
#
|
|
292
|
-
# @param plan [DeclarationPlan, Hash, nil] the parent plan
|
|
293
|
-
# @param attr_name [Symbol] the attribute name
|
|
294
|
-
# @return [DeclarationPlan, Hash, nil] the child plan or nil
|
|
295
|
-
def child_plan_for(plan, attr_name)
|
|
296
|
-
return nil unless plan
|
|
297
|
-
|
|
298
|
-
if plan.respond_to?(:child_plan)
|
|
299
|
-
# DeclarationPlan object (Nokogiri/Oga)
|
|
300
|
-
plan.child_plan(attr_name)
|
|
301
|
-
elsif plan.respond_to?(:[])
|
|
302
|
-
# Hash-based plan (Ox/REXML)
|
|
303
|
-
plan[:children_plans]&.[](attr_name)
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# Build unordered child elements using prepared namespace declaration plan
|
|
308
|
-
#
|
|
309
|
-
# This is the shared implementation for all adapters. Adapters may override
|
|
310
|
-
# if they need custom behavior.
|
|
311
|
-
#
|
|
312
|
-
# @param xml [Builder] the XML builder
|
|
313
|
-
# @param element [Object] the model instance
|
|
314
|
-
# @param plan [DeclarationPlan, Hash] the declaration plan
|
|
315
|
-
# @param options [Hash] serialization options
|
|
316
|
-
def build_unordered_children_with_plan(xml, element, plan, options)
|
|
317
|
-
mapper_class = options[:mapper_class] || element.class
|
|
318
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
319
|
-
|
|
320
|
-
# Process child elements with their plans (INCLUDING raw_mapping for map all)
|
|
321
|
-
mappings = xml_mapping.elements + [xml_mapping.raw_mapping].compact
|
|
322
|
-
mappings.each do |element_rule|
|
|
323
|
-
next if options[:except]&.include?(element_rule.to)
|
|
324
|
-
|
|
325
|
-
# Handle custom methods
|
|
326
|
-
if element_rule.custom_methods[:to]
|
|
327
|
-
mapper_class.new.send(element_rule.custom_methods[:to], element,
|
|
328
|
-
xml.parent, xml)
|
|
329
|
-
next
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
attribute_def = attribute_definition_for(element, element_rule,
|
|
333
|
-
mapper_class: mapper_class)
|
|
334
|
-
|
|
335
|
-
# For delegated attributes, attribute_def might be nil
|
|
336
|
-
next unless attribute_def || element_rule.delegate
|
|
337
|
-
|
|
338
|
-
value = attribute_value_for(element, element_rule)
|
|
339
|
-
next unless element_rule.render?(value, element)
|
|
340
|
-
|
|
341
|
-
# Get child's plan if available
|
|
342
|
-
child_plan = child_plan_for(plan, element_rule.to)
|
|
343
|
-
|
|
344
|
-
# Check if value is a Collection instance
|
|
345
|
-
is_collection_instance = value.is_a?(Lutaml::Model::Collection)
|
|
346
|
-
|
|
347
|
-
if value && (attribute_def&.type(register)&.<=(Lutaml::Model::Serialize) || is_collection_instance)
|
|
348
|
-
handle_nested_elements_with_plan(
|
|
349
|
-
xml,
|
|
350
|
-
value,
|
|
351
|
-
element_rule,
|
|
352
|
-
attribute_def,
|
|
353
|
-
child_plan,
|
|
354
|
-
options,
|
|
355
|
-
parent_plan: plan,
|
|
356
|
-
)
|
|
357
|
-
elsif element_rule.delegate && attribute_def.nil?
|
|
358
|
-
# Handle non-model values (strings, etc.) for delegated attributes
|
|
359
|
-
add_simple_value(xml, element_rule, value, nil, plan: plan,
|
|
360
|
-
mapping: xml_mapping, options: options)
|
|
361
|
-
else
|
|
362
|
-
add_simple_value(xml, element_rule, value, attribute_def,
|
|
363
|
-
plan: plan, mapping: xml_mapping, options: options)
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
# Process content mapping
|
|
368
|
-
process_content_mapping(element, xml_mapping.content_mapping,
|
|
369
|
-
xml, mapper_class)
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
# Build ordered child elements using prepared namespace declaration plan
|
|
373
|
-
#
|
|
374
|
-
# This is the shared implementation for all adapters. Adapters may override
|
|
375
|
-
# if they need custom behavior.
|
|
376
|
-
#
|
|
377
|
-
# @param xml [Builder] the XML builder
|
|
378
|
-
# @param element [Object] the model instance
|
|
379
|
-
# @param plan [DeclarationPlan, Hash] the declaration plan
|
|
380
|
-
# @param options [Hash] serialization options
|
|
381
|
-
def build_ordered_element_with_plan(xml, element, plan, options)
|
|
382
|
-
mapper_class = options[:mapper_class] || element.class
|
|
383
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
384
|
-
|
|
385
|
-
index_hash = {}
|
|
386
|
-
content = []
|
|
387
|
-
|
|
388
|
-
element.element_order.each do |object|
|
|
389
|
-
object_key = "#{object.name}-#{object.type}"
|
|
390
|
-
index_hash[object_key] ||= -1
|
|
391
|
-
curr_index = index_hash[object_key] += 1
|
|
392
|
-
|
|
393
|
-
element_rule = xml_mapping.find_by_name(object.name,
|
|
394
|
-
type: object.type,
|
|
395
|
-
node_type: object.node_type,
|
|
396
|
-
namespace_uri: object.namespace_uri)
|
|
397
|
-
next if element_rule.nil? || options[:except]&.include?(element_rule.to)
|
|
398
|
-
|
|
399
|
-
# Handle custom methods
|
|
400
|
-
if element_rule.custom_methods[:to]
|
|
401
|
-
mapper_class.new.send(element_rule.custom_methods[:to], element,
|
|
402
|
-
xml.parent, xml)
|
|
403
|
-
next
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
# Get attribute definition and value (handle delegation)
|
|
407
|
-
attribute_def, value = fetch_attribute_and_value(element,
|
|
408
|
-
element_rule, mapper_class)
|
|
409
|
-
|
|
410
|
-
next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
|
|
411
|
-
|
|
412
|
-
if element_rule == xml_mapping.content_mapping
|
|
413
|
-
process_ordered_content(element, xml_mapping, xml, curr_index,
|
|
414
|
-
content)
|
|
415
|
-
elsif !value.nil? || element_rule.render_nil?
|
|
416
|
-
process_ordered_element(xml, element, element_rule, attribute_def,
|
|
417
|
-
value, curr_index, plan, xml_mapping, options)
|
|
418
|
-
end
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
add_ordered_content(xml, content) unless content.empty?
|
|
422
|
-
end
|
|
423
|
-
|
|
424
225
|
private
|
|
425
226
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if cdata
|
|
430
|
-
element.add_child(doc.create_cdata(text.to_s))
|
|
227
|
+
def attribute_values(element, &)
|
|
228
|
+
if element.respond_to?(:attributes_each_value)
|
|
229
|
+
element.attributes_each_value(&)
|
|
431
230
|
else
|
|
432
|
-
|
|
231
|
+
element.attributes.each_value(&)
|
|
433
232
|
end
|
|
434
233
|
end
|
|
435
234
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
235
|
+
def schema_location_attribute?(attr)
|
|
236
|
+
attr_name = if attr.respond_to?(:unprefixed_name)
|
|
237
|
+
attr.unprefixed_name
|
|
238
|
+
else
|
|
239
|
+
attr.name
|
|
240
|
+
end
|
|
241
|
+
attr_name == "schemaLocation"
|
|
440
242
|
end
|
|
441
243
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
xml_element.attributes.each_with_index do |xml_attr, idx|
|
|
446
|
-
attr_name_str = xml_attr.name.to_s
|
|
447
|
-
if attr_name_str.start_with?("xmlns")
|
|
448
|
-
apply_xmlns_attribute(attr_name_str, xml_attr.value.to_s,
|
|
449
|
-
element_node, element)
|
|
450
|
-
next
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
attr_node = element_node.attribute_nodes[idx]
|
|
454
|
-
element[attr_node.qualified_name] = xml_attr.value.to_s
|
|
455
|
-
end
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
def apply_xmlns_attribute(attr_name_str, value, element_node, element)
|
|
459
|
-
if attr_name_str.include?(":")
|
|
460
|
-
prefix = attr_name_str.split(":", 2).last
|
|
461
|
-
unless element_node.hoisted_declarations.key?(prefix)
|
|
462
|
-
element.add_namespace(prefix, value)
|
|
463
|
-
end
|
|
464
|
-
elsif attr_name_str == "xmlns"
|
|
465
|
-
unless element_node.hoisted_declarations.key?(nil)
|
|
466
|
-
element.add_namespace(nil, value)
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
# Fetch attribute definition and value, handling delegation
|
|
472
|
-
#
|
|
473
|
-
# @param element [Object] the model instance
|
|
474
|
-
# @param element_rule [MappingRule] the mapping rule
|
|
475
|
-
# @param mapper_class [Class] the mapper class
|
|
476
|
-
# @return [Array<(Attribute, Object)>] attribute definition and value tuple
|
|
477
|
-
def fetch_attribute_and_value(element, element_rule, mapper_class)
|
|
478
|
-
attribute_def = nil
|
|
479
|
-
value = nil
|
|
480
|
-
|
|
481
|
-
if element_rule.delegate
|
|
482
|
-
delegate_obj = element.send(element_rule.delegate)
|
|
483
|
-
if delegate_obj.respond_to?(element_rule.to)
|
|
484
|
-
attribute_def = delegate_obj.class.attributes[element_rule.to]
|
|
485
|
-
value = delegate_obj.send(element_rule.to)
|
|
486
|
-
end
|
|
487
|
-
else
|
|
488
|
-
attribute_def = attribute_definition_for(element, element_rule,
|
|
489
|
-
mapper_class: mapper_class)
|
|
490
|
-
value = attribute_value_for(element, element_rule)
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
[attribute_def, value]
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
# Process content for ordered elements
|
|
497
|
-
#
|
|
498
|
-
# @param element [Object] the model instance
|
|
499
|
-
# @param xml_mapping [Xml::Mapping] the XML mapping
|
|
500
|
-
# @param xml [Builder] the XML builder
|
|
501
|
-
# @param curr_index [Integer] current index in collection
|
|
502
|
-
# @param content [Array] accumulated content strings
|
|
503
|
-
def process_ordered_content(element, xml_mapping, xml, curr_index,
|
|
504
|
-
content)
|
|
505
|
-
text = element.send(xml_mapping.content_mapping.to)
|
|
506
|
-
text = text[curr_index] if text.is_a?(Array)
|
|
507
|
-
|
|
508
|
-
if element.mixed?
|
|
509
|
-
add_mixed_text(xml, text)
|
|
244
|
+
def attribute_namespace_prefix(attr)
|
|
245
|
+
if attr.respond_to?(:namespace_prefix)
|
|
246
|
+
attr.namespace_prefix
|
|
510
247
|
else
|
|
511
|
-
|
|
248
|
+
attr.namespace&.prefix
|
|
512
249
|
end
|
|
513
250
|
end
|
|
514
251
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
# @param element [Object] the model instance
|
|
519
|
-
# @param element_rule [MappingRule] the mapping rule
|
|
520
|
-
# @param attribute_def [Attribute, nil] the attribute definition
|
|
521
|
-
# @param value [Object] the value
|
|
522
|
-
# @param curr_index [Integer] current index in collection
|
|
523
|
-
# @param plan [DeclarationPlan, Hash] the declaration plan
|
|
524
|
-
# @param xml_mapping [Xml::Mapping] the XML mapping
|
|
525
|
-
# @param options [Hash] serialization options
|
|
526
|
-
def process_ordered_element(xml, element, element_rule, attribute_def,
|
|
527
|
-
value, curr_index, plan, xml_mapping, options)
|
|
528
|
-
# Handle collection values by index
|
|
529
|
-
current_value = if attribute_def&.collection? && value.is_a?(Array)
|
|
530
|
-
value[curr_index]
|
|
531
|
-
elsif attribute_def&.collection? && value.is_a?(Lutaml::Model::Collection)
|
|
532
|
-
value.to_a[curr_index]
|
|
533
|
-
else
|
|
534
|
-
value
|
|
535
|
-
end
|
|
536
|
-
|
|
537
|
-
# Get child's plan if available
|
|
538
|
-
child_plan = child_plan_for(plan, element_rule.to)
|
|
539
|
-
|
|
540
|
-
is_collection_instance = current_value.is_a?(Lutaml::Model::Collection)
|
|
541
|
-
|
|
542
|
-
if current_value && (attribute_def&.type(register)&.<=(Lutaml::Model::Serialize) || is_collection_instance)
|
|
543
|
-
handle_nested_elements_with_plan(
|
|
544
|
-
xml,
|
|
545
|
-
current_value,
|
|
546
|
-
element_rule,
|
|
547
|
-
attribute_def,
|
|
548
|
-
child_plan,
|
|
549
|
-
options,
|
|
550
|
-
parent_plan: plan,
|
|
551
|
-
)
|
|
252
|
+
def attribute_hash_name(attr)
|
|
253
|
+
if attr.respond_to?(:namespaced_name)
|
|
254
|
+
attr.namespaced_name
|
|
552
255
|
else
|
|
553
|
-
|
|
554
|
-
if attribute_def
|
|
555
|
-
current_value = ExportTransformer.call(current_value,
|
|
556
|
-
element_rule, attribute_def, format: :xml)
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
# For mixed content, create elements directly
|
|
560
|
-
if element.mixed? && !attribute_def&.raw?
|
|
561
|
-
add_mixed_element(xml, element_rule, current_value, attribute_def,
|
|
562
|
-
plan: plan, mapping: xml_mapping)
|
|
563
|
-
else
|
|
564
|
-
add_simple_value(xml, element_rule, current_value,
|
|
565
|
-
attribute_def, plan: plan, mapping: xml_mapping, options: options)
|
|
566
|
-
end
|
|
567
|
-
end
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
# Add text for mixed content (can be overridden by adapters)
|
|
571
|
-
#
|
|
572
|
-
# @param xml [Builder] the XML builder
|
|
573
|
-
# @param text [String] the text to add
|
|
574
|
-
def add_mixed_text(xml, text)
|
|
575
|
-
# Default implementation - adapters may override
|
|
576
|
-
xml.add_text(xml, text) unless text.nil? || text.to_s.empty?
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
# Add element for mixed content (can be overridden by adapters)
|
|
580
|
-
#
|
|
581
|
-
# @param xml [Builder] the XML builder
|
|
582
|
-
# @param element_rule [MappingRule] the element rule
|
|
583
|
-
# @param value [Object] the value to add
|
|
584
|
-
# @param attribute [Attribute, nil] the attribute definition
|
|
585
|
-
# @param plan [DeclarationPlan, Hash, nil] the declaration plan
|
|
586
|
-
# @param mapping [Xml::Mapping] the XML mapping
|
|
587
|
-
def add_mixed_element(xml, element_rule, value, _attribute, _plan:,
|
|
588
|
-
_mapping:)
|
|
589
|
-
# Default implementation - adapters may override
|
|
590
|
-
xml.create_and_add_element(element_rule.name) do |child_element|
|
|
591
|
-
child_element.text(value.to_s) unless ::Lutaml::Model::Utils.empty?(value)
|
|
592
|
-
end
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
# Add accumulated content (can be overridden by adapters)
|
|
596
|
-
#
|
|
597
|
-
# @param xml [Builder] the XML builder
|
|
598
|
-
# @param content [Array<String>] accumulated content strings
|
|
599
|
-
def add_ordered_content(xml, content)
|
|
600
|
-
# Default implementation - adapters may override
|
|
601
|
-
xml.add_text(xml, content.join)
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
# Collect original namespace URIs from a model tree for namespace alias support.
|
|
605
|
-
#
|
|
606
|
-
# When parsing XML with alias URIs (e.g., "http://.../") against a namespace
|
|
607
|
-
# class with canonical URI (e.g., "http://.../reqif.xsd"), the original alias
|
|
608
|
-
# URI is stored on the model instance as @__xml_original_namespace_uri.
|
|
609
|
-
# This method collects all such mappings from the model tree.
|
|
610
|
-
#
|
|
611
|
-
# @param model [Object] the model instance to walk
|
|
612
|
-
# @param mapping [Xml::Mapping, nil] the mapping for the model
|
|
613
|
-
# @return [Hash<String, String>] Mapping of canonical URI => original alias URI
|
|
614
|
-
def collect_original_namespace_uris(model, mapping = nil)
|
|
615
|
-
original_uris = {}
|
|
616
|
-
return original_uris unless model
|
|
617
|
-
|
|
618
|
-
collect_from_model(model, mapping, original_uris, Set.new)
|
|
619
|
-
original_uris
|
|
620
|
-
end
|
|
621
|
-
|
|
622
|
-
# Recursively walk model tree to collect original namespace URIs
|
|
623
|
-
def collect_from_model(model, mapping, original_uris, visited)
|
|
624
|
-
return unless model.is_a?(::Lutaml::Model::Serialize)
|
|
625
|
-
return if visited.include?(model.object_id)
|
|
626
|
-
|
|
627
|
-
visited.add(model.object_id)
|
|
628
|
-
|
|
629
|
-
# Check if this model has an original namespace URI
|
|
630
|
-
if model.respond_to?(:original_namespace_uri) && model.original_namespace_uri
|
|
631
|
-
original_uri = model.original_namespace_uri
|
|
632
|
-
if original_uri && !original_uri.empty?
|
|
633
|
-
# Look up the model's namespace class
|
|
634
|
-
ns_class = model.class.mappings_for(:xml)&.namespace_class
|
|
635
|
-
if ns_class && ns_class.uri != original_uri
|
|
636
|
-
# Only store if the canonical URI differs (it's an alias)
|
|
637
|
-
original_uris[ns_class.uri] = original_uri
|
|
638
|
-
end
|
|
639
|
-
end
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
return unless mapping
|
|
643
|
-
|
|
644
|
-
# Recurse into child Serializable attributes
|
|
645
|
-
attributes = model.class.attributes
|
|
646
|
-
mapping.elements.each do |elem_rule|
|
|
647
|
-
attr_def = attributes[elem_rule.to]
|
|
648
|
-
next unless attr_def
|
|
649
|
-
|
|
650
|
-
child_type = attr_def.type(Lutaml::Model::Config.default_register)
|
|
651
|
-
next unless child_type.respond_to?(:<) && child_type < ::Lutaml::Model::Serializable
|
|
652
|
-
|
|
653
|
-
child_mapping = child_type.mappings_for(:xml)
|
|
654
|
-
next unless child_mapping
|
|
655
|
-
|
|
656
|
-
child_instance = model.public_send(elem_rule.to) if model.respond_to?(elem_rule.to)
|
|
657
|
-
|
|
658
|
-
if child_instance.is_a?(Array) || child_instance.is_a?(::Lutaml::Model::Collection)
|
|
659
|
-
instances = child_instance.is_a?(::Lutaml::Model::Collection) ? child_instance.collection : child_instance
|
|
660
|
-
instances.each do |item|
|
|
661
|
-
collect_from_model(item, child_mapping, original_uris, visited)
|
|
662
|
-
end
|
|
663
|
-
elsif child_instance
|
|
664
|
-
collect_from_model(child_instance, child_mapping, original_uris,
|
|
665
|
-
visited)
|
|
666
|
-
end
|
|
256
|
+
self.class.namespaced_attr_name(attr)
|
|
667
257
|
end
|
|
668
258
|
end
|
|
669
259
|
end
|
|
@@ -30,23 +30,6 @@ module Lutaml
|
|
|
30
30
|
@prefix = normalize_prefix(prefix)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
# Generate unique key for this namespace configuration
|
|
34
|
-
#
|
|
35
|
-
# The key is based on prefix and URI, ensuring that same config = same key.
|
|
36
|
-
# This enables proper deduplication and lookup in hash structures.
|
|
37
|
-
#
|
|
38
|
-
# @return [String] unique key in format "prefix:uri" or ":uri" for default
|
|
39
|
-
def self.to_key
|
|
40
|
-
prefix = prefix_default
|
|
41
|
-
uri = self.uri
|
|
42
|
-
|
|
43
|
-
if prefix && !prefix.empty?
|
|
44
|
-
"#{prefix}:#{uri}"
|
|
45
|
-
else
|
|
46
|
-
":#{uri}"
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
33
|
def normalize_prefix(prefix)
|
|
51
34
|
# Only strip "xmlns:" prefix (e.g., "xmlns:foo" → "foo").
|
|
52
35
|
# Do NOT strip "xmlns" from prefixes like "xmlns_1.0" (valid NCName).
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Xml
|
|
5
|
+
module Adapter
|
|
6
|
+
# Collects original namespace URIs from a model tree for namespace alias support.
|
|
7
|
+
#
|
|
8
|
+
# When parsing XML with alias URIs (e.g., "http://.../") against a namespace
|
|
9
|
+
# class with canonical URI (e.g., "http://.../reqif.xsd"), the original alias
|
|
10
|
+
# URI is stored on the model instance as @__xml_original_namespace_uri.
|
|
11
|
+
# This module collects all such mappings from the model tree.
|
|
12
|
+
module NamespaceUriCollector
|
|
13
|
+
# @param model [Object] the model instance to walk
|
|
14
|
+
# @param mapping [Xml::Mapping, nil] the mapping for the model
|
|
15
|
+
# @return [Hash<String, String>] Mapping of canonical URI => original alias URI
|
|
16
|
+
def collect_original_namespace_uris(model, mapping = nil)
|
|
17
|
+
original_uris = {}
|
|
18
|
+
return original_uris unless model
|
|
19
|
+
|
|
20
|
+
collect_from_model(model, mapping, original_uris, Set.new)
|
|
21
|
+
original_uris
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def collect_from_model(model, mapping, original_uris, visited)
|
|
27
|
+
return unless model.is_a?(::Lutaml::Model::Serialize)
|
|
28
|
+
return if visited.include?(model.object_id)
|
|
29
|
+
|
|
30
|
+
visited.add(model.object_id)
|
|
31
|
+
|
|
32
|
+
if model.respond_to?(:original_namespace_uri) && model.original_namespace_uri
|
|
33
|
+
original_uri = model.original_namespace_uri
|
|
34
|
+
if original_uri && !original_uri.empty?
|
|
35
|
+
ns_class = model.class.mappings_for(:xml)&.namespace_class
|
|
36
|
+
if ns_class && ns_class.uri != original_uri
|
|
37
|
+
original_uris[ns_class.uri] = original_uri
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return unless mapping
|
|
43
|
+
|
|
44
|
+
attributes = model.class.attributes
|
|
45
|
+
mapping.elements.each do |elem_rule|
|
|
46
|
+
attr_def = attributes[elem_rule.to]
|
|
47
|
+
next unless attr_def
|
|
48
|
+
|
|
49
|
+
child_type = attr_def.type(Lutaml::Model::Config.default_register)
|
|
50
|
+
next unless child_type.respond_to?(:<) && child_type < ::Lutaml::Model::Serializable
|
|
51
|
+
|
|
52
|
+
child_mapping = child_type.mappings_for(:xml)
|
|
53
|
+
next unless child_mapping
|
|
54
|
+
|
|
55
|
+
child_instance = model.public_send(elem_rule.to) if model.respond_to?(elem_rule.to)
|
|
56
|
+
|
|
57
|
+
if child_instance.is_a?(Array) || child_instance.is_a?(::Lutaml::Model::Collection)
|
|
58
|
+
instances = child_instance.is_a?(::Lutaml::Model::Collection) ? child_instance.collection : child_instance
|
|
59
|
+
instances.each do |item|
|
|
60
|
+
collect_from_model(item, child_mapping, original_uris, visited)
|
|
61
|
+
end
|
|
62
|
+
elsif child_instance
|
|
63
|
+
collect_from_model(child_instance, child_mapping, original_uris,
|
|
64
|
+
visited)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|