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
|
@@ -1,893 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "moxml/adapter/ox"
|
|
4
|
+
require_relative "base_adapter"
|
|
2
5
|
|
|
3
6
|
module Lutaml
|
|
4
7
|
module Xml
|
|
5
8
|
module Adapter
|
|
6
9
|
class OxAdapter < BaseAdapter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def self.parse(xml, options = {})
|
|
13
|
-
enc = encoding(xml, options)
|
|
14
|
-
|
|
15
|
-
parsed = begin
|
|
16
|
-
Moxml::Adapter::Ox.parse(xml, encoding: enc)
|
|
17
|
-
rescue Moxml::ParseError => e
|
|
18
|
-
raise Lutaml::Model::InvalidFormatError.new(
|
|
19
|
-
:xml,
|
|
20
|
-
e.message,
|
|
21
|
-
)
|
|
22
|
-
end
|
|
23
|
-
root_element = parsed.root
|
|
24
|
-
|
|
25
|
-
# Validate that we have a root element
|
|
26
|
-
if root_element.nil?
|
|
27
|
-
raise Lutaml::Model::InvalidFormatError.new(
|
|
28
|
-
:xml,
|
|
29
|
-
"Document has no root element. " \
|
|
30
|
-
"The XML may be empty, contain only whitespace, " \
|
|
31
|
-
"or consist only of an XML declaration.",
|
|
32
|
-
)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Extract DOCTYPE information if present
|
|
36
|
-
# Moxml/Ox doesn't directly expose DOCTYPE, extract from raw XML
|
|
37
|
-
doctype_info = extract_doctype_from_xml(xml)
|
|
38
|
-
|
|
39
|
-
@root = Ox::Element.new(root_element)
|
|
40
|
-
@root.processing_instructions = extract_document_processing_instructions(parsed)
|
|
41
|
-
new(@root, enc, doctype: doctype_info)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def to_xml(options = {})
|
|
45
|
-
# Accept xml_declaration from options if present (for model serialization)
|
|
46
|
-
@xml_declaration = options[:xml_declaration] if options[:xml_declaration]
|
|
47
|
-
|
|
48
|
-
builder_options = {}
|
|
49
|
-
encoding = determine_encoding(options)
|
|
50
|
-
builder_options[:encoding] = encoding if encoding
|
|
51
|
-
|
|
52
|
-
builder = Builder::Ox.build(builder_options) do |xml|
|
|
53
|
-
if @root.is_a?(Ox::Element)
|
|
54
|
-
@root.build_xml(xml)
|
|
55
|
-
else
|
|
56
|
-
original_model = nil
|
|
57
|
-
|
|
58
|
-
xml_element = if @root.is_a?(Lutaml::Xml::DataModel::XmlElement)
|
|
59
|
-
@root
|
|
60
|
-
else
|
|
61
|
-
mapper_class = options[:mapper_class] || @root.class
|
|
62
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
63
|
-
|
|
64
|
-
has_custom_map_all = xml_mapping.raw_mapping&.custom_methods &&
|
|
65
|
-
xml_mapping.raw_mapping.custom_methods[:to]
|
|
66
|
-
|
|
67
|
-
if has_custom_map_all
|
|
68
|
-
nil
|
|
69
|
-
else
|
|
70
|
-
original_model = @root
|
|
71
|
-
transformation = mapper_class.transformation_for(
|
|
72
|
-
:xml, register
|
|
73
|
-
)
|
|
74
|
-
transformation.transform(@root, options)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
if xml_element
|
|
79
|
-
mapper_class = options[:mapper_class] || xml_element.class
|
|
80
|
-
mapping = mapper_class.mappings_for(:xml)
|
|
81
|
-
|
|
82
|
-
collector = NamespaceCollector.new(register)
|
|
83
|
-
needs = collector.collect(xml_element, mapping,
|
|
84
|
-
mapper_class: mapper_class)
|
|
85
|
-
|
|
86
|
-
planner = DeclarationPlanner.new(register)
|
|
87
|
-
plan = planner.plan(xml_element, mapping, needs,
|
|
88
|
-
options: options)
|
|
89
|
-
|
|
90
|
-
render_options = options.merge(is_root_element: true)
|
|
91
|
-
if original_model
|
|
92
|
-
render_options[:original_model] =
|
|
93
|
-
original_model
|
|
94
|
-
end
|
|
95
|
-
build_xml_element_with_plan(xml, xml_element, plan,
|
|
96
|
-
render_options)
|
|
97
|
-
else
|
|
98
|
-
mapper_class = options[:mapper_class] || @root.class
|
|
99
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
100
|
-
|
|
101
|
-
collector = NamespaceCollector.new(register)
|
|
102
|
-
needs = collector.collect(@root, xml_mapping)
|
|
103
|
-
|
|
104
|
-
planner = DeclarationPlanner.new(register)
|
|
105
|
-
plan = planner.plan(@root, xml_mapping, needs, options: options)
|
|
106
|
-
|
|
107
|
-
build_element_with_plan(xml, @root, plan, options)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
xml_data = builder.to_xml
|
|
113
|
-
|
|
114
|
-
result = ""
|
|
115
|
-
# Use DeclarationHandler methods instead of Document#declaration
|
|
116
|
-
# Include declaration when encoding is specified OR when declaration is requested
|
|
117
|
-
if (options[:encoding] && !options[:encoding].nil?) || options[:declaration]
|
|
118
|
-
result += generate_declaration(options)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Add DOCTYPE if present - use DeclarationHandler method
|
|
122
|
-
doctype_to_use = options[:doctype] || @doctype
|
|
123
|
-
if doctype_to_use && !options[:omit_doctype]
|
|
124
|
-
result += generate_doctype_declaration(doctype_to_use)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
result += xml_data
|
|
128
|
-
result
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Build XML from XmlDataModel::XmlElement using DeclarationPlan tree (PARALLEL TRAVERSAL)
|
|
132
|
-
#
|
|
133
|
-
# @param builder [Builder::Ox] XML builder
|
|
134
|
-
# @param xml_element [XmlDataModel::XmlElement] Element content
|
|
135
|
-
# @param plan [DeclarationPlan] Declaration plan with tree structure
|
|
136
|
-
# @param options [Hash] Serialization options
|
|
137
|
-
def build_xml_element_with_plan(builder, xml_element, plan,
|
|
138
|
-
_options = {})
|
|
139
|
-
# Add processing instructions before root element
|
|
140
|
-
xml_element.processing_instructions.each do |pi|
|
|
141
|
-
builder.add_processing_instruction(pi.target, pi.content)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
build_ox_node(builder, xml_element, plan.root_node,
|
|
145
|
-
plan.global_prefix_registry, plan: plan)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
private
|
|
149
|
-
|
|
150
|
-
# Recursively build XML element tree manually (PARALLEL TRAVERSAL)
|
|
151
|
-
#
|
|
152
|
-
# @param xml [Builder::Ox] XML builder
|
|
153
|
-
# @param xml_element [XmlDataModel::XmlElement] Content
|
|
154
|
-
# @param element_node [ElementNode] Decisions
|
|
155
|
-
# @param global_registry [Hash] Global prefix registry (URI => prefix)
|
|
156
|
-
# @param plan [DeclarationPlan] Declaration plan with original namespace URIs
|
|
157
|
-
# @return [void]
|
|
158
|
-
def build_ox_node(xml, xml_element, element_node, global_registry,
|
|
159
|
-
plan: nil)
|
|
160
|
-
qualified_name = element_node.qualified_name
|
|
161
|
-
|
|
162
|
-
# 1. Collect attributes (xmlns declarations + regular attributes)
|
|
163
|
-
attributes = {}
|
|
164
|
-
|
|
165
|
-
# 2. Add hoisted xmlns declarations
|
|
166
|
-
original_ns_uris = plan&.original_namespace_uris || {}
|
|
167
|
-
element_node.hoisted_declarations.each do |key, uri|
|
|
168
|
-
next if uri == "http://www.w3.org/XML/1998/namespace"
|
|
169
|
-
|
|
170
|
-
# Convert FPI to URN if necessary (Ox requires valid URI)
|
|
171
|
-
effective_uri = if self.class.fpi?(uri)
|
|
172
|
-
self.class.fpi_to_urn(uri)
|
|
173
|
-
else
|
|
174
|
-
original_ns_uris[uri] || uri
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
xmlns_name = key ? "xmlns:#{key}" : "xmlns"
|
|
178
|
-
attributes[xmlns_name] = effective_uri
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# 3. Add regular attributes by INDEX (PARALLEL TRAVERSAL)
|
|
182
|
-
xml_element.attributes.each_with_index do |xml_attr, idx|
|
|
183
|
-
attr_node = element_node.attribute_nodes[idx]
|
|
184
|
-
attributes[attr_node.qualified_name] = xml_attr.value.to_s
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Check for xsi:nil
|
|
188
|
-
if xml_element.respond_to?(:xsi_nil) && xml_element.xsi_nil
|
|
189
|
-
attributes["xsi:nil"] = "true"
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Add schema_location attribute from ElementNode if present
|
|
193
|
-
attributes.merge!(element_node.schema_location_attr) if element_node.schema_location_attr
|
|
194
|
-
|
|
195
|
-
# 4. Add xmlns="" if element needs to opt out of parent's default namespace
|
|
196
|
-
if element_node.needs_xmlns_blank
|
|
197
|
-
attributes["xmlns"] = ""
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# 5. Create element with qualified name using block for proper nesting
|
|
201
|
-
xml.create_and_add_element(qualified_name, attributes: attributes) do
|
|
202
|
-
# 6. Handle raw content (map_all directive)
|
|
203
|
-
if xml_element.respond_to?(:raw_content)
|
|
204
|
-
raw_content = xml_element.raw_content
|
|
205
|
-
if raw_content && !raw_content.to_s.empty?
|
|
206
|
-
xml.add_xml_fragment(xml, raw_content.to_s)
|
|
207
|
-
return
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# 7. Add text content if present
|
|
212
|
-
if xml_element.text_content
|
|
213
|
-
if xml_element.cdata
|
|
214
|
-
xml.cdata(xml_element.text_content.to_s)
|
|
215
|
-
else
|
|
216
|
-
xml.text(xml_element.text_content.to_s)
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# 8. Recursively build children by INDEX (PARALLEL TRAVERSAL)
|
|
221
|
-
child_element_index = 0
|
|
222
|
-
xml_element.children.each do |xml_child|
|
|
223
|
-
case xml_child
|
|
224
|
-
when Lutaml::Xml::DataModel::XmlElement
|
|
225
|
-
child_node = element_node.element_nodes[child_element_index]
|
|
226
|
-
child_element_index += 1
|
|
227
|
-
|
|
228
|
-
build_ox_node(xml, xml_child, child_node, global_registry,
|
|
229
|
-
plan: plan)
|
|
230
|
-
when String
|
|
231
|
-
xml.text(xml_child)
|
|
232
|
-
when ::Lutaml::Xml::DataModel::XmlComment
|
|
233
|
-
xml.comment(xml_child.content)
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
public
|
|
240
|
-
|
|
241
|
-
# Build element using prepared namespace declaration plan
|
|
242
|
-
#
|
|
243
|
-
# @param xml [Builder] the XML builder
|
|
244
|
-
# @param element [Object] the model instance
|
|
245
|
-
# @param plan [Hash] the declaration plan from DeclarationPlanner
|
|
246
|
-
# @param options [Hash] serialization options
|
|
247
|
-
def build_element_with_plan(xml, element, plan, options = {})
|
|
248
|
-
mapper_class = options[:mapper_class] || element.class
|
|
249
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
250
|
-
return xml unless xml_mapping
|
|
251
|
-
|
|
252
|
-
# TYPE-ONLY MODELS: No element wrapper, serialize children directly
|
|
253
|
-
# BUT if we have a tag_name in options, that means parent wants a wrapper
|
|
254
|
-
plan ||= DeclarationPlan.empty
|
|
255
|
-
|
|
256
|
-
if xml_mapping.no_element?
|
|
257
|
-
# If parent provided a tag_name, create that wrapper first
|
|
258
|
-
if options[:tag_name]
|
|
259
|
-
xml.create_and_add_element(options[:tag_name]) do |inner_xml|
|
|
260
|
-
# Serialize type-only model's children inside parent's wrapper
|
|
261
|
-
xml_mapping.elements.each do |element_rule|
|
|
262
|
-
next if options[:except]&.include?(element_rule.to)
|
|
263
|
-
|
|
264
|
-
attribute_def = mapper_class.attributes[element_rule.to]
|
|
265
|
-
next unless attribute_def
|
|
266
|
-
|
|
267
|
-
value = element.send(element_rule.to)
|
|
268
|
-
next unless element_rule.render?(value, element)
|
|
269
|
-
|
|
270
|
-
# For type-only models, children plans may not be available
|
|
271
|
-
# Serialize children directly
|
|
272
|
-
if value && attribute_def.type(register)&.<=(Lutaml::Model::Serialize)
|
|
273
|
-
# Nested model - recursively build it
|
|
274
|
-
child_plan = plan.child_plan(element_rule.to) || DeclarationPlan.empty
|
|
275
|
-
build_element_with_plan(
|
|
276
|
-
inner_xml,
|
|
277
|
-
value,
|
|
278
|
-
child_plan,
|
|
279
|
-
{ mapper_class: attribute_def.type(register),
|
|
280
|
-
tag_name: element_rule.name },
|
|
281
|
-
)
|
|
282
|
-
else
|
|
283
|
-
# Simple value - create element directly
|
|
284
|
-
inner_xml.create_and_add_element(element_rule.name) do
|
|
285
|
-
add_value(inner_xml, value, attribute_def,
|
|
286
|
-
cdata: element_rule.cdata)
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
else
|
|
292
|
-
# No wrapper at all - serialize children directly (for root-level type-only)
|
|
293
|
-
xml_mapping.elements.each do |element_rule|
|
|
294
|
-
next if options[:except]&.include?(element_rule.to)
|
|
295
|
-
|
|
296
|
-
attribute_def = mapper_class.attributes[element_rule.to]
|
|
297
|
-
next unless attribute_def
|
|
298
|
-
|
|
299
|
-
value = element.send(element_rule.to)
|
|
300
|
-
next unless element_rule.render?(value, element)
|
|
301
|
-
|
|
302
|
-
child_plan = plan.child_plan(element_rule.to)
|
|
303
|
-
|
|
304
|
-
if value && attribute_def.type(register)&.<=(Lutaml::Model::Serialize)
|
|
305
|
-
handle_nested_elements_with_plan(
|
|
306
|
-
xml,
|
|
307
|
-
value,
|
|
308
|
-
element_rule,
|
|
309
|
-
attribute_def,
|
|
310
|
-
child_plan,
|
|
311
|
-
options,
|
|
312
|
-
)
|
|
313
|
-
else
|
|
314
|
-
add_simple_value(xml, element_rule, value, attribute_def,
|
|
315
|
-
plan: plan, mapping: xml_mapping, options: options)
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
return xml
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
# Use xmlns declarations from plan
|
|
323
|
-
attributes = {}
|
|
324
|
-
|
|
325
|
-
# Apply namespace declarations from plan using extracted module
|
|
326
|
-
attributes.merge!(NamespaceDeclarationBuilder.build_xmlns_attributes(plan))
|
|
327
|
-
|
|
328
|
-
# Collect attribute custom methods to call after element creation
|
|
329
|
-
attribute_custom_methods = []
|
|
330
|
-
|
|
331
|
-
# Add regular attributes (non-xmlns)
|
|
332
|
-
xml_mapping.attributes.each do |attribute_rule|
|
|
333
|
-
next if options[:except]&.include?(attribute_rule.to)
|
|
334
|
-
|
|
335
|
-
# Collect custom methods for later execution (after element is created)
|
|
336
|
-
if attribute_rule.custom_methods[:to]
|
|
337
|
-
attribute_custom_methods << attribute_rule
|
|
338
|
-
next
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
mapping_rule_name = if attribute_rule.multiple_mappings?
|
|
342
|
-
attribute_rule.name.first
|
|
343
|
-
else
|
|
344
|
-
attribute_rule.name
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
attr = attribute_definition_for(element, attribute_rule,
|
|
348
|
-
mapper_class: mapper_class)
|
|
349
|
-
value = attribute_rule.to_value_for(element)
|
|
350
|
-
|
|
351
|
-
# Handle as_list and delimiter BEFORE serialization for array values
|
|
352
|
-
# These features convert arrays to delimited strings before serialization
|
|
353
|
-
if value.is_a?(Array)
|
|
354
|
-
if attribute_rule.as_list && attribute_rule.as_list[:export]
|
|
355
|
-
value = attribute_rule.as_list[:export].call(value)
|
|
356
|
-
elsif attribute_rule.delimiter
|
|
357
|
-
value = value.join(attribute_rule.delimiter)
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
value = attr.serialize(value, :xml, register) if attr
|
|
362
|
-
value = ExportTransformer.call(value, attribute_rule, attr,
|
|
363
|
-
format: :xml)
|
|
364
|
-
|
|
365
|
-
if render_element?(attribute_rule, element, value)
|
|
366
|
-
# Resolve attribute namespace using extracted module
|
|
367
|
-
ns_info = AttributeNamespaceResolver.resolve(
|
|
368
|
-
rule: attribute_rule,
|
|
369
|
-
attribute: attr,
|
|
370
|
-
plan: plan,
|
|
371
|
-
mapper_class: mapper_class,
|
|
372
|
-
register: register,
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
# Build qualified attribute name based on W3C semantics
|
|
376
|
-
attr_name = AttributeNamespaceResolver.build_qualified_name(
|
|
377
|
-
ns_info,
|
|
378
|
-
mapping_rule_name,
|
|
379
|
-
attribute_rule,
|
|
380
|
-
)
|
|
381
|
-
attributes[attr_name] = value ? value.to_s : value
|
|
382
|
-
|
|
383
|
-
# Add local xmlns declaration if needed
|
|
384
|
-
if ns_info[:needs_local_declaration]
|
|
385
|
-
attributes[ns_info[:local_xmlns_attr]] =
|
|
386
|
-
ns_info[:local_xmlns_uri]
|
|
387
|
-
end
|
|
388
|
-
end
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
# Add schema_location attribute from ElementNode if present
|
|
392
|
-
# This is for the plan-based path where schema_location_attr is computed during planning
|
|
393
|
-
attributes.merge!(plan.root_node.schema_location_attr) if plan&.root_node&.schema_location_attr
|
|
394
|
-
|
|
395
|
-
# Determine prefix from plan using extracted module
|
|
396
|
-
prefix_info = ElementPrefixResolver.resolve(mapping: xml_mapping,
|
|
397
|
-
plan: plan)
|
|
398
|
-
prefix = prefix_info[:prefix]
|
|
399
|
-
|
|
400
|
-
tag_name = options[:tag_name] || xml_mapping.root_element
|
|
401
|
-
return if options[:except]&.include?(tag_name)
|
|
402
|
-
|
|
403
|
-
xml.create_and_add_element(tag_name, prefix: prefix,
|
|
404
|
-
attributes: attributes.compact) do |el|
|
|
405
|
-
# Call attribute custom methods now that element is created
|
|
406
|
-
attribute_custom_methods.each do |attribute_rule|
|
|
407
|
-
mapper_class.new.send(attribute_rule.custom_methods[:to],
|
|
408
|
-
element, el.parent, el)
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
if ordered?(element, options.merge(mapper_class: mapper_class))
|
|
412
|
-
build_ordered_element_with_plan(el, element, plan,
|
|
413
|
-
options.merge(
|
|
414
|
-
mapper_class: mapper_class,
|
|
415
|
-
parent_ns_decl: prefix_info[:ns_decl],
|
|
416
|
-
))
|
|
417
|
-
else
|
|
418
|
-
build_unordered_children_with_plan(el, element, plan,
|
|
419
|
-
options.merge(
|
|
420
|
-
mapper_class: mapper_class,
|
|
421
|
-
parent_ns_decl: prefix_info[:ns_decl],
|
|
422
|
-
))
|
|
423
|
-
end
|
|
424
|
-
end
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
# Build XML from XmlDataModel::XmlElement structure
|
|
428
|
-
#
|
|
429
|
-
# @param xml [Builder] XML builder
|
|
430
|
-
# @param element [XmlDataModel::XmlElement] element to build
|
|
431
|
-
# @param parent_uses_default_ns [Boolean] parent uses default namespace format
|
|
432
|
-
# @param parent_element_form_default [Symbol] parent's element_form_default
|
|
433
|
-
# @param parent_namespace_class [Class] parent's namespace class
|
|
434
|
-
# @param plan [DeclarationPlan, nil] optional declaration plan for xmlns=""
|
|
435
|
-
# @param xml_mapping [Xml::Mapping] optional mapping for namespace resolution
|
|
436
|
-
def build_xml_element(xml, element, parent_uses_default_ns: false,
|
|
437
|
-
parent_element_form_default: nil, parent_namespace_class: nil, plan: nil, xml_mapping: nil)
|
|
438
|
-
# Prepare attributes hash
|
|
439
|
-
attributes = {}
|
|
440
|
-
|
|
441
|
-
# Get element's namespace class
|
|
442
|
-
element_ns_class = element.namespace_class
|
|
443
|
-
attribute_form_default = element_ns_class&.attribute_form_default || :unqualified
|
|
444
|
-
element_prefix = element_ns_class&.prefix_default
|
|
445
|
-
|
|
446
|
-
# Get element_form_default for children
|
|
447
|
-
this_element_form_default = element_ns_class&.element_form_default || :unqualified
|
|
448
|
-
|
|
449
|
-
# Add regular attributes
|
|
450
|
-
element.attributes.each do |attr|
|
|
451
|
-
# Determine attribute name with namespace consideration
|
|
452
|
-
attr_name = if attr.namespace_class
|
|
453
|
-
# Check if attribute is in SAME namespace as element
|
|
454
|
-
if attr.namespace_class == element_ns_class && attribute_form_default == :unqualified
|
|
455
|
-
# Same namespace + unqualified → NO prefix (W3C rule)
|
|
456
|
-
attr.name
|
|
457
|
-
else
|
|
458
|
-
# Different namespace OR qualified → use prefix
|
|
459
|
-
attr_prefix = attr.namespace_class.prefix_default
|
|
460
|
-
attr_prefix ? "#{attr_prefix}:#{attr.name}" : attr.name
|
|
461
|
-
end
|
|
462
|
-
elsif attribute_form_default == :qualified && element_prefix
|
|
463
|
-
# Attribute inherits element's namespace when qualified
|
|
464
|
-
"#{element_prefix}:#{attr.name}"
|
|
465
|
-
else
|
|
466
|
-
# Unqualified attribute
|
|
467
|
-
attr.name
|
|
468
|
-
end
|
|
469
|
-
# Ensure attribute value is a string for Ox
|
|
470
|
-
attributes[attr_name] = attr.value.to_s
|
|
471
|
-
end
|
|
472
|
-
|
|
473
|
-
# Determine element name with namespace prefix
|
|
474
|
-
tag_name = element.name
|
|
475
|
-
|
|
476
|
-
# Priority 2.5: Child namespace different from parent's default namespace
|
|
477
|
-
# MUST use prefix format to distinguish from parent
|
|
478
|
-
child_needs_prefix = if element_ns_class && parent_namespace_class &&
|
|
479
|
-
element_ns_class != parent_namespace_class && parent_uses_default_ns
|
|
480
|
-
element_prefix # Use child's prefix
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
# FIX: Read prefix from plan if available, otherwise use fallback logic
|
|
484
|
-
prefix = if child_needs_prefix
|
|
485
|
-
# Priority 2.5 takes precedence
|
|
486
|
-
child_needs_prefix
|
|
487
|
-
elsif plan && element_ns_class
|
|
488
|
-
# Read format decision from DeclarationPlan
|
|
489
|
-
ns_info = ElementPrefixResolver.resolve(
|
|
490
|
-
mapping: xml_mapping,
|
|
491
|
-
plan: plan,
|
|
492
|
-
)
|
|
493
|
-
ns_info[:prefix]
|
|
494
|
-
elsif element_ns_class && element_prefix
|
|
495
|
-
# Fallback: Element has explicit prefix_default - use prefix format
|
|
496
|
-
element_prefix
|
|
497
|
-
else
|
|
498
|
-
# Fallback: No prefix (default format or no namespace)
|
|
499
|
-
# CRITICAL: Child with NO namespace should NEVER get parent's prefix
|
|
500
|
-
nil
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
# Track if THIS element uses default namespace format for children
|
|
504
|
-
this_element_uses_default_ns = false
|
|
505
|
-
|
|
506
|
-
# Add namespace declaration if element has namespace
|
|
507
|
-
if element.namespace_class
|
|
508
|
-
ns_uri = element.namespace_class.uri
|
|
509
|
-
|
|
510
|
-
# Check if namespace is already declared by parent (hoisting optimization)
|
|
511
|
-
# This works for BOTH default and prefix format parents
|
|
512
|
-
ns_already_declared = parent_namespace_class && parent_namespace_class.uri == ns_uri
|
|
513
|
-
|
|
514
|
-
if prefix && !ns_already_declared
|
|
515
|
-
attributes["xmlns:#{prefix}"] = ns_uri
|
|
516
|
-
# W3C Compliance: xmlns="" only needed for blank namespace children
|
|
517
|
-
# Prefixed children are already in different namespace from parent's default
|
|
518
|
-
elsif !prefix && !ns_already_declared
|
|
519
|
-
attributes["xmlns"] = ns_uri
|
|
520
|
-
this_element_uses_default_ns = true
|
|
521
|
-
end
|
|
522
|
-
elsif plan && DeclarationPlanQuery.element_needs_xmlns_blank?(plan,
|
|
523
|
-
element)
|
|
524
|
-
# W3C Compliance: Element has no namespace (blank namespace)
|
|
525
|
-
# Check if DeclarationPlan says this element needs xmlns=""
|
|
526
|
-
# The planner already determined this based on W3C semantics during planning phase
|
|
527
|
-
attributes["xmlns"] = ""
|
|
528
|
-
elsif !plan
|
|
529
|
-
# Fallback logic when no plan is available
|
|
530
|
-
# Check if should inherit parent's namespace based on element_form_default
|
|
531
|
-
if parent_uses_default_ns
|
|
532
|
-
# Parent uses default namespace format
|
|
533
|
-
if parent_element_form_default == :qualified
|
|
534
|
-
# Child should INHERIT parent's namespace - no xmlns="" needed
|
|
535
|
-
# The child is in same namespace as parent (qualified)
|
|
536
|
-
else
|
|
537
|
-
# Parent's element_form_default is :unqualified - child in blank namespace
|
|
538
|
-
# Add xmlns="" to explicitly opt out of parent's default namespace
|
|
539
|
-
attributes["xmlns"] = ""
|
|
540
|
-
end
|
|
541
|
-
end
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
# Check if element was created from nil value with render_nil option
|
|
545
|
-
# Add xsi:nil="true" attribute for W3C compliance
|
|
546
|
-
if element.respond_to?(:xsi_nil) && element.xsi_nil
|
|
547
|
-
attributes["xsi:nil"] = true
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
# Create element
|
|
551
|
-
xml.create_and_add_element(tag_name, attributes: attributes,
|
|
552
|
-
prefix: prefix) do |inner_xml|
|
|
553
|
-
# Handle raw content (map_all directive)
|
|
554
|
-
has_raw_content = false
|
|
555
|
-
if element.respond_to?(:raw_content)
|
|
556
|
-
raw_content = element.raw_content
|
|
557
|
-
if raw_content && !raw_content.to_s.empty?
|
|
558
|
-
inner_xml.add_xml_fragment(inner_xml, raw_content.to_s)
|
|
559
|
-
has_raw_content = true
|
|
560
|
-
end
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
# Skip text content and children if we have raw content
|
|
564
|
-
unless has_raw_content
|
|
565
|
-
# Add text content if present
|
|
566
|
-
if element.text_content
|
|
567
|
-
if element.cdata
|
|
568
|
-
inner_xml.cdata(element.text_content.to_s)
|
|
569
|
-
else
|
|
570
|
-
inner_xml.text(element.text_content.to_s)
|
|
571
|
-
end
|
|
572
|
-
end
|
|
573
|
-
|
|
574
|
-
# Recursively build child elements, passing namespace context and plan
|
|
575
|
-
element.children.each do |child|
|
|
576
|
-
if child.is_a?(Lutaml::Xml::DataModel::XmlElement)
|
|
577
|
-
build_xml_element(inner_xml, child,
|
|
578
|
-
parent_uses_default_ns: this_element_uses_default_ns,
|
|
579
|
-
parent_element_form_default: this_element_form_default,
|
|
580
|
-
parent_namespace_class: element_ns_class,
|
|
581
|
-
plan: plan,
|
|
582
|
-
xml_mapping: xml_mapping)
|
|
583
|
-
elsif child.is_a?(String)
|
|
584
|
-
inner_xml.text(child)
|
|
585
|
-
end
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
end
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
# NOTE: build_unordered_children_with_plan and build_ordered_element_with_plan
|
|
592
|
-
# are inherited from BaseAdapter - no need to override
|
|
593
|
-
|
|
594
|
-
def handle_nested_elements_with_plan(xml, value, rule, attribute, plan,
|
|
595
|
-
options, parent_plan: nil)
|
|
596
|
-
element_options = options.merge(
|
|
597
|
-
rule: rule,
|
|
598
|
-
attribute: attribute,
|
|
599
|
-
tag_name: rule.name,
|
|
600
|
-
mapper_class: attribute.type(register), # Override with child's type
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
# Handle Collection instances
|
|
604
|
-
if value.is_a?(Lutaml::Model::Collection)
|
|
605
|
-
items = value.collection
|
|
606
|
-
attr_type = attribute.type(register)
|
|
607
|
-
|
|
608
|
-
if attr_type <= Lutaml::Model::Type::Value
|
|
609
|
-
# Simple types - use add_simple_value for each item
|
|
610
|
-
items.each do |val|
|
|
611
|
-
xml_mapping = options[:mapper_class]&.mappings_for(:xml)
|
|
612
|
-
add_simple_value(xml, rule, val, attribute, plan: parent_plan,
|
|
613
|
-
mapping: xml_mapping, options: options)
|
|
614
|
-
end
|
|
615
|
-
else
|
|
616
|
-
# Model types - build elements with plans
|
|
617
|
-
items.each do |val|
|
|
618
|
-
# For polymorphic collections, use each item's actual class
|
|
619
|
-
item_mapper_class = if polymorphic_value?(attribute, val)
|
|
620
|
-
val.class
|
|
621
|
-
else
|
|
622
|
-
attribute.type(register)
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# CRITICAL: Transform model to XmlElement, then collect and plan
|
|
626
|
-
item_mapping = item_mapper_class.mappings_for(:xml)
|
|
627
|
-
if item_mapping
|
|
628
|
-
# Transform model to XmlElement tree
|
|
629
|
-
transformation = item_mapper_class.transformation_for(:xml,
|
|
630
|
-
register)
|
|
631
|
-
xml_element = transformation.transform(val, options)
|
|
632
|
-
|
|
633
|
-
# Collect namespace needs from XmlElement tree
|
|
634
|
-
collector = NamespaceCollector.new(register)
|
|
635
|
-
item_needs = collector.collect(xml_element, item_mapping,
|
|
636
|
-
mapper_class: item_mapper_class)
|
|
637
|
-
|
|
638
|
-
# Plan with XmlElement tree (not model instance)
|
|
639
|
-
planner = DeclarationPlanner.new(register)
|
|
640
|
-
item_plan = planner.plan(xml_element, item_mapping,
|
|
641
|
-
item_needs, parent_plan: parent_plan, options: options)
|
|
642
|
-
else
|
|
643
|
-
item_plan = plan
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
item_options = element_options.merge(mapper_class: item_mapper_class)
|
|
647
|
-
build_element_with_plan(xml, val, item_plan, item_options)
|
|
648
|
-
end
|
|
649
|
-
end
|
|
650
|
-
return
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
case value
|
|
654
|
-
when Array
|
|
655
|
-
value.each do |val|
|
|
656
|
-
# For polymorphic arrays, use each item's actual class
|
|
657
|
-
item_mapper_class = if polymorphic_value?(attribute, val)
|
|
658
|
-
val.class
|
|
659
|
-
else
|
|
660
|
-
attribute.type(register)
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
# CRITICAL: Transform model to XmlElement, then collect and plan
|
|
664
|
-
item_mapping = item_mapper_class.mappings_for(:xml)
|
|
665
|
-
if item_mapping
|
|
666
|
-
# Transform model to XmlElement tree
|
|
667
|
-
transformation = item_mapper_class.transformation_for(:xml,
|
|
668
|
-
register)
|
|
669
|
-
xml_element = transformation.transform(val, options)
|
|
670
|
-
|
|
671
|
-
# Collect namespace needs from XmlElement tree
|
|
672
|
-
collector = NamespaceCollector.new(register)
|
|
673
|
-
item_needs = collector.collect(xml_element, item_mapping,
|
|
674
|
-
mapper_class: item_mapper_class)
|
|
675
|
-
|
|
676
|
-
# Plan with XmlElement tree (not model instance)
|
|
677
|
-
planner = DeclarationPlanner.new(register)
|
|
678
|
-
item_plan = planner.plan(xml_element, item_mapping, item_needs,
|
|
679
|
-
parent_plan: parent_plan, options: options)
|
|
680
|
-
else
|
|
681
|
-
item_plan = plan
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
item_options = element_options.merge(mapper_class: item_mapper_class)
|
|
685
|
-
if item_plan
|
|
686
|
-
build_element_with_plan(xml, val, item_plan, item_options)
|
|
687
|
-
else
|
|
688
|
-
build_element(xml, val, item_options)
|
|
689
|
-
end
|
|
690
|
-
end
|
|
691
|
-
else
|
|
692
|
-
build_element_with_plan(xml, value, plan, element_options)
|
|
693
|
-
end
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
# Add simple (non-model) values to XML
|
|
697
|
-
def add_simple_value(xml, rule, value, attribute, plan: nil,
|
|
698
|
-
mapping: nil, options: {})
|
|
699
|
-
if value.is_a?(Array)
|
|
700
|
-
value.each do |val|
|
|
701
|
-
add_simple_value(xml, rule, val, attribute, plan: plan,
|
|
702
|
-
mapping: mapping, options: options)
|
|
703
|
-
end
|
|
704
|
-
return
|
|
705
|
-
end
|
|
706
|
-
|
|
707
|
-
resolver = NamespaceResolver.new(register)
|
|
708
|
-
|
|
709
|
-
# Extract parent_uses_default_ns from options or calculate it
|
|
710
|
-
parent_uses_default_ns = options[:parent_uses_default_ns]
|
|
711
|
-
if parent_uses_default_ns.nil?
|
|
712
|
-
parent_uses_default_ns = if mapping&.namespace_class && plan
|
|
713
|
-
DeclarationPlanQuery.declared_at_root_default_format?(plan,
|
|
714
|
-
mapping.namespace_class)
|
|
715
|
-
else
|
|
716
|
-
false
|
|
717
|
-
end
|
|
718
|
-
end
|
|
719
|
-
|
|
720
|
-
# Resolve namespace using the resolver
|
|
721
|
-
ns_result = resolver.resolve_for_element(rule, attribute, mapping,
|
|
722
|
-
plan, options)
|
|
723
|
-
resolved_prefix = ns_result[:prefix]
|
|
724
|
-
type_ns_info = ns_result[:ns_info]
|
|
725
|
-
|
|
726
|
-
# CRITICAL FIX: Type namespace format inheritance for namespace_scope
|
|
727
|
-
# When a type has namespace_class and that namespace is in the stored plan,
|
|
728
|
-
# inherit the format from the stored plan (preserves input format)
|
|
729
|
-
type_ns_class = if attribute && !rule.namespace_set?
|
|
730
|
-
type_class = attribute.type(register)
|
|
731
|
-
type_class.namespace_class if type_class.is_a?(Class) && type_class <= Lutaml::Model::Type::Value
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
format_from_stored_plan = false
|
|
735
|
-
false # Will be set below if needed
|
|
736
|
-
|
|
737
|
-
if type_ns_class
|
|
738
|
-
# Check BOTH the current plan (programmatic) and stored plan (round-trip)
|
|
739
|
-
check_plan = plan || options[:stored_xml_declaration_plan]
|
|
740
|
-
if check_plan
|
|
741
|
-
stored_ns_decl = check_plan.namespaces.values.find do |decl|
|
|
742
|
-
decl.uri == type_ns_class.uri
|
|
743
|
-
end
|
|
744
|
-
if stored_ns_decl
|
|
745
|
-
# Namespace in plan - inherit its format
|
|
746
|
-
# CRITICAL: local_on_use namespaces MUST use prefix format
|
|
747
|
-
# (can't use default format - parent already using default)
|
|
748
|
-
resolved_prefix = if stored_ns_decl.local_on_use? || stored_ns_decl.prefix_format?
|
|
749
|
-
stored_ns_decl.prefix
|
|
750
|
-
else
|
|
751
|
-
nil # Use default format
|
|
752
|
-
end
|
|
753
|
-
format_from_stored_plan = true # Don't let subsequent logic override this
|
|
754
|
-
false # Using plan namespace format, no xmlns="" needed
|
|
755
|
-
end
|
|
756
|
-
end
|
|
757
|
-
end
|
|
758
|
-
|
|
759
|
-
# BUG FIX #49: Check if child element is in same namespace as parent
|
|
760
|
-
# If yes, inherit parent's format (default vs prefix)
|
|
761
|
-
|
|
762
|
-
# Get parent's namespace URI
|
|
763
|
-
parent_ns_class = options[:parent_namespace_class]
|
|
764
|
-
parent_ns_decl = options[:parent_ns_decl]
|
|
765
|
-
parent_ns_uri = parent_ns_class&.uri
|
|
766
|
-
|
|
767
|
-
# Get child's resolved namespace URI
|
|
768
|
-
child_ns_uri = ns_result[:uri]
|
|
769
|
-
|
|
770
|
-
# Initialize resolved_prefix from namespace resolution
|
|
771
|
-
resolved_prefix = ns_result[:prefix]
|
|
772
|
-
|
|
773
|
-
# CRITICAL FIX FOR NATIVE TYPE NAMESPACE INHERITANCE:
|
|
774
|
-
# Elements without explicit namespace declaration should NOT inherit
|
|
775
|
-
# parent's prefix format. They should be in blank namespace.
|
|
776
|
-
#
|
|
777
|
-
# BUT: Skip this logic if we already determined format from stored plan
|
|
778
|
-
unless format_from_stored_plan
|
|
779
|
-
# Check if this is a native type without explicit namespace:
|
|
780
|
-
# 1. No namespace directive on the mapping rule
|
|
781
|
-
# 2. Attribute type doesn't have namespace_class (native type like :string)
|
|
782
|
-
element_has_no_explicit_ns = !rule.namespace_set?
|
|
783
|
-
type_class = attribute&.type(register)
|
|
784
|
-
type_has_no_ns = !(type_class.is_a?(Class) && type_class <= Lutaml::Model::Type::Value) ||
|
|
785
|
-
!type_class&.namespace_class
|
|
786
|
-
|
|
787
|
-
# If native type with no explicit namespace, DON'T inherit parent's prefix
|
|
788
|
-
if element_has_no_explicit_ns && type_has_no_ns
|
|
789
|
-
# Native type - force blank namespace (no prefix)
|
|
790
|
-
resolved_prefix = nil
|
|
791
|
-
# Check if parent uses default format - if so, need xmlns="" to opt out
|
|
792
|
-
parent_ns_decl&.default_format?
|
|
793
|
-
elsif parent_ns_class && parent_ns_decl &&
|
|
794
|
-
child_ns_uri && parent_ns_uri &&
|
|
795
|
-
child_ns_uri == parent_ns_uri
|
|
796
|
-
# Same namespace URI - inherit parent's format
|
|
797
|
-
resolved_prefix = if parent_ns_decl.prefix_format?
|
|
798
|
-
parent_ns_decl.prefix
|
|
799
|
-
else
|
|
800
|
-
# Parent uses default format, child should too (no prefix)
|
|
801
|
-
nil
|
|
802
|
-
end
|
|
803
|
-
# No blank xmlns needed when inheriting
|
|
804
|
-
false
|
|
805
|
-
else
|
|
806
|
-
# Different namespace or no parent context - use standard resolution
|
|
807
|
-
resolved_prefix = ns_result[:prefix]
|
|
808
|
-
ns_result[:blank_xmlns]
|
|
809
|
-
end
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
# Prepare attributes for element creation
|
|
813
|
-
attributes = {}
|
|
814
|
-
|
|
815
|
-
# W3C COMPLIANCE: Use resolver to determine xmlns="" requirement
|
|
816
|
-
if resolver.xmlns_blank_required?(ns_result, parent_uses_default_ns)
|
|
817
|
-
attributes["xmlns"] = ""
|
|
818
|
-
end
|
|
819
|
-
|
|
820
|
-
# Check if this namespace needs local declaration (out of scope)
|
|
821
|
-
if resolved_prefix && plan&.namespaces
|
|
822
|
-
ns_entry = plan.namespaces.values.find do |ns_decl|
|
|
823
|
-
ns_decl.ns_object.prefix_default == resolved_prefix ||
|
|
824
|
-
(type_ns_info && type_ns_info[:uri] && ns_decl.ns_object.uri == type_ns_info[:uri])
|
|
825
|
-
end
|
|
826
|
-
|
|
827
|
-
if ns_entry&.local_on_use?
|
|
828
|
-
xmlns_attr = resolved_prefix ? "xmlns:#{resolved_prefix}" : "xmlns"
|
|
829
|
-
attributes[xmlns_attr] = ns_entry.ns_object.uri
|
|
830
|
-
end
|
|
831
|
-
end
|
|
832
|
-
|
|
833
|
-
if value.nil?
|
|
834
|
-
xml.create_and_add_element(rule.name,
|
|
835
|
-
attributes: attributes.merge({ "xsi:nil" => true }),
|
|
836
|
-
prefix: resolved_prefix)
|
|
837
|
-
elsif ::Lutaml::Model::Utils.empty?(value)
|
|
838
|
-
xml.create_and_add_element(rule.name,
|
|
839
|
-
attributes: attributes.empty? ? nil : attributes,
|
|
840
|
-
prefix: resolved_prefix)
|
|
841
|
-
elsif rule.raw_mapping?
|
|
842
|
-
xml.add_xml_fragment(xml, value)
|
|
843
|
-
elsif value.is_a?(::Hash) && attribute&.type(register) == Lutaml::Model::Type::Hash
|
|
844
|
-
# Check if value is Hash type that needs wrapper
|
|
845
|
-
xml.create_and_add_element(rule.name,
|
|
846
|
-
attributes: attributes.empty? ? nil : attributes,
|
|
847
|
-
prefix: resolved_prefix) do
|
|
848
|
-
value.each do |key, val|
|
|
849
|
-
xml.create_and_add_element(key.to_s) do
|
|
850
|
-
xml.add_text(xml, val.to_s)
|
|
851
|
-
end
|
|
852
|
-
end
|
|
853
|
-
end
|
|
854
|
-
else
|
|
855
|
-
xml.create_and_add_element(rule.name,
|
|
856
|
-
attributes: attributes.empty? ? nil : attributes,
|
|
857
|
-
prefix: resolved_prefix) do
|
|
858
|
-
add_value(xml, value, attribute, cdata: rule.cdata)
|
|
859
|
-
end
|
|
860
|
-
end
|
|
861
|
-
end
|
|
862
|
-
|
|
863
|
-
def attributes_hash(element)
|
|
864
|
-
result = Lutaml::Model::MappingHash.new
|
|
865
|
-
|
|
866
|
-
element.attributes.each_value do |attr|
|
|
867
|
-
if attr.unprefixed_name == "schemaLocation"
|
|
868
|
-
result["__schema_location"] = {
|
|
869
|
-
namespace: attr.namespace,
|
|
870
|
-
prefix: attr.namespace_prefix,
|
|
871
|
-
schema_location: attr.value,
|
|
872
|
-
}
|
|
873
|
-
else
|
|
874
|
-
result[attr.namespaced_name] = attr.value
|
|
875
|
-
end
|
|
876
|
-
end
|
|
877
|
-
|
|
878
|
-
result
|
|
879
|
-
end
|
|
880
|
-
|
|
881
|
-
# NOTE: name_of, prefixed_name_of, namespaced_attr_name, namespaced_name_of
|
|
882
|
-
# are provided by AdapterHelpers module via extend
|
|
883
|
-
|
|
884
|
-
def self.text_of(element)
|
|
885
|
-
element.text
|
|
886
|
-
end
|
|
887
|
-
|
|
888
|
-
def self.order_of(element)
|
|
889
|
-
element.order
|
|
890
|
-
end
|
|
10
|
+
MOXML_ADAPTER = Moxml::Adapter::Ox
|
|
11
|
+
BUILDER_CLASS = Builder::Ox
|
|
12
|
+
PARSED_ELEMENT_CLASS = Ox::Element
|
|
13
|
+
PARSE_ERROR_CLASS = Moxml::ParseError
|
|
891
14
|
end
|
|
892
15
|
end
|
|
893
16
|
end
|