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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +4 -1
  3. data/.rubocop_todo.yml +97 -22
  4. data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
  5. data/lib/lutaml/model/version.rb +1 -1
  6. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
  7. data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
  8. data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
  9. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
  10. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
  11. data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
  12. data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
  13. data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
  14. data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
  15. data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
  16. data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
  17. data/lib/lutaml/xml/adapter.rb +0 -1
  18. data/lib/lutaml/xml/adapter_element.rb +7 -1
  19. data/lib/lutaml/xml/builder/base.rb +0 -1
  20. data/lib/lutaml/xml/data_model.rb +9 -1
  21. data/lib/lutaml/xml/document.rb +3 -1
  22. data/lib/lutaml/xml/element.rb +13 -10
  23. data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
  24. data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
  25. data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
  26. data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
  27. data/lib/lutaml/xml/xml_element.rb +24 -20
  28. data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
  29. data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
  30. data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
  31. data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
  32. data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
  33. data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
  34. metadata +9 -3
  35. data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "moxml"
2
4
  require "moxml/adapter/nokogiri"
3
5
  require_relative "base_adapter"
@@ -6,1116 +8,9 @@ module Lutaml
6
8
  module Xml
7
9
  module Adapter
8
10
  class NokogiriAdapter < BaseAdapter
9
- extend DocTypeExtractor
10
- extend AdapterHelpers
11
-
12
- TEXT_CLASSES = [Moxml::Text, Moxml::Cdata].freeze
13
-
14
- def self.parse(xml, options = {})
15
- enc = encoding(xml, options)
16
- parsed = Moxml::Adapter::Nokogiri.parse(xml, encoding: enc)
17
- root_element = parsed.root
18
-
19
- # Validate that we have a root element
20
- if root_element.nil?
21
- raise Lutaml::Model::InvalidFormatError.new(
22
- :xml,
23
- "Document has no root element. " \
24
- "The XML may be empty, contain only whitespace, " \
25
- "or consist only of an XML declaration.",
26
- )
27
- end
28
-
29
- # Extract DOCTYPE information from raw XML
30
- # (Moxml doesn't directly expose DOCTYPE)
31
- doctype_info = extract_doctype_from_xml(xml)
32
-
33
- # Extract XML declaration for preservation
34
- xml_decl_info = DeclarationHandler.extract_xml_declaration(xml)
35
-
36
- @root = NokogiriElement.new(root_element)
37
- @root.processing_instructions = extract_document_processing_instructions(parsed)
38
- new(@root, enc, doctype: doctype_info,
39
- xml_declaration: xml_decl_info)
40
- end
41
-
42
- def to_xml(options = {})
43
- # Accept xml_declaration from options if present (for model serialization)
44
- @xml_declaration = options[:xml_declaration] if options[:xml_declaration]
45
-
46
- builder_options = {}
47
- encoding = determine_encoding(options)
48
- builder_options[:encoding] = encoding if encoding
49
-
50
- builder = Builder::Nokogiri.build(builder_options) do |xml|
51
- if root.is_a?(Lutaml::Xml::NokogiriElement)
52
- # Case A: Old parsed XML (from NokogiriElement) - use build_xml
53
- root.build_xml(xml)
54
- else
55
- # Cases B & C: XmlElement or Model instance
56
- # ARCHITECTURE: Normalize to XmlElement, then use single rendering path
57
-
58
- # Determine the source (XmlElement or model instance)
59
- original_model = nil
60
-
61
- xml_element = if root.is_a?(Lutaml::Xml::DataModel::XmlElement)
62
- # Case B: Already an XmlElement
63
- root
64
- else
65
- # Case C: Model instance - transform to XmlElement
66
- original_model = root
67
- mapper_class = options[:mapper_class] || root.class
68
- transformation = mapper_class.transformation_for(
69
- :xml, @register
70
- )
71
- transformation.transform(root, options)
72
- end
73
-
74
- # Collect original namespace URIs for namespace alias support.
75
- # This enables round-trip fidelity when XML uses alias URIs.
76
- original_ns_uris = {}
77
- stored_plan = nil
78
- if original_model
79
- # Case C: Model instance was transformed to XmlElement
80
- mapping_for_original = options[:mapper_class]&.mappings_for(:xml) || original_model.class.mappings_for(:xml)
81
- original_ns_uris = collect_original_namespace_uris(
82
- original_model, mapping_for_original
83
- )
84
- # Get stored declaration plan from model for PRESERVATION phase
85
- if original_model.is_a?(Lutaml::Model::Serialize)
86
- stored_plan = original_model.import_declaration_plan
87
- end
88
- elsif xml_element.is_a?(Lutaml::Xml::DataModel::XmlElement)
89
- # Case B: XmlElement from transformation may have @__xml_original_namespace_uri
90
- original_ns_uri = xml_element.original_namespace_uri
91
- if original_ns_uri
92
- # Get mapping from the mapper_class (model class) not from XmlElement
93
- mapper_klass = options[:mapper_class] || xml_element.class
94
- xml_mapping = begin
95
- mapper_klass.mappings_for(:xml)
96
- rescue StandardError
97
- nil
98
- end
99
- if xml_mapping&.namespace_class
100
- canonical_uri = xml_mapping.namespace_class.uri
101
- if canonical_uri != original_ns_uri
102
- original_ns_uris[canonical_uri] =
103
- original_ns_uri
104
- end
105
- end
106
- end
107
- end
108
- options_with_original_ns = options.merge(__original_namespace_uris: original_ns_uris)
109
- if stored_plan
110
- options_with_original_ns[:stored_xml_declaration_plan] =
111
- stored_plan
112
- end
113
-
114
- mapper_class = options[:mapper_class] || xml_element.class
115
- mapping = mapper_class.mappings_for(:xml)
116
-
117
- # Phase 1: Collect namespace needs from XmlElement tree
118
- collector = NamespaceCollector.new(@register)
119
- needs = collector.collect(xml_element, mapping,
120
- mapper_class: mapper_class)
121
-
122
- # Phase 2: Plan namespace declarations (builds ElementNode tree)
123
- planner = DeclarationPlanner.new(@register)
124
- plan = planner.plan(xml_element, mapping, needs,
125
- options: options_with_original_ns)
126
-
127
- # Phase 3: Render using XmlElement + DeclarationPlan
128
- # Pass original model for custom method invocation
129
- render_options = options.merge(is_root_element: true)
130
- render_options[:original_model] = original_model if original_model
131
- build_xml_element_with_plan(xml, xml_element, plan,
132
- render_options)
133
- end
134
- end
135
-
136
- xml_data = builder.to_xml
137
-
138
- # Transcode to target encoding if needed
139
- target_encoding = encoding || options[:encoding]
140
- if target_encoding && target_encoding.upcase != "UTF-8"
141
- xml_data = xml_data.encode(target_encoding)
142
- end
143
-
144
- result = ""
145
-
146
- # Handle XML declaration based on Issue #1: XML Declaration Preservation
147
- # Include declaration when encoding is specified OR when declaration is requested
148
- if (options[:encoding] && !options[:encoding].nil?) || should_include_declaration?(options)
149
- result += generate_declaration(options)
150
- end
151
-
152
- # Add DOCTYPE if present - use DeclarationHandler method
153
- doctype_to_use = options[:doctype] || @doctype
154
- if doctype_to_use && !options[:omit_doctype]
155
- result += generate_doctype_declaration(doctype_to_use)
156
- end
157
-
158
- result += xml_data
159
-
160
- # Post-process: Fix OOXML format issues (opt-in)
161
- result = fix_ooxml_format(result) if options[:fix_boolean_elements]
162
-
163
- result
164
- end
165
-
166
- def attributes_hash(element)
167
- result = Lutaml::Model::MappingHash.new
168
-
169
- element.attributes_each_value do |attr|
170
- if attr.name == "schemaLocation"
171
- result["__schema_location"] = {
172
- namespace: attr.namespace,
173
- prefix: attr.namespace.prefix,
174
- schema_location: attr.value,
175
- }
176
- else
177
- result[self.class.namespaced_attr_name(attr)] = attr.value
178
- end
179
- end
180
-
181
- result
182
- end
183
-
184
- # NOTE: name_of, prefixed_name_of, namespaced_attr_name, namespaced_name_of
185
- # are provided by AdapterHelpers module via extend
186
-
187
- def self.text_of(element)
188
- element.text
189
- end
190
-
191
- def order
192
- children.filter_map do |child|
193
- if child.text?
194
- next if child.text.nil?
195
-
196
- Element.new("Text", "text", text_content: child.text)
197
- elsif child.comment?
198
- Element.new("Comment", "comment",
199
- text_content: child.content,
200
- node_type: :comment)
201
- else
202
- Element.new("Element", child.unprefixed_name)
203
- end
204
- end
205
- end
206
-
207
- def self.order_of(element)
208
- element.children.each do |node|
209
- if node.is_a?(Moxml::ProcessingInstruction)
210
- return [Element.new("ProcessingInstruction", node.name)]
211
- end
212
- end
213
- super
214
- end
215
-
216
- # Build element using prepared namespace declaration plan
217
- #
218
- # @param xml [Builder] the XML builder
219
- # @param element [Object] the model instance
220
- # @param plan [Hash] the declaration plan from DeclarationPlanner
221
- # @param options [Hash] serialization options
222
- def build_element_with_plan(xml, element, plan, options = {})
223
- # Provide default empty plan if nil (e.g., for custom methods)
224
- plan ||= DeclarationPlan.empty
225
-
226
- mapper_class = options[:mapper_class] || element.class
227
-
228
- # New: Handle simple types that don't have mappings
229
- unless mapper_class.is_a?(Class) && mapper_class.include?(Lutaml::Model::Serialize)
230
- tag_name = options[:tag_name] || "element"
231
- xml.create_and_add_element(tag_name) do |inner_xml|
232
- inner_xml.text(element.to_s)
233
- end
234
- return xml
235
- end
236
-
237
- mapping = mapper_class.mappings_for(:xml)
238
- return xml unless mapping
239
-
240
- # TYPE-ONLY MODELS: No element wrapper, serialize children directly
241
- # BUT if we have a tag_name in options, that means parent wants a wrapper
242
- if mapping.namespace_class
243
- # Check if this element's namespace is explicitly :blank
244
- # This happens when the model uses 'namespace :blank' in its xml block
245
- # We can detect this through the plan - but since we're inside build_element_with_plan,
246
- # we need to check the mapping directly
247
- # Actually, the element itself won't have explicit_blank in its namespace resolution
248
- # because it's the element's OWN namespace. We need to skip this for the element itself.
249
- # The xmlns="" handling is for CHILD elements, not the parent element.
250
- # So this section is actually not needed here - it's needed in add_simple_value
251
- # But it reads:
252
- # @mapping.namespace_class
253
- # element.ns_info_for(repository_name, mapping.xml_namespace)
254
- end
255
-
256
- # Use xmlns declarations from plan
257
- attributes = {}
258
- attributes.merge!(NamespaceDeclarationBuilder.build_xmlns_attributes(plan))
259
-
260
- # Collect attribute custom methods to call after element creation
261
- attribute_custom_methods = []
262
-
263
- # Add regular attributes (non-xmlns)
264
- mapping.attributes.each do |attribute_rule|
265
- next if options[:except]&.include?(attribute_rule.to)
266
-
267
- # Collect custom methods for later execution (after element is created)
268
- if attribute_rule.custom_methods[:to]
269
- attribute_custom_methods << attribute_rule
270
- next
271
- end
272
-
273
- mapping_rule_name = if attribute_rule.multiple_mappings?
274
- attribute_rule.name.first
275
- else
276
- attribute_rule.name
277
- end
278
-
279
- attr = attribute_definition_for(element, attribute_rule,
280
- mapper_class: mapper_class)
281
- value = attribute_rule.to_value_for(element)
282
-
283
- # Handle as_list and delimiter BEFORE serialization for array values
284
- # These features convert arrays to delimited strings before serialization
285
- if value.is_a?(Array)
286
- if attribute_rule.as_list && attribute_rule.as_list[:export]
287
- value = attribute_rule.as_list[:export].call(value)
288
- elsif attribute_rule.delimiter
289
- value = value.join(attribute_rule.delimiter)
290
- end
291
- end
292
-
293
- value = attr.serialize(value, :xml, @register) if attr
294
- value = ExportTransformer.call(value, attribute_rule, attr,
295
- format: :xml)
296
-
297
- if render_element?(attribute_rule, element, value)
298
- # Resolve attribute namespace using extracted module
299
- ns_info = AttributeNamespaceResolver.resolve(
300
- rule: attribute_rule,
301
- attribute: attr,
302
- plan: plan,
303
- mapper_class: mapper_class,
304
- register: @register,
305
- )
306
-
307
- # Build qualified attribute name based on W3C semantics
308
- attr_name = AttributeNamespaceResolver.build_qualified_name(
309
- ns_info,
310
- mapping_rule_name,
311
- attribute_rule,
312
- )
313
- attributes[attr_name] = value ? value.to_s : value
314
-
315
- # Add local xmlns declaration if needed
316
- if ns_info[:needs_local_declaration]
317
- attributes[ns_info[:local_xmlns_attr]] =
318
- ns_info[:local_xmlns_uri]
319
- end
320
- end
321
- end
322
-
323
- # Add schema_location attribute from ElementNode if present
324
- # This is for the plan-based path where schema_location_attr is computed during planning
325
- attributes.merge!(plan.root_node.schema_location_attr) if plan&.root_node&.schema_location_attr
326
-
327
- # Determine prefix from plan using extracted module
328
- prefix_info = ElementPrefixResolver.resolve(mapping: mapping,
329
- plan: plan)
330
- prefix = prefix_info[:prefix]
331
- ns_decl = prefix_info[:ns_decl]
332
-
333
- # Check if element's own namespace needs local declaration (out of scope)
334
- if ns_decl&.local_on_use?
335
- # FIX: Handle both default (nil prefix) and prefixed namespaces
336
- xmlns_attr = if prefix
337
- "xmlns:#{prefix}"
338
- else
339
- "xmlns"
340
- end
341
- attributes[xmlns_attr] = ns_decl.uri
342
- end
343
-
344
- # W3C COMPLIANCE: Detect if element needs xmlns="" using extracted module
345
- if BlankNamespaceHandler.needs_xmlns_blank?(mapping: mapping,
346
- options: options)
347
- attributes["xmlns"] = ""
348
- end
349
-
350
- # Native type inheritance fix: handle local_on_use xmlns="" even if parents uses default format
351
- xmlns_prefix = nil
352
- xmlns_ns = nil
353
- if mapping&.namespace_class && plan
354
- xmlns_ns = plan.namespace_for_class(mapping.namespace_class)
355
- xmlns_prefix = xmlns_ns&.prefix
356
- end
357
- if xmlns_ns&.local_on_use? && !mapping.namespace_uri
358
- attributes["xmlns:#{xmlns_prefix}"] =
359
- xmlns_ns&.uri || mapping.namespace_uri
360
- end
361
-
362
- tag_name = options[:tag_name] || mapping.root_element
363
- return if options[:except]&.include?(tag_name)
364
-
365
- # Track if THIS element uses default namespace format
366
- # Children will need this info to know if they should add xmlns=""
367
- this_element_uses_default_ns = mapping.namespace_class &&
368
- plan.namespace_for_class(mapping.namespace_class)&.default_format?
369
-
370
- # Get element_form_default from this element's namespace for children
371
- parent_element_form_default = mapping.namespace_class&.element_form_default
372
-
373
- xml.create_and_add_element(tag_name, attributes: attributes,
374
- prefix: prefix) do |xml|
375
- # Call attribute custom methods now that element is created
376
- attribute_custom_methods.each do |attribute_rule|
377
- mapper_class.new.send(attribute_rule.custom_methods[:to],
378
- element, xml.parent, xml)
379
- end
380
-
381
- if ordered?(element, options.merge(mapper_class: mapper_class))
382
- build_ordered_element_with_plan(xml, element, plan,
383
- options.merge(
384
- mapper_class: mapper_class,
385
- parent_prefix: prefix,
386
- parent_uses_default_ns: this_element_uses_default_ns,
387
- parent_element_form_default: parent_element_form_default,
388
- parent_ns_decl: ns_decl,
389
- ))
390
- else
391
- build_unordered_children_with_plan(xml, element, plan,
392
- options.merge(
393
- mapper_class: mapper_class,
394
- parent_prefix: prefix,
395
- parent_uses_default_ns: this_element_uses_default_ns,
396
- parent_element_form_default: parent_element_form_default,
397
- parent_ns_decl: ns_decl,
398
- ))
399
- end
400
- end
401
- end
402
-
403
- # Build element children in the original order (for ordered content)
404
- #
405
- # This method preserves the order of elements as they appeared in the
406
- # original XML, using element.element_order to iterate through elements.
407
- #
408
- # @param xml [Builder] XML builder
409
- # @param element [Object] model instance
410
- # @param plan [DeclarationPlan] namespace declaration plan
411
-
412
- # NOTE: build_unordered_children_with_plan and build_ordered_element_with_plan
413
- # are inherited from BaseAdapter - no need to override
414
-
415
- def build_nested_element_with_plan(xml, value, element_rule,
416
- attribute_def, plan, options, parent_plan: nil)
417
- if value.is_a?(Lutaml::Model::Collection)
418
- items = value.collection
419
- attr_type = attribute_def.type(@register)
420
-
421
- if attr_type <= Lutaml::Model::Type::Value
422
- # Simple types - serialize each item
423
- items.each do |val|
424
- build_element_value_with_plan(xml, element_rule, val, attribute_def,
425
- plan: plan, mapping: nil, options: options.merge(element: val))
426
- end
427
- else
428
- # Model types - build elements
429
- items.each do |val|
430
- # For polymorphic collections, use each item's actual class
431
- item_mapper_class = if polymorphic_value?(attribute_def, val)
432
- val.class
433
- else
434
- attr_type
435
- end
436
-
437
- # Collect and plan for each item
438
- item_mapping = item_mapper_class.mappings_for(:xml)
439
- if item_mapping
440
- collector = NamespaceCollector.new(@register)
441
- item_needs = collector.collect(val, item_mapping)
442
-
443
- planner = DeclarationPlanner.new(@register)
444
- item_plan = planner.plan(val, item_mapping, item_needs,
445
- parent_plan: plan, options: options)
446
- else
447
- item_plan = plan
448
- end
449
-
450
- # Performance: Use dup with direct assignment to avoid merge allocations
451
- # when mapper_class differs from current
452
- if options[:mapper_class] == item_mapper_class
453
- item_options = options
454
- else
455
- item_options = options.dup
456
- item_options[:mapper_class] = item_mapper_class
457
- end
458
- if item_plan
459
- build_element_with_plan(xml, val, item_plan, item_options)
460
- else
461
- build_element(xml, val, item_options)
462
- end
463
- end
464
- end
465
- else
466
- # Single Serialize instance
467
- # Performance: Use dup with direct assignment
468
- child_mapper = attribute_def.type(@register)
469
- if options[:mapper_class] == child_mapper
470
- child_options = options
471
- else
472
- child_options = options.dup
473
- child_options[:mapper_class] = child_mapper
474
- end
475
- build_element_with_plan(xml, value, plan, child_options)
476
- end
477
- end
478
-
479
- # Build simple element value with plan
480
- #
481
- # @param xml [Builder] XML builder
482
- # @param element_rule [MappingRule] element mapping rule
483
- # @param value [Object] value to serialize
484
- # @param attribute_def [Attribute] attribute definition
485
- # @param plan [DeclarationPlan] namespace plan
486
- # @param mapping [Xml::Mapping] optional mapping
487
- # @param options [Hash] serialization options
488
- def build_element_value_with_plan(xml, element_rule, value,
489
- attribute_def, plan:, mapping: nil, options: {})
490
- # Handle array values by creating multiple elements
491
- if value.is_a?(Array)
492
- value.each do |val|
493
- build_element_value_with_plan(xml, element_rule, val, attribute_def,
494
- plan: plan, mapping: mapping, options: options)
495
- end
496
- return
497
- end
498
-
499
- return unless render_element?(element_rule, options[:element], value)
500
-
501
- # Get namespace info for this element
502
- mapping_local = mapping || options[:mapper_class]&.mappings_for(:xml)
503
- ns_info = if mapping_local
504
- # Try to resolve namespace using local mapping
505
- begin
506
- NamespaceResolver.new(@register).resolve_for_element(
507
- element_rule, attribute_def, mapping_local, plan, options
508
- )
509
- rescue StandardError
510
- # Fallback to default behavior
511
- { prefix: nil, ns_info: nil }
512
- end
513
- else
514
- { prefix: nil, ns_info: nil }
515
- end
516
-
517
- prefix = ns_info[:prefix]
518
-
519
- # Get child's plan if available
520
- child_plan = plan&.child_plan(element_rule.to)
521
-
522
- if value.is_a?(Lutaml::Model::Serialize)
523
- # Nested Serialize object
524
- child_mapper_class = value.class
525
- child_mapper_class.mappings_for(:xml)
526
-
527
- # Performance: Use dup with direct assignment to avoid merge allocations
528
- if options[:mapper_class] == child_mapper_class
529
- child_options = options
530
- else
531
- child_options = options.dup
532
- child_options[:mapper_class] = child_mapper_class
533
- end
534
-
535
- if child_plan
536
- build_element_with_plan(xml, value, child_plan, child_options)
537
- else
538
- build_element(xml, value, child_options)
539
- end
540
- elsif value.nil? && element_rule.render_nil?
541
- # Render nil value
542
- element_name = element_rule.multiple_mappings? ? element_rule.name.first : element_rule.name
543
- xml.create_and_add_element(element_name,
544
- prefix: prefix) do |inner_xml|
545
- inner_xml.text("")
546
- end
547
- elsif value
548
- # Simple string value
549
- element_name = element_rule.multiple_mappings? ? element_rule.name.first : element_rule.name
550
- xml.create_and_add_element(element_name,
551
- prefix: prefix) do |inner_xml|
552
- if element_rule.cdata
553
- inner_xml.cdata(value.to_s)
554
- else
555
- inner_xml.text(value.to_s)
556
- end
557
- end
558
- end
559
- end
560
-
561
- # Build XML from XmlDataModel::XmlElement structure
562
- #
563
- # @param xml [Builder] XML builder
564
- # @param element [XmlDataModel::XmlElement] element to build
565
- # @param parent_uses_default_ns [Boolean] parent uses default namespace format
566
- # @param parent_element_form_default [Symbol] parent's element_form_default
567
- # @param parent_namespace_class [Class] parent's namespace class
568
- def build_xml_element(xml, element, parent_uses_default_ns: false,
569
- parent_element_form_default: nil, parent_namespace_class: nil)
570
- # Prepare attributes hash
571
- attributes = {}
572
-
573
- # Determine if attributes should be qualified based on element's namespace
574
- element_ns_class = element.namespace_class
575
- attribute_form_default = element_ns_class&.attribute_form_default || :unqualified
576
- element_prefix = element_ns_class&.prefix_default
577
-
578
- # Get element_form_default for children
579
- # Only set when explicitly configured, not when defaulted to :unqualified
580
- this_element_form_default = if element_ns_class&.element_form_default_set?
581
- element_ns_class.element_form_default
582
- end
583
-
584
- # Add regular attributes
585
- element.attributes.each do |attr|
586
- # Determine attribute name with namespace consideration
587
- attr_name = if attr.namespace_class
588
- # Check if attribute is in SAME namespace as element
589
- if attr.namespace_class == element_ns_class && attribute_form_default == :unqualified
590
- # Same namespace + unqualified → NO prefix (W3C rule)
591
- attr.name
592
- else
593
- # Different namespace OR qualified → use prefix
594
- attr_prefix = attr.namespace_class.prefix_default
595
- attr_prefix ? "#{attr_prefix}:#{attr.name}" : attr.name
596
- end
597
- elsif attribute_form_default == :qualified && element_prefix
598
- # Attribute inherits element's namespace when qualified
599
- "#{element_prefix}:#{attr.name}"
600
- else
601
- # Unqualified attribute
602
- attr.name
603
- end
604
- attributes[attr_name] = attr.value
605
- end
606
-
607
- # Determine element name with namespace prefix
608
- tag_name = element.name
609
- # CRITICAL FIX: element_form_default: :qualified means child elements inherit parent's namespace PREFIX
610
- # even when child has NO explicit namespace_class
611
- prefix = if element_ns_class && element_prefix
612
- # Element has explicit prefix_default - use prefix format
613
- element_prefix
614
- elsif !element_ns_class && parent_element_form_default == :qualified && parent_namespace_class&.prefix_default
615
- # Child has NO namespace, but parent has :qualified form_default
616
- # Child should INHERIT parent's namespace PREFIX
617
- parent_namespace_class.prefix_default
618
- else
619
- # No prefix (default format or no parent namespace)
620
- nil
621
- end
622
-
623
- # Track if THIS element uses default namespace format for children
624
- this_element_uses_default_ns = false
625
-
626
- # Add namespace declaration if element has namespace
627
- if element.namespace_class
628
- ns_uri = element.namespace_class.uri
629
-
630
- if prefix
631
- attributes["xmlns:#{prefix}"] = ns_uri
632
- # W3C Compliance: When parent uses default namespace and child declares
633
- # a DIFFERENT prefixed namespace, child must also add xmlns="" to prevent
634
- # its children from inheriting parent's default namespace
635
- if parent_uses_default_ns
636
- attributes["xmlns"] = ""
637
- end
638
- else
639
- attributes["xmlns"] = ns_uri
640
- this_element_uses_default_ns = true
641
- end
642
- elsif parent_uses_default_ns
643
- # W3C Compliance: Element has no namespace (blank namespace)
644
- # Check if should inherit parent's namespace based on element_form_default
645
- # Parent uses default namespace format
646
- if parent_element_form_default == :qualified
647
- # Child should INHERIT parent's namespace - no xmlns="" needed
648
- # The child is in parent namespace (qualified)
649
- elsif parent_element_form_default == :unqualified
650
- # Parent's element_form_default is :unqualified - child should be in blank namespace
651
- # WITHOUT xmlns="" (no xmlns attribute at all). The child is simply
652
- # not in any namespace, which is the correct W3C behavior for unqualified.
653
- else
654
- # element_form_default is not set (nil/default :unqualified)
655
- # Child needs xmlns="" to explicitly opt out of parent's default namespace
656
- attributes["xmlns"] = ""
657
- end
658
- end
659
-
660
- # Check if element was created from nil value with render_nil option
661
- # Add xsi:nil="true" attribute for W3C compliance
662
- if element.respond_to?(:xsi_nil) && element.xsi_nil
663
- attributes["xsi:nil"] = true
664
- end
665
-
666
- # Create element
667
- xml.create_and_add_element(tag_name, attributes: attributes,
668
- prefix: prefix) do |inner_xml|
669
- # Add text content if present
670
- if element.text_content
671
- # Check if content should be wrapped in CDATA
672
- if element.cdata
673
- inner_xml.cdata(element.text_content)
674
- else
675
- add_text_with_entities(inner_xml.parent,
676
- element.text_content.to_s, inner_xml.doc)
677
- end
678
- end
679
-
680
- # Recursively build child elements, passing namespace context
681
- element.children.each do |child|
682
- if child.is_a?(Lutaml::Xml::DataModel::XmlElement)
683
- build_xml_element(inner_xml, child,
684
- parent_uses_default_ns: this_element_uses_default_ns,
685
- parent_element_form_default: this_element_form_default,
686
- parent_namespace_class: element_ns_class)
687
- elsif child.is_a?(String)
688
- inner_xml.text(child)
689
- end
690
- end
691
- end
692
- end
693
-
694
- # Build XML from XmlDataModel::XmlElement using DeclarationPlan tree (PARALLEL TRAVERSAL)
695
- #
696
- # Constructs Moxml node tree for XML serialization.
697
- #
698
- # @param xml [Builder] XML builder (provides doc access)
699
- # @param xml_element [XmlDataModel::XmlElement] Element content
700
- # @param plan [DeclarationPlan] Declaration plan with tree structure
701
- # @param options [Hash] Serialization options
702
- def build_xml_element_with_plan(xml, xml_element, plan, options = {})
703
- moxml_doc = xml.doc
704
-
705
- root_node = build_xml_node(xml_element, plan.root_node, moxml_doc,
706
- plan.global_prefix_registry, nil, options: options, plan: plan)
707
- moxml_doc.root = root_node
708
-
709
- # Add processing instructions before the root element.
710
- # reverse_each + add_previous_sibling maintains original order:
711
- # each PI is inserted before the root (and before previously-inserted PIs).
712
- xml_element.processing_instructions.reverse_each do |pi|
713
- pi_node = moxml_doc.create_processing_instruction(pi.target,
714
- pi.content)
715
- root_node.add_previous_sibling(pi_node)
716
- end
717
- end
718
-
719
- private
720
-
721
- # Override BaseAdapter hook to preserve entity references.
722
- def add_text_nodes(element, text, doc)
723
- add_text_with_entities(element, text, doc)
724
- end
725
-
726
- # Builds Moxml node tree from XmlDataModel::XmlElement content and
727
- # DeclarationPlan decisions (PARALLEL TRAVERSAL).
728
- #
729
- # Uses Moxml APIs for all DOM operations:
730
- # - doc.create_element for document-aware node creation
731
- # - element.in_scope_namespaces for in-scope namespace query
732
- # - element.namespace= for namespace assignment
733
- # - doc.create_text/create_cdata/create_entity_reference for child nodes
734
- #
735
- # @param xml_element [XmlDataModel::XmlElement] Content
736
- # @param element_node [ElementNode] Decisions
737
- # @param doc [Moxml::Document] Document
738
- # @param global_registry [Hash] Global prefix registry (URI => prefix)
739
- # @param parent [Moxml::Element, nil] Parent element for namespace inheritance
740
- # @param options [Hash] Serialization options
741
- # @param plan [DeclarationPlan] Declaration plan with original namespace URIs
742
- # @param previous_sibling_had_xmlns_blank [Boolean] Previous sibling had xmlns="" for W3C optimization
743
- # @return [Moxml::Element] Created node
744
- def build_xml_node(xml_element, element_node, doc,
745
- global_registry, parent = nil, options: {}, plan: nil, previous_sibling_had_xmlns_blank: false)
746
- qualified_name = element_node.qualified_name
747
-
748
- # Split qualified_name to get prefix and local_name
749
- # IMPORTANT: Only split on SINGLE colon (namespace prefix separator),
750
- # not on double colon (::) which is a Ruby module path separator.
751
- # e.g., "prefix:name" should split to ["prefix", "name"]
752
- # but "Module::Class" should NOT be split (use whole name as local_name)
753
- if qualified_name.include?(":") && !qualified_name.include?("::")
754
- _, local_name = qualified_name.split(":", 2)
755
- else
756
- local_name = qualified_name
757
- end
758
-
759
- element = doc.create_element(local_name)
760
-
761
- # Add xmlns declarations FIRST (before adding to parent!)
762
- # This ensures the element's own namespace is declared before it can inherit parent's
763
- # Keys: nil = default namespace, "prefix" = prefixed namespace
764
- original_ns_uris = plan&.original_namespace_uris || {}
765
- use_prefix_option = options[:use_prefix]
766
- element_node.hoisted_declarations.each do |key, uri|
767
- next if uri == "http://www.w3.org/XML/1998/namespace"
768
-
769
- # Convert FPI to URN if necessary (Nokogiri requires valid URI)
770
- # Only apply original_ns_uris conversion when preserving original format.
771
- # When use_prefix is explicitly set, we're using system's format preferences.
772
- effective_uri = if self.class.fpi?(uri)
773
- self.class.fpi_to_urn(uri)
774
- elsif use_prefix_option.nil?
775
- # Preserving original format - use alias URIs from original
776
- original_ns_uris[uri] || uri
777
- else
778
- # Using explicit format preference - use canonical URIs
779
- uri
780
- end
781
-
782
- if key.nil?
783
- # Default namespace (xmlns="uri")
784
- element.add_namespace(nil, effective_uri)
785
- else
786
- # Prefixed namespace (xmlns:prefix="uri")
787
- element.add_namespace(key, effective_uri)
788
- end
789
- end
790
-
791
- # NOW set element's namespace (before adding to parent)
792
- # This ensures the element uses its own namespace, not inherited from parent
793
- # CRITICAL: Use the decision's namespace_class from element_node, not the element's namespace_class
794
- # The decision's namespace_class may be nil (blank namespace) even if the element has a namespace_class
795
- # set during transformation (e.g., when form: :unqualified is set)
796
- #
797
- # IMPORTANT: Use xml_element's namespace_class for the namespace decision
798
- # The element_node (DeclarationPlan::ElementNode) doesn't have namespace_class
799
- # because namespace decisions are stored in the xml_element during transformation
800
- #
801
- # IMPORTANT: When xml_element.namespace_class is nil, check if this is an explicit decision
802
- # (blank namespace) or if no decision was made. We can tell by checking if the element has
803
- # a form attribute set to :unqualified.
804
- target_namespace_class = xml_element.namespace_class
805
- # Check if this is an explicit "blank namespace" decision (form: :unqualified)
806
- # If so, don't fall back to any namespace_class
807
- if target_namespace_class.nil? && xml_element.respond_to?(:form) && xml_element.form == :unqualified
808
- # Explicit blank namespace decision - don't set any namespace
809
- target_namespace_class = nil
810
- end
811
- # Note: If no explicit decision, we keep target_namespace_class as nil
812
- # and don't fall back to anything (no default namespace_class)
813
-
814
- if target_namespace_class && target_namespace_class != :blank
815
- # Use the prefix to find the namespace when available.
816
- # This is more reliable than matching by URI because hoisted_declarations
817
- # may contain canonical URIs while the actual namespace was added using
818
- # alias URIs (via original_ns_uris conversion).
819
- target_prefix = element_node.use_prefix
820
- target_uri = target_namespace_class.uri
821
- ns = if target_prefix
822
- # Find namespace by prefix (most reliable - prefix is unique per element)
823
- element.in_scope_namespaces.find do |n|
824
- n.prefix == target_prefix
825
- end
826
- else
827
- # Fall back to URI-based lookup for default namespace
828
- element.in_scope_namespaces.find do |n|
829
- n.uri == target_uri && n.prefix.nil?
830
- end
831
- end
832
- if ns
833
- element.namespace = ns
834
- elsif target_prefix
835
- # CRITICAL FIX: Check if namespace is declared on parent before adding locally
836
- # When parent declares the namespace with the SAME format (prefix or default),
837
- # child should use parent's namespace declaration without re-declaring it.
838
- # Also check namespace aliases: if parent declared alias URI and child uses
839
- # canonical URI (or vice versa), the namespace is already established on parent.
840
- target_prefix = element_node.use_prefix
841
- parent_has_namespace = parent_has_matching_namespace?(parent, target_uri,
842
- target_namespace_class)
843
-
844
- if parent_has_namespace
845
- # Parent has the namespace declared - find the matching namespace object
846
- # Must check all URIs (canonical + aliases) since parent may have declared
847
- # with an alias URI while child uses canonical (or vice versa)
848
- matching_uris = if target_namespace_class.respond_to?(:all_uris)
849
- target_namespace_class.all_uris
850
- else
851
- [target_uri]
852
- end
853
- parent_ns = if target_prefix
854
- parent.in_scope_namespaces.find do |n|
855
- matching_uris.include?(n.uri) && n.prefix == target_prefix
856
- end
857
- else
858
- parent.in_scope_namespaces.find do |n|
859
- matching_uris.include?(n.uri) && n.prefix.nil?
860
- end
861
- end
862
-
863
- if parent_ns
864
- # Parent has the SAME format declaration - use parent's namespace
865
- # Defer setting until after add_child so element is in the tree.
866
- @deferred_namespace = parent_ns
867
- nil
868
- else
869
- # Parent has different format - add namespace declaration locally
870
- if target_prefix.nil?
871
- # Default format: add xmlns="uri" declaration
872
- element.add_namespace(nil, target_uri)
873
- # Find the newly added namespace and set it
874
- ns = element.in_scope_namespaces.find do |n|
875
- n.uri == target_uri
876
- end
877
- else
878
- # Prefix format: add xmlns:prefix="uri" declaration
879
- element.add_namespace(target_prefix, target_uri)
880
- # Find the newly added namespace and set it
881
- ns = element.in_scope_namespaces.find do |n|
882
- n.uri == target_uri && n.prefix == target_prefix
883
- end
884
- end
885
- element.namespace = ns if ns
886
- end
887
- elsif target_prefix.nil?
888
- # Default format: add xmlns="uri" declaration
889
- element.add_namespace(nil, target_uri)
890
- # Find the newly added namespace and set it
891
- ns = element.in_scope_namespaces.find do |n|
892
- n.uri == target_uri
893
- end
894
- element.namespace = ns if ns
895
- else
896
- # Prefix format: add xmlns:prefix="uri" declaration
897
- element.add_namespace(target_prefix, target_uri)
898
- # Find the newly added namespace and set it
899
- ns = element.in_scope_namespaces.find do |n|
900
- n.uri == target_uri && n.prefix == target_prefix
901
- end
902
- element.namespace = ns if ns
903
- end
904
- end
905
- end
906
-
907
- # Add to parent AFTER namespace is set
908
- # This prevents the element from inheriting parent's namespace before declaring its own
909
- parent&.add_child(element)
910
-
911
- # CRITICAL FIX: Set deferred namespace after adding to parent
912
- # This allows the element to use parent's namespace declaration without re-declaring it
913
- if @deferred_namespace
914
- element.namespace = @deferred_namespace
915
- @deferred_namespace = nil
916
- end
917
-
918
- # CRITICAL FIX: Handle blank namespace elements
919
- # When element has no namespace_class, it should remain in blank namespace
920
- # Even if parent uses prefix format, the child should NOT inherit parent's namespace
921
- # Also applies when form: :unqualified is set (element should be in blank namespace)
922
- if !xml_element.namespace_class || xml_element.namespace_class == :blank ||
923
- (xml_element.respond_to?(:form) && xml_element.form == :unqualified)
924
- # Explicitly set element to blank namespace (no namespace)
925
- # This prevents the child from inheriting parent's namespace
926
- element.namespace = nil
927
- end
928
-
929
- # W3C Compliance: Add xmlns="" if element is in blank namespace
930
- # and needs to opt out of parent's default namespace
931
- # W3C Optimization: Only first sibling needs xmlns="", subsequent inherit
932
- # Only apply optimization when pretty: true is set
933
- if element_node.needs_xmlns_blank && (options[:pretty] ? !previous_sibling_had_xmlns_blank : true)
934
- # Add xmlns="" as an attribute (Nokogiri-specific)
935
- element["xmlns"] = ""
936
- end
937
-
938
- # Add regular attributes (PARALLEL TRAVERSAL by index)
939
- apply_plan_attributes(xml_element, element_node, element)
940
-
941
- # Check if element was created from nil value with render_nil option
942
- # Add xsi:nil="true" attribute for W3C compliance
943
- if xml_element.respond_to?(:xsi_nil) && xml_element.xsi_nil
944
- element["xsi:nil"] = true
945
- end
946
-
947
- # Add schema_location attribute from ElementNode if present
948
- element_node.schema_location_attr&.each do |attr_name, attr_value|
949
- element[attr_name] = attr_value
950
- end
951
-
952
- # Handle raw content (map_all directive)
953
- # If @raw_content exists, parse and add as XML fragment
954
- # NOTE: We do NOT return early here because the element may have
955
- # children that also need to be processed. Raw content should be
956
- # added alongside children, not replace them.
957
- if xml_element.respond_to?(:raw_content)
958
- raw_content = xml_element.raw_content
959
- if raw_content && !raw_content.to_s.empty?
960
- # Parse raw XML content and add as children using moxml
961
- # Use inner_xml= approach: parse wrapper, then move children to element
962
- parsed_fragment = doc.context.parse("<__root__>#{raw_content}</__root__>")
963
- parsed_fragment.root&.children&.each do |child_node|
964
- element.add_child(child_node)
965
- end
966
- # Do NOT return early - continue to process element's children
967
- end
968
- end
969
-
970
- # Recursively build children (PARALLEL TRAVERSAL by index)
971
- # Pass THIS element as parent so children can inherit namespaces
972
- child_element_index = 0
973
- previous_sibling_had_xmlns_blank = false
974
- xml_element.children.each do |xml_child| # rubocop:disable Metrics/BlockLength
975
- # Entity reference nodes are preserved via the marker
976
- # preprocessing approach in NokogiriAdapter.parse.
977
- if xml_child.is_a?(Lutaml::Xml::NokogiriElement) &&
978
- xml_child.adapter_node.respond_to?(:entity_reference?) &&
979
- xml_child.adapter_node.entity_reference?
980
- entity_node = doc.create_entity_reference(xml_child.adapter_node.name)
981
- element.add_child(entity_node)
982
- next
983
- elsif xml_child.is_a?(Lutaml::Xml::DataModel::XmlElement)
984
- child_node = element_node.element_nodes[child_element_index]
985
- child_element_index += 1
986
-
987
- # Recurse - child auto-adds itself to element (parent)
988
- # Pass previous_sibling_had_xmlns_blank for W3C optimization
989
- build_xml_node(xml_child, child_node, doc, global_registry, element,
990
- options: options, plan: plan,
991
- previous_sibling_had_xmlns_blank: previous_sibling_had_xmlns_blank)
992
- # Track if this child had xmlns="" for next sibling
993
- # Blank namespace children get xmlns="" to opt out of parent's default namespace
994
- if !xml_child.namespace_class && xml_element.namespace_class
995
- previous_sibling_had_xmlns_blank = true
996
- end
997
- elsif xml_child.is_a?(String)
998
- add_content_node(element, xml_child, doc,
999
- cdata: xml_element.cdata && !xml_child.strip.empty?)
1000
- elsif xml_child.is_a?(::Lutaml::Xml::DataModel::XmlComment)
1001
- comment_node = doc.create_comment(xml_child.content)
1002
- element.add_child(comment_node)
1003
- end
1004
- end
1005
-
1006
- # Add text content AFTER child elements
1007
- if xml_element.text_content
1008
- add_content_node(element, xml_element.text_content.to_s,
1009
- doc, cdata: xml_element.cdata)
1010
- end
1011
-
1012
- element
1013
- end
1014
-
1015
- # Standard XML predefined entities — these are always resolved by the
1016
- # XML parser and must NOT be turned into EntityReference nodes during
1017
- # serialization. If we created an EntityReference for e.g. "lt", the
1018
- # output would render as `<` instead of preserving the literal text
1019
- # `&lt;`, which corrupts double-encoded content like `&amp;lt;`.
1020
- STANDARD_XML_ENTITIES = %w[lt gt amp apos quot].freeze
1021
-
1022
- # Add text content to an element, preserving entity reference patterns.
1023
- # Only non-standard named entities (e.g. &copy;, &nbsp;, &mdash;) are
1024
- # promoted to EntityReference nodes. Standard XML entities, numeric
1025
- # character references, and all other text are added as plain text nodes
1026
- # so the XML serializer handles proper escaping.
1027
- #
1028
- # Uses Moxml's doc.create_text/create_entity_reference for node creation.
1029
- #
1030
- # @param element [Moxml::Element] Target element
1031
- # @param text [String] Text content possibly containing entity references
1032
- # @param doc [Moxml::Document] Document for node creation
1033
- def add_text_with_entities(element, text, doc)
1034
- entity_pattern = /(&(?:\w+|#\d+|#x[\da-fA-F]+);)/
1035
- parts = text.to_s.split(entity_pattern, -1)
1036
- parts.each do |part|
1037
- next if part.empty?
1038
-
1039
- # Only non-standard named entities become EntityReference nodes.
1040
- # Standard XML entities (lt, gt, amp, apos, quot) and numeric
1041
- # character references (&#NNN;, &#xHHH;) must remain as text so
1042
- # the serializer escapes them correctly (e.g. &lt; → &amp;lt;).
1043
- # Entity names must start with a letter per the XML specification,
1044
- # so patterns like &1; are NOT entity references.
1045
- # NOTE: use #match (not #match?) because match? does not set $1.
1046
- if (m = part.match(/\A&([a-zA-Z]\w*);\z/)) && !STANDARD_XML_ENTITIES.include?(m[1])
1047
- entity_name = m[1]
1048
- entity_node = doc.create_entity_reference(entity_name)
1049
- element.add_child(entity_node)
1050
- else
1051
- text_node = doc.create_text(part)
1052
- element.add_child(text_node)
1053
- end
1054
- end
1055
- end
1056
-
1057
- # Check if parent element has a matching namespace declaration in scope.
1058
- # Uses Moxml's in_scope_namespaces for namespace query.
1059
- def parent_has_matching_namespace?(parent, target_uri,
1060
- target_namespace_class)
1061
- return false unless parent
1062
-
1063
- parent_uris = parent.in_scope_namespaces.map(&:uri)
1064
-
1065
- # Check exact match first
1066
- return true if parent_uris.include?(target_uri)
1067
-
1068
- # Check if parent declared an alias URI for the same namespace
1069
- if target_namespace_class.respond_to?(:all_uris)
1070
- all_ns_uris = target_namespace_class.all_uris
1071
- return parent_uris.any? { |href| all_ns_uris.include?(href) }
1072
- end
1073
-
1074
- false
1075
- end
1076
-
1077
- # Post-process XML string to fix OOXML format issues.
1078
- # Handles two normalization rules:
1079
- # 1. Boolean elements: <w:elem w:val="true"/> -> <w:elem/>
1080
- # 2. XML namespace attribute: <w:t w:xml:space=...> -> <w:t xml:space=...>
1081
- #
1082
- # @param xml [String] The XML string to process
1083
- # @return [String] The processed XML string
1084
- # OOXML boolean element names: self-closing elements where presence = true.
1085
- # This is a whitelist of known boolean element names to avoid incorrectly
1086
- # transforming non-boolean elements like numId, colSpan, etc.
1087
- OOXML_BOOLEAN_ELEMENTS = %w[
1088
- b i strike bCs iCs smallCaps caps vanish noProof
1089
- shadow emboss imprint keepNext keepLines outline
1090
- tblHeader cantSplit contextualSpacing highlight
1091
- rPr pPr trPr tcPr
1092
- ].freeze
1093
-
1094
- def fix_ooxml_format(xml)
1095
- # Build regex pattern that only matches known boolean element names
1096
- bool_elem_pattern = OOXML_BOOLEAN_ELEMENTS.join("|")
1097
-
1098
- # Fix self-closing: <ns:elem w:val="true"/> or <ns:elem w:val="1"/>
1099
- # Only for known boolean elements
1100
- xml = xml.gsub(
1101
- /<([a-zA-Z][a-zA-Z0-9]*):(#{bool_elem_pattern})(\s+w:val=")(true|1)("\s*\/?>)/,
1102
- ) { "<#{$1}:#{$2}/>" }
1103
-
1104
- # Fix with content: <ns:elem w:val="true">true</ns:elem> or <ns:elem w:val="1">1</ns:elem>
1105
- xml = xml.gsub(
1106
- /<([a-zA-Z][a-zA-Z0-9]*):(#{bool_elem_pattern})(\s+w:val=")(true|1)(">)true<\/\1:\2>/,
1107
- ) { "<#{$1}:#{$2}>" }
1108
-
1109
- # Fix content-only: <ns:elem>true</ns:elem> -> <ns:elem/>
1110
- # For elements that serialize boolean value as text content
1111
- xml = xml.gsub(
1112
- /<([a-zA-Z][a-zA-Z0-9]*):(#{bool_elem_pattern})>true<\/\1:\2>/,
1113
- ) { "<#{$1}:#{$2}/>" }
1114
-
1115
- # Fix xml:space attribute: <w:t w:xml:space=...> -> <w:t xml:space=...>
1116
- # The xml: attribute belongs to the xml: namespace, not w:
1117
- xml.gsub(/\bw:xml:space=/, "xml:space=")
1118
- end
11
+ MOXML_ADAPTER = Moxml::Adapter::Nokogiri
12
+ BUILDER_CLASS = Builder::Nokogiri
13
+ PARSED_ELEMENT_CLASS = Lutaml::Xml::NokogiriElement
1119
14
  end
1120
15
  end
1121
16
  end