lutaml-model 0.8.12 → 0.8.13

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +116 -103
  3. data/RELEASE_NOTES.adoc +3 -3
  4. data/benchmark/quick_benchmark.rb +2 -2
  5. data/benchmark/serialization_benchmark.rb +4 -4
  6. data/docs/_guides/advanced-mapping.adoc +1 -1
  7. data/docs/_guides/character-encoding.adoc +3 -3
  8. data/docs/_guides/missing-values-handling.adoc +6 -6
  9. data/docs/_guides/ooxml-examples.adoc +7 -7
  10. data/docs/_guides/opal.adoc +1 -1
  11. data/docs/_guides/value-transformations.adoc +7 -7
  12. data/docs/_guides/xml/namespace-presentation.adoc +1 -1
  13. data/docs/_guides/xml/namespace-semantics.adoc +15 -15
  14. data/docs/_guides/xml/type-namespaces.adoc +9 -9
  15. data/docs/_guides/xml-mapping.adoc +32 -26
  16. data/docs/_guides/xml-namespace-qualification.adoc +4 -4
  17. data/docs/_guides/xml-namespaces.adoc +2 -2
  18. data/docs/_guides/xml_mappings/04_xml_namespace_class.adoc +18 -18
  19. data/docs/_guides/xml_mappings/05_common_patterns.adoc +16 -16
  20. data/docs/_guides/xml_mappings/06_migration_guide.adoc +5 -5
  21. data/docs/_guides/xml_mappings/07_best_practices.adoc +11 -11
  22. data/docs/_migrations/0-8-0-namespace-restructuring.adoc +2 -2
  23. data/docs/_pages/attributes.adoc +2 -2
  24. data/docs/_pages/collections.adoc +26 -20
  25. data/docs/_pages/consolidation-mapping.adoc +4 -4
  26. data/docs/_pages/importable_models.adoc +14 -13
  27. data/docs/_pages/quick-start.adoc +1 -1
  28. data/docs/_pages/value_types.adoc +10 -10
  29. data/docs/_references/custom_registers.adoc +7 -7
  30. data/docs/_references/format-independent-features.adoc +4 -4
  31. data/docs/_references/instance-serialization.adoc +1 -1
  32. data/docs/_references/parent-root-context.adoc +3 -3
  33. data/docs/_tutorials/basic-model-definition.adoc +1 -1
  34. data/docs/_tutorials/first-xml-serialization.adoc +4 -4
  35. data/docs/_tutorials/lutaml-xml-architecture.adoc +4 -4
  36. data/docs/_tutorials/validation-basics.adoc +1 -1
  37. data/docs/_tutorials/working-with-collections.adoc +2 -2
  38. data/docs/_tutorials/xml-namespaces-basics.adoc +1 -1
  39. data/docs/_tutorials/xml-schema-primer-style-guide.adoc +29 -29
  40. data/docs/cli_compare.adoc +1 -1
  41. data/docs/index.adoc +1 -1
  42. data/docs/namespace-management.adoc +14 -14
  43. data/lib/lutaml/key_value/mapping.rb +31 -6
  44. data/lib/lutaml/model/collection.rb +11 -11
  45. data/lib/lutaml/model/error/no_root_mapping_error.rb +6 -5
  46. data/lib/lutaml/model/error/no_root_namespace_error.rb +6 -5
  47. data/lib/lutaml/model/error/type_only_mapping_error.rb +13 -0
  48. data/lib/lutaml/model/error/type_only_namespace_error.rb +12 -0
  49. data/lib/lutaml/model/version.rb +1 -1
  50. data/lib/lutaml/model.rb +3 -0
  51. data/lib/lutaml/xml/adapter/base_adapter.rb +0 -9
  52. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +0 -1
  53. data/lib/lutaml/xml/adapter/oga_adapter.rb +0 -1
  54. data/lib/lutaml/xml/adapter/ox_adapter.rb +0 -1
  55. data/lib/lutaml/xml/adapter/rexml_adapter.rb +0 -1
  56. data/lib/lutaml/xml/adapter/xml_serializer.rb +42 -22
  57. data/lib/lutaml/xml/adapter.rb +4 -0
  58. data/lib/lutaml/xml/builder/base.rb +64 -25
  59. data/lib/lutaml/xml/builder/nokogiri.rb +0 -2
  60. data/lib/lutaml/xml/builder/oga.rb +0 -2
  61. data/lib/lutaml/xml/builder/ox.rb +0 -2
  62. data/lib/lutaml/xml/builder/rexml.rb +0 -2
  63. data/lib/lutaml/xml/builder.rb +1 -0
  64. data/lib/lutaml/xml/configurable.rb +2 -2
  65. data/lib/lutaml/xml/declaration_handler.rb +3 -105
  66. data/lib/lutaml/xml/mapping.rb +3 -3
  67. data/lib/lutaml/xml/schema/xsd/documentation.rb +1 -1
  68. data/lib/lutaml/xml/serialization/collection_ext.rb +7 -7
  69. data/lib/lutaml/xml/serialization/format_conversion.rb +1 -1
  70. data/lib/lutaml/xml/serialization/instance_methods.rb +1 -1
  71. data/lib/lutaml/xml.rb +1 -2
  72. data/lib/tasks/memory_profile.rb +2 -2
  73. data/lib/tasks/performance_benchmark.rb +5 -5
  74. data/spec/lutaml/key_value/transformation/rule_compiler_spec.rb +1 -1
  75. data/spec/lutaml/key_value/transformation/value_serializer_spec.rb +1 -1
  76. data/spec/lutaml/model/attribute_collection_spec.rb +1 -1
  77. data/spec/lutaml/model/cli_spec.rb +1 -1
  78. data/spec/lutaml/model/collection_spec.rb +1 -1
  79. data/spec/lutaml/model/collection_validation_spec.rb +6 -6
  80. data/spec/lutaml/model/consolidation_spec.rb +8 -8
  81. data/spec/lutaml/model/custom_collection_spec.rb +3 -3
  82. data/spec/lutaml/model/default_register_spec.rb +23 -23
  83. data/spec/lutaml/model/delegation_spec.rb +3 -10
  84. data/spec/lutaml/model/derived_attribute_serialization_spec.rb +1 -1
  85. data/spec/lutaml/model/dynamic_attribute_spec.rb +2 -2
  86. data/spec/lutaml/model/enum_spec.rb +1 -1
  87. data/spec/lutaml/model/group_spec.rb +12 -12
  88. data/spec/lutaml/model/lazy_collection_spec.rb +4 -4
  89. data/spec/lutaml/model/mixed_content_spec.rb +2 -2
  90. data/spec/lutaml/model/namespace_versioning_spec.rb +4 -4
  91. data/spec/lutaml/model/processing_instruction_spec.rb +11 -11
  92. data/spec/lutaml/model/register_methods_spec.rb +2 -2
  93. data/spec/lutaml/model/render_empty_spec.rb +1 -1
  94. data/spec/lutaml/model/serialize_perf_guard_spec.rb +1 -1
  95. data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +1 -1
  96. data/spec/lutaml/model/transformation_builder_spec.rb +2 -2
  97. data/spec/lutaml/model/xml_decoupling_spec.rb +3 -3
  98. data/spec/lutaml/model/xsd_patterns_spec.rb +2 -3
  99. data/spec/lutaml/xml/adapter/order_spec.rb +1 -1
  100. data/spec/lutaml/xml/clear_parse_state_spec.rb +1 -1
  101. data/spec/lutaml/xml/content_model_validation_spec.rb +4 -2
  102. data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +5 -5
  103. data/spec/lutaml/xml/enhanced_mapping_spec.rb +2 -1
  104. data/spec/lutaml/xml/entity_fragmentation_spec.rb +5 -5
  105. data/spec/lutaml/xml/indent_spec.rb +109 -0
  106. data/spec/lutaml/xml/line_ending_spec.rb +66 -0
  107. data/spec/lutaml/xml/mapping_finalization_guard_spec.rb +2 -2
  108. data/spec/lutaml/xml/model_transform_guard_spec.rb +4 -4
  109. data/spec/lutaml/xml/namespace_alias_spec.rb +4 -4
  110. data/spec/lutaml/xml/namespace_aware_parsing_spec.rb +3 -3
  111. data/spec/lutaml/xml/namespace_bound_element_roundtrip_spec.rb +2 -2
  112. data/spec/lutaml/xml/namespace_format_preservation_spec.rb +1 -1
  113. data/spec/lutaml/xml/namespace_inheritance_spec.rb +3 -3
  114. data/spec/lutaml/xml/namespace_preservation_spec.rb +5 -5
  115. data/spec/lutaml/xml/opal_xml_spec.rb +8 -8
  116. data/spec/lutaml/xml/pipeline_integration_spec.rb +145 -0
  117. data/spec/lutaml/xml/schema_primer_spec.rb +5 -5
  118. data/spec/lutaml/xml/transformation_spec.rb +20 -20
  119. data/spec/lutaml/xml/type_namespace/collector_spec.rb +1 -1
  120. data/spec/lutaml/xml/type_namespace/planner_spec.rb +3 -3
  121. metadata +7 -2
@@ -49,6 +49,35 @@ module Lutaml
49
49
  encoding = determine_encoding(options)
50
50
  builder_options = {}
51
51
  builder_options[:encoding] = encoding if encoding
52
+ builder_options[:line_ending] = options[:line_ending] if options.key?(:line_ending)
53
+ builder_options[:indent] = options[:indent] if options.key?(:indent)
54
+
55
+ # Pass doctype to builder for document-level insertion
56
+ doctype_to_use = options[:doctype] || @doctype
57
+ if doctype_to_use && !options[:omit_doctype]
58
+ builder_options[:doctype] = doctype_to_use
59
+ end
60
+
61
+ # Pass declaration info to builder
62
+ if should_include_declaration?(options)
63
+ builder_options[:include_declaration] = true
64
+ builder_options[:xml_declaration] = @xml_declaration || {}
65
+ if options.key?(:standalone)
66
+ if options[:standalone] == :preserve
67
+ # Keep original standalone from parsed declaration (may be nil)
68
+ else
69
+ builder_options[:xml_declaration][:standalone] = standalone_value(options[:standalone])
70
+ end
71
+ end
72
+ if options[:declaration].is_a?(String)
73
+ builder_options[:xml_declaration][:version] = options[:declaration]
74
+ elsif options[:declaration] == true
75
+ builder_options[:xml_declaration][:version] = "1.0"
76
+ end
77
+ builder_options[:xml_declaration][:encoding] = encoding if options.key?(:encoding) && encoding
78
+ elsif options[:encoding] && !options[:encoding].nil?
79
+ builder_options[:force_declaration] = true
80
+ end
52
81
 
53
82
  builder = self.class::BUILDER_CLASS.build(builder_options) do |xml|
54
83
  if root.is_a?(self.class::PARSED_ELEMENT_CLASS)
@@ -58,7 +87,7 @@ module Lutaml
58
87
  end
59
88
  end
60
89
 
61
- finalize_adapter_xml(builder.to_xml, encoding, options)
90
+ builder.to_xml
62
91
  end
63
92
 
64
93
  def build_serializable_xml(xml, options)
@@ -95,6 +124,14 @@ module Lutaml
95
124
 
96
125
  private
97
126
 
127
+ def standalone_value(value)
128
+ case value
129
+ when true then "yes"
130
+ when false then "no"
131
+ else value.to_s
132
+ end
133
+ end
134
+
98
135
  def transformable_xml_element(options)
99
136
  return root if root.is_a?(Lutaml::Xml::DataModel::XmlElement)
100
137
 
@@ -183,27 +220,8 @@ module Lutaml
183
220
  options_with_original_ns
184
221
  end
185
222
 
186
- def finalize_adapter_xml(xml_data, encoding, options)
187
- result = ""
188
- if (options[:encoding] && !options[:encoding].nil?) ||
189
- should_include_declaration?(options)
190
- result += generate_declaration(options)
191
- end
192
-
193
- doctype_to_use = options[:doctype] || @doctype
194
- if doctype_to_use && !options[:omit_doctype]
195
- result += generate_doctype_declaration(doctype_to_use)
196
- end
197
-
198
- result += xml_data
199
- if encoding && result.encoding.to_s.upcase != encoding.to_s.upcase
200
- result = result.encode(encoding)
201
- end
202
- result
203
- end
204
-
205
223
  def text_content_for_xml(value)
206
- ::Moxml::Adapter::Base.preprocess_entities(value.to_s)
224
+ ::Moxml.preprocess_entities(value.to_s)
207
225
  end
208
226
 
209
227
  def build_plan_node(xml, xml_element, element_node, plan: nil,
@@ -212,7 +230,9 @@ module Lutaml
212
230
  attributes = {}
213
231
 
214
232
  original_ns_uris = plan&.original_namespace_uris || {}
215
- element_node.hoisted_declarations.each do |key, uri|
233
+ element_node.hoisted_declarations.sort_by do |prefix, _uri|
234
+ prefix.nil? ? "" : prefix.to_s
235
+ end.each do |key, uri|
216
236
  next if uri == "http://www.w3.org/XML/1998/namespace"
217
237
 
218
238
  effective_uri = if self.class.fpi?(uri)
@@ -7,6 +7,10 @@ module Lutaml
7
7
  autoload :AdapterHelpers, "#{__dir__}/adapter/adapter_helpers"
8
8
  autoload :BaseAdapter, "#{__dir__}/adapter/base_adapter"
9
9
  autoload :NamespaceData, "#{__dir__}/adapter/namespace_data"
10
+ autoload :XmlParser, "#{__dir__}/adapter/xml_parser"
11
+ autoload :XmlSerializer, "#{__dir__}/adapter/xml_serializer"
12
+ autoload :PlanBasedBuilder, "#{__dir__}/adapter/plan_based_builder"
13
+ autoload :NamespaceUriCollector, "#{__dir__}/adapter/namespace_uri_collector"
10
14
  autoload :OgaAdapter, "#{__dir__}/adapter/oga_adapter"
11
15
  Lutaml::Model::RuntimeCompatibility.autoload_native(
12
16
  self,
@@ -7,15 +7,63 @@ module Lutaml
7
7
  module Builder
8
8
  # Base builder for XML construction using moxml.
9
9
  # All adapter-specific builders inherit from this class.
10
+ #
11
+ # The builder creates XML documents through moxml's document model.
12
+ # Declaration, doctype, indentation, and line endings are handled
13
+ # by moxml — no manual string assembly.
10
14
  class Base
11
15
  def self.build(options = {})
12
16
  context = Moxml.new(moxml_backend)
13
17
  if Lutaml::Model::RuntimeCompatibility.opal?
14
18
  context.config.namespace_validation_mode = :lenient
15
19
  end
20
+
21
+ encoding_value = options.delete(:encoding)
22
+ context.config.default_indent = options.delete(:indent) if options.key?(:indent)
23
+ context.config.default_line_ending = options.delete(:line_ending) if options.key?(:line_ending)
24
+
16
25
  doc = context.create_document
26
+
27
+ # Capture doctype — added after root to avoid Ox incompatibility
28
+ doctype = options.delete(:doctype)
29
+
17
30
  instance = new(doc, context, options)
31
+ instance.encoding = encoding_value if encoding_value
18
32
  yield(instance) if block_given?
33
+
34
+ # Add doctype before root (after build block sets root)
35
+ if doctype && doc.root
36
+ dt = doc.create_doctype(
37
+ doctype[:name],
38
+ doctype[:public_id],
39
+ doctype[:system_id],
40
+ )
41
+ doc.add_child(dt)
42
+ end
43
+
44
+ # Handle declaration — configure it on the document so moxml
45
+ # serializes it natively (works across all adapters)
46
+ xml_decl = options.delete(:xml_declaration) || {}
47
+ include_decl = options.delete(:include_declaration)
48
+ force_decl = options.delete(:force_declaration)
49
+
50
+ if include_decl
51
+ version = xml_decl[:version] || "1.0"
52
+ encoding = xml_decl[:encoding]
53
+ encoding ||= "UTF-8" unless xml_decl[:had_declaration]
54
+ standalone = xml_decl[:standalone]
55
+ decl = doc.create_declaration(version, encoding, standalone)
56
+ doc.add_child(decl)
57
+ instance.declaration_mode = :default
58
+ elsif force_decl
59
+ decl_encoding = encoding_value || "UTF-8"
60
+ decl = doc.create_declaration("1.0", decl_encoding, nil)
61
+ doc.add_child(decl)
62
+ instance.declaration_mode = :default
63
+ else
64
+ instance.declaration_mode = :none
65
+ end
66
+
19
67
  instance
20
68
  end
21
69
 
@@ -24,13 +72,15 @@ module Lutaml
24
72
  nil
25
73
  end
26
74
 
27
- attr_reader :doc, :encoding
75
+ attr_reader :doc
76
+ attr_accessor :encoding, :declaration_mode
28
77
 
29
78
  def initialize(doc, context, options = {})
30
79
  @doc = doc
31
80
  @context = context
32
81
  @encoding = options[:encoding]
33
82
  @current_stack = [doc]
83
+ @declaration_mode = :none
34
84
  end
35
85
 
36
86
  def current_element
@@ -116,33 +166,15 @@ module Lutaml
116
166
  add_cdata(current_element, content)
117
167
  end
118
168
 
119
- def to_s
169
+ def to_xml
120
170
  return "" unless @doc.root
121
171
 
122
- # Serialize full document content (including PIs before root)
123
- # Check if there are any nodes before the root element
124
- doc_children = @doc.children
125
- has_pi_or_comment = doc_children.any? do |child|
126
- child.is_a?(Moxml::ProcessingInstruction) || child.is_a?(Moxml::Comment)
127
- end
128
-
129
- if has_pi_or_comment
130
- # Serialize each top-level node individually
131
- parts = doc_children.map do |child|
132
- if child == @doc.root
133
- child.to_xml(declaration: false, expand_empty: false)
134
- else
135
- child.to_xml
136
- end
137
- end
138
- parts.join("\n")
139
- else
140
- @doc.root.to_xml(declaration: false, expand_empty: false)
141
- end
142
- end
172
+ result = if @declaration_mode == :none && !has_document_level_nodes?
173
+ @doc.root.to_xml(declaration: false, expand_empty: false)
174
+ else
175
+ @doc.to_xml(declaration: @declaration_mode == :default, expand_empty: false)
176
+ end
143
177
 
144
- def to_xml
145
- result = to_s
146
178
  result = result.encode(encoding) if encoding && result.encoding.to_s != encoding
147
179
  result
148
180
  end
@@ -158,6 +190,13 @@ module Lutaml
158
190
 
159
191
  private
160
192
 
193
+ def has_document_level_nodes?
194
+ @doc.children.any? do |child|
195
+ child != @doc.root &&
196
+ !child.is_a?(Moxml::Text)
197
+ end
198
+ end
199
+
161
200
  def resolve_target(element)
162
201
  element.is_a?(self.class) || element.is_a?(Base) ? element.current_element : element
163
202
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
4
-
5
3
  module Lutaml
6
4
  module Xml
7
5
  module Builder
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
4
-
5
3
  module Lutaml
6
4
  module Xml
7
5
  module Builder
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
4
-
5
3
  module Lutaml
6
4
  module Xml
7
5
  module Builder
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
4
-
5
3
  module Lutaml
6
4
  module Xml
7
5
  module Builder
@@ -4,6 +4,7 @@ module Lutaml
4
4
  module Xml
5
5
  # Builder module for XML generation
6
6
  module Builder
7
+ autoload :Base, "#{__dir__}/builder/base"
7
8
  autoload :Oga, "#{__dir__}/builder/oga"
8
9
  Lutaml::Model::RuntimeCompatibility.autoload_native(
9
10
  self,
@@ -13,7 +13,7 @@ module Lutaml
13
13
  # include Lutaml::Xml::Configurable
14
14
  #
15
15
  # xml do
16
- # root 'MyModel'
16
+ # element "MyModel"
17
17
  # namespace MyNamespace
18
18
  # end
19
19
  # end
@@ -51,7 +51,7 @@ module Lutaml
51
51
  #
52
52
  # @example Configuration with block
53
53
  # xml do
54
- # root 'MyModel'
54
+ # element "MyModel"
55
55
  # namespace MyNamespace
56
56
  # map_element 'name', to: :name
57
57
  # end
@@ -1,10 +1,9 @@
1
1
  module Lutaml
2
2
  module Xml
3
- # DeclarationHandler provides XML declaration and DOCTYPE handling
4
- # for all XML adapter implementations.
3
+ # DeclarationHandler provides XML declaration extraction from input XML.
5
4
  #
6
- # This module implements Issue #1: XML Declaration Preservation
7
- # across Nokogiri, Oga, and Ox adapters.
5
+ # Extraction methods detect and parse XML declarations from input strings.
6
+ # Generation is handled by moxml's document model — no manual string assembly.
8
7
  module DeclarationHandler
9
8
  # Extract XML declaration information from input string
10
9
  #
@@ -92,126 +91,25 @@ module Lutaml
92
91
  # @param xml_declaration [Hash] extracted declaration info from input
93
92
  # @return [Boolean] true if declaration should be included
94
93
  def should_include_declaration?(options, xml_declaration = nil)
95
- # Use instance variable if not provided (for adapter instance methods)
96
94
  xml_declaration ||= @xml_declaration
97
95
 
98
96
  if options.key?(:declaration)
99
97
  case options[:declaration]
100
98
  when false
101
- # Explicit false: omit declaration
102
99
  false
103
100
  when true
104
- # Explicit true: force include
105
101
  true
106
102
  when :preserve
107
- # Preserve mode: include if input had one
108
103
  xml_declaration&.dig(:had_declaration) || false
109
104
  when String
110
- # Custom version string: include
111
105
  true
112
106
  else
113
- # Default: preserve from input
114
107
  xml_declaration&.dig(:had_declaration) || false
115
108
  end
116
109
  else
117
- # No declaration option provided: default behavior is preserve from input
118
110
  xml_declaration&.dig(:had_declaration) || false
119
111
  end
120
112
  end
121
-
122
- # Generate XML declaration string
123
- #
124
- # Uses stored declaration info if available, otherwise uses defaults.
125
- # Supports custom version strings, encoding, and standalone options.
126
- #
127
- # @param options [Hash] serialization options
128
- # - :declaration => String for custom version, true for default
129
- # - :encoding => String or true for UTF-8
130
- # - :standalone => String ("yes"/"no"), true ("yes"), false ("no"), :preserve
131
- # @param xml_declaration [Hash] extracted declaration info from input
132
- # @return [String] the XML declaration (includes trailing newline)
133
- def generate_declaration(options, xml_declaration = nil)
134
- # Use instance variable if not provided (for adapter instance methods)
135
- xml_declaration ||= @xml_declaration
136
-
137
- # Determine version
138
- # When declaration: true (force), use default 1.0 not input version
139
- # When declaration: "1.x" (custom), use that string
140
- # When preserving (no option or :preserve), use input version or default
141
- version = if options[:declaration].is_a?(String)
142
- # Custom version string
143
- options[:declaration]
144
- elsif options[:declaration] == true
145
- # Force with default version
146
- "1.0"
147
- elsif xml_declaration&.dig(:version)
148
- # Preserve from input
149
- xml_declaration[:version]
150
- else
151
- # Default fallback
152
- "1.0"
153
- end
154
-
155
- # Determine encoding
156
- # Priority: explicit encoding option > input encoding > none
157
- encoding = if options[:encoding].is_a?(String)
158
- options[:encoding]
159
- elsif options[:encoding] == true
160
- "UTF-8"
161
- elsif xml_declaration&.dig(:encoding)
162
- xml_declaration[:encoding]
163
- end
164
-
165
- # Determine standalone
166
- # Priority: explicit standalone option > input standalone > none
167
- # Supported values: "yes", "no", true ("yes"), false ("no"), :preserve
168
- standalone = if options.key?(:standalone)
169
- case options[:standalone]
170
- when String
171
- options[:standalone]
172
- when true
173
- "yes"
174
- when false
175
- "no"
176
- when :preserve
177
- xml_declaration&.dig(:standalone)
178
- end
179
- elsif xml_declaration&.dig(:standalone)
180
- xml_declaration[:standalone]
181
- end
182
-
183
- declaration = "<?xml version=\"#{version}\""
184
- declaration += " encoding=\"#{encoding}\"" if encoding
185
- declaration += " standalone=\"#{standalone}\"" if standalone
186
- declaration += "?>\n"
187
- declaration
188
- end
189
-
190
- # Generate DOCTYPE declaration from doctype hash
191
- #
192
- # Supports both PUBLIC and SYSTEM DTDs.
193
- # Format: <!DOCTYPE name PUBLIC "public_id" "system_id">
194
- # <!DOCTYPE name SYSTEM "system_id">
195
- #
196
- # @param doctype [Hash] the doctype information
197
- # - :name => root element name
198
- # - :public_id => public identifier (optional)
199
- # - :system_id => system identifier (optional)
200
- # @return [String, nil] the DOCTYPE declaration or nil if no doctype
201
- def generate_doctype_declaration(doctype)
202
- return nil unless doctype
203
-
204
- parts = ["<!DOCTYPE #{doctype[:name]}"]
205
-
206
- if doctype[:public_id]
207
- parts << %(PUBLIC "#{doctype[:public_id]}")
208
- parts << %("#{doctype[:system_id]}") if doctype[:system_id]
209
- elsif doctype[:system_id]
210
- parts << %(SYSTEM "#{doctype[:system_id]}")
211
- end
212
-
213
- "#{parts.join(' ')}>\n"
214
- end
215
113
  end
216
114
  end
217
115
  end
@@ -350,11 +350,11 @@ module Lutaml
350
350
  # namespace :blank
351
351
  #
352
352
  # @raise [ArgumentError] if invalid arguments provided
353
- # @raise [Lutaml::Model::NoRootNamespaceError] if explicitly marked as no_root
353
+ # @raise [Lutaml::Model::TypeOnlyNamespaceError] if explicitly marked as no_root
354
354
  def namespace(ns_class_or_symbol, _deprecated_prefix = nil)
355
355
  # Only raise error for explicitly marked no_root (using deprecated method)
356
356
  # Type-only models (no element declared) CAN have namespaces
357
- raise Lutaml::Model::NoRootNamespaceError if @no_root
357
+ raise Lutaml::Model::TypeOnlyNamespaceError if @no_root
358
358
 
359
359
  # Warn if prefix parameter is provided
360
360
  if _deprecated_prefix
@@ -784,7 +784,7 @@ module Lutaml
784
784
  #
785
785
  # @example
786
786
  # xml do
787
- # root "rfc"
787
+ # element "rfc"
788
788
  # map_processing_instruction "rfc", to: :pi_settings
789
789
  # end
790
790
  #
@@ -10,7 +10,7 @@ module Lutaml
10
10
  attribute :content, :string
11
11
 
12
12
  xml do
13
- root "documentation"
13
+ element "documentation"
14
14
  namespace Lutaml::Xml::Schema::XsdNamespace
15
15
 
16
16
  map_all to: :content
@@ -6,7 +6,7 @@ module Lutaml
6
6
  # XML-specific overrides for Collection class methods.
7
7
  #
8
8
  # Prepended into Collection's singleton class when XML is loaded.
9
- # Provides XML-specific no_root handling for collections.
9
+ # Provides XML-specific unwrapped collection handling.
10
10
  module CollectionExt
11
11
  # XML is a structured (tree-based) format
12
12
  def collection_structured_format?(format)
@@ -15,15 +15,15 @@ module Lutaml
15
15
  true
16
16
  end
17
17
 
18
- # XML handles no_root serialization specially
19
- def collection_no_root_to?(format)
18
+ # XML handles unwrapped serialization specially
19
+ def collection_unwrapped_to?(format)
20
20
  return super unless format == :xml
21
21
 
22
22
  true
23
23
  end
24
24
 
25
- # XML no_root serialization: serialize each mapping separately
26
- def collection_no_root_to(format, mappings, instance, options)
25
+ # XML unwrapped serialization: serialize each mapping separately
26
+ def collection_unwrapped_to(format, mappings, instance, options)
27
27
  return super unless format == :xml
28
28
 
29
29
  mappings.mappings.map do |mapping|
@@ -31,8 +31,8 @@ module Lutaml
31
31
  end.join("\n")
32
32
  end
33
33
 
34
- # XML no_root: wrap raw XML in a fake root tag before parsing
35
- def wrap_no_root_input(format, mappings, data)
34
+ # XML unwrapped: wrap raw XML in a fake root tag before parsing
35
+ def wrap_unwrapped_input(format, mappings, data)
36
36
  return super unless format == :xml
37
37
 
38
38
  tag_name = mappings.find_by_to!(instance_name).name
@@ -94,7 +94,7 @@ module Lutaml
94
94
  return super unless format == :xml
95
95
 
96
96
  valid = root?(register) || options[:from_collection]
97
- raise Lutaml::Model::NoRootMappingError.new(self) unless valid
97
+ raise Lutaml::Model::TypeOnlyMappingError.new(self) unless valid
98
98
 
99
99
  options[:encoding] = doc.encoding
100
100
  if doc.is_a?(Lutaml::Xml::Document) && doc.doctype
@@ -204,7 +204,7 @@ module Lutaml
204
204
  return super unless format == :xml
205
205
  return if options[:collection] || self.class.root?(lutaml_register)
206
206
 
207
- raise Lutaml::Model::NoRootMappingError.new(self.class)
207
+ raise Lutaml::Model::TypeOnlyMappingError.new(self.class)
208
208
  end
209
209
 
210
210
  # XML-specific instance options preparation
data/lib/lutaml/xml.rb CHANGED
@@ -134,7 +134,6 @@ module Lutaml
134
134
  autoload :ElementPrefixResolver, "#{__dir__}/xml/element_prefix_resolver"
135
135
  autoload :FormatChooser, "#{__dir__}/xml/format_chooser"
136
136
  autoload :HoistingAlgorithm, "#{__dir__}/xml/hoisting_algorithm"
137
- autoload :HoistingAlgorithm, "#{__dir__}/xml/hoisting_algorithm"
138
137
  autoload :NamespaceInheritanceResolver,
139
138
  "#{__dir__}/xml/namespace_inheritance_resolver"
140
139
  autoload :NamespaceScopeConfig, "#{__dir__}/xml/namespace_scope_config"
@@ -229,7 +228,7 @@ end
229
228
  Lutaml::Model::Attribute.format_specific_warn_names.push(:element_order,
230
229
  :schema_location, :encoding, :doctype, :ordered?, :mixed?)
231
230
 
232
- # Prepend XML-specific Collection overrides (no_root handling for XML)
231
+ # Prepend XML-specific Collection overrides (unwrapped collection handling for XML)
233
232
  require_relative "xml/serialization/collection_ext"
234
233
  Lutaml::Model::Collection.singleton_class.prepend(
235
234
  Lutaml::Xml::Serialization::CollectionExt,
@@ -18,7 +18,7 @@ class Address < Lutaml::Model::Serializable
18
18
  attribute :country, :string
19
19
 
20
20
  xml do
21
- root "address"
21
+ element "address"
22
22
  map_element "street", to: :street
23
23
  map_element "city", to: :city
24
24
  map_element "zip", to: :zip
@@ -37,7 +37,7 @@ class Person < Lutaml::Model::Serializable
37
37
  attribute :tags, :string, collection: true
38
38
 
39
39
  xml do
40
- root "person"
40
+ element "person"
41
41
  map_attribute "id", to: :id
42
42
  map_element "first_name", to: :first_name
43
43
  map_element "last_name", to: :last_name
@@ -21,7 +21,7 @@ module Lutaml
21
21
  attribute :created_at, :date_time
22
22
 
23
23
  xml do
24
- root "simple"
24
+ element "simple"
25
25
  map_attribute "id", to: :id
26
26
  map_element "name", to: :name
27
27
  map_element "active", to: :active
@@ -46,7 +46,7 @@ module Lutaml
46
46
  attribute :country, :string
47
47
 
48
48
  xml do
49
- root "address"
49
+ element "address"
50
50
  map_element "street", to: :street
51
51
  map_element "city", to: :city
52
52
  map_element "zip", to: :zip
@@ -73,7 +73,7 @@ module Lutaml
73
73
  attribute :tags, :string, collection: true
74
74
 
75
75
  xml do
76
- root "person"
76
+ element "person"
77
77
  map_attribute "id", to: :id
78
78
  map_element "first_name", to: :first_name
79
79
  map_element "last_name", to: :last_name
@@ -103,7 +103,7 @@ module Lutaml
103
103
  attribute :price, :float
104
104
 
105
105
  xml do
106
- root "item"
106
+ element "item"
107
107
  map_attribute "product_id", to: :product_id
108
108
  map_element "quantity", to: :quantity
109
109
  map_element "price", to: :price
@@ -118,7 +118,7 @@ module Lutaml
118
118
  attribute :status, :string
119
119
 
120
120
  xml do
121
- root "order"
121
+ element "order"
122
122
  map_attribute "id", to: :id
123
123
  map_element "customer", to: :customer
124
124
  map_element "item", to: :items
@@ -14,7 +14,7 @@ RSpec.describe Lutaml::KeyValue::Transformation::RuleCompiler do
14
14
  attribute :active, :boolean
15
15
 
16
16
  json do
17
- root "person"
17
+ key "person"
18
18
  map "name", to: :name
19
19
  map "age", to: :age
20
20
  map "active", to: :active
@@ -91,7 +91,7 @@ RSpec.describe Lutaml::KeyValue::Transformation::ValueSerializer do
91
91
  Class.new(Lutaml::Model::Serializable) do
92
92
  attribute :name, :string
93
93
  json do
94
- root "nested"
94
+ key "nested"
95
95
  map "name", to: :name
96
96
  end
97
97
  end
@@ -19,7 +19,7 @@ module AttributeCollection
19
19
  end
20
20
 
21
21
  key_value do
22
- root "titles"
22
+ key "titles"
23
23
  map_instances to: :title_parts
24
24
  end
25
25
 
@@ -33,7 +33,7 @@ RSpec.describe Lutaml::Model::Cli do
33
33
  attribute :extract_language, ExtractLanguage, collection: true
34
34
 
35
35
  xml do
36
- root "termium_extract"
36
+ element "termium_extract"
37
37
  namespace TermiumNamespace
38
38
 
39
39
  map_attribute "language", to: :language, namespace: nil