lutaml-model 0.8.11 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/opal.yml +31 -0
- data/.rspec-opal +5 -0
- data/.rubocop_todo.yml +45 -34
- data/README.adoc +126 -104
- data/RELEASE_NOTES.adoc +3 -3
- data/benchmark/quick_benchmark.rb +2 -2
- data/benchmark/serialization_benchmark.rb +4 -4
- data/docs/_guides/advanced-mapping.adoc +1 -1
- data/docs/_guides/character-encoding.adoc +3 -3
- data/docs/_guides/index.adoc +4 -0
- data/docs/_guides/missing-values-handling.adoc +6 -6
- data/docs/_guides/ooxml-examples.adoc +7 -7
- data/docs/_guides/opal.adoc +221 -0
- data/docs/_guides/value-transformations.adoc +7 -7
- data/docs/_guides/xml/namespace-presentation.adoc +1 -1
- data/docs/_guides/xml/namespace-semantics.adoc +15 -15
- data/docs/_guides/xml/type-namespaces.adoc +9 -9
- data/docs/_guides/xml-mapping.adoc +32 -26
- data/docs/_guides/xml-namespace-qualification.adoc +4 -4
- data/docs/_guides/xml-namespaces.adoc +2 -2
- data/docs/_guides/xml_mappings/04_xml_namespace_class.adoc +18 -18
- data/docs/_guides/xml_mappings/05_common_patterns.adoc +16 -16
- data/docs/_guides/xml_mappings/06_migration_guide.adoc +5 -5
- data/docs/_guides/xml_mappings/07_best_practices.adoc +13 -12
- data/docs/_migrations/0-8-0-namespace-restructuring.adoc +2 -2
- data/docs/_pages/attributes.adoc +2 -2
- data/docs/_pages/collections.adoc +26 -20
- data/docs/_pages/configuration.adoc +9 -4
- data/docs/_pages/consolidation-mapping.adoc +4 -4
- data/docs/_pages/importable_models.adoc +14 -13
- data/docs/_pages/index.adoc +1 -0
- data/docs/_pages/quick-start.adoc +1 -1
- data/docs/_pages/serialization_adapters.adoc +3 -2
- data/docs/_pages/value_types.adoc +10 -10
- data/docs/_references/custom_registers.adoc +7 -7
- data/docs/_references/format-independent-features.adoc +4 -4
- data/docs/_references/instance-serialization.adoc +1 -1
- data/docs/_references/parent-root-context.adoc +3 -3
- data/docs/_tutorials/basic-model-definition.adoc +1 -1
- data/docs/_tutorials/first-xml-serialization.adoc +4 -4
- data/docs/_tutorials/lutaml-xml-architecture.adoc +4 -4
- data/docs/_tutorials/validation-basics.adoc +1 -1
- data/docs/_tutorials/working-with-collections.adoc +2 -2
- data/docs/_tutorials/xml-namespaces-basics.adoc +1 -1
- data/docs/_tutorials/xml-schema-primer-style-guide.adoc +29 -29
- data/docs/cli_compare.adoc +1 -1
- data/docs/index.adoc +2 -1
- data/docs/namespace-management.adoc +14 -14
- data/lib/lutaml/hash_format/adapter/mapping.rb +2 -4
- data/lib/lutaml/json/adapter/mapping.rb +2 -4
- data/lib/lutaml/jsonl/adapter/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/hash/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/json/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/jsonl/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/toml/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/yaml/mapping.rb +2 -4
- data/lib/lutaml/key_value/adapter/yamls/mapping.rb +2 -4
- data/lib/lutaml/key_value/mapping.rb +35 -10
- data/lib/lutaml/model/adapter_resolver.rb +5 -8
- data/lib/lutaml/model/collection.rb +11 -11
- data/lib/lutaml/model/error/no_root_mapping_error.rb +6 -5
- data/lib/lutaml/model/error/no_root_namespace_error.rb +6 -5
- data/lib/lutaml/model/error/type_only_mapping_error.rb +13 -0
- data/lib/lutaml/model/error/type_only_namespace_error.rb +12 -0
- data/lib/lutaml/model/mapping/mapping.rb +12 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +3 -0
- data/lib/lutaml/toml/adapter/mapping.rb +2 -4
- data/lib/lutaml/xml/adapter/base_adapter.rb +0 -9
- data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +0 -1
- data/lib/lutaml/xml/adapter/oga_adapter.rb +0 -1
- data/lib/lutaml/xml/adapter/ox_adapter.rb +0 -1
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +0 -1
- data/lib/lutaml/xml/adapter/xml_serializer.rb +42 -22
- data/lib/lutaml/xml/adapter.rb +4 -0
- data/lib/lutaml/xml/builder/base.rb +64 -25
- data/lib/lutaml/xml/builder/nokogiri.rb +0 -2
- data/lib/lutaml/xml/builder/oga.rb +0 -2
- data/lib/lutaml/xml/builder/ox.rb +0 -2
- data/lib/lutaml/xml/builder/rexml.rb +0 -2
- data/lib/lutaml/xml/builder.rb +1 -0
- data/lib/lutaml/xml/configurable.rb +2 -2
- data/lib/lutaml/xml/declaration_handler.rb +3 -105
- data/lib/lutaml/xml/mapping.rb +3 -3
- data/lib/lutaml/xml/schema/xsd/documentation.rb +1 -1
- data/lib/lutaml/xml/schema/xsd.rb +5 -4
- data/lib/lutaml/xml/schema.rb +8 -5
- data/lib/lutaml/xml/serialization/collection_ext.rb +7 -7
- data/lib/lutaml/xml/serialization/format_conversion.rb +1 -1
- data/lib/lutaml/xml/serialization/instance_methods.rb +1 -1
- data/lib/lutaml/xml/xml_orderable.rb +17 -0
- data/lib/lutaml/xml.rb +9 -13
- data/lib/lutaml/yaml/adapter/mapping.rb +2 -4
- data/lib/lutaml/yamls/adapter/mapping.rb +7 -3
- data/lib/tasks/memory_profile.rb +2 -2
- data/lib/tasks/performance_benchmark.rb +5 -5
- data/lutaml-model.gemspec +1 -1
- data/spec/lutaml/key_value/transformation/rule_compiler_spec.rb +1 -1
- data/spec/lutaml/key_value/transformation/value_serializer_spec.rb +1 -1
- data/spec/lutaml/model/attribute_collection_spec.rb +1 -1
- data/spec/lutaml/model/cli_spec.rb +1 -1
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/collection_validation_spec.rb +6 -6
- data/spec/lutaml/model/consolidation_spec.rb +8 -8
- data/spec/lutaml/model/custom_collection_spec.rb +3 -3
- data/spec/lutaml/model/default_register_spec.rb +23 -23
- data/spec/lutaml/model/delegation_spec.rb +3 -10
- data/spec/lutaml/model/derived_attribute_serialization_spec.rb +1 -1
- data/spec/lutaml/model/dynamic_attribute_spec.rb +2 -2
- data/spec/lutaml/model/enum_spec.rb +1 -1
- data/spec/lutaml/model/group_spec.rb +12 -12
- data/spec/lutaml/model/lazy_collection_spec.rb +4 -4
- data/spec/lutaml/model/mixed_content_spec.rb +2 -2
- data/spec/lutaml/model/namespace_versioning_spec.rb +4 -4
- data/spec/lutaml/model/opal_smoke_spec.rb +117 -0
- data/spec/lutaml/model/processing_instruction_spec.rb +11 -11
- data/spec/lutaml/model/register_methods_spec.rb +2 -2
- data/spec/lutaml/model/render_empty_spec.rb +1 -1
- data/spec/lutaml/model/serialize_perf_guard_spec.rb +1 -1
- data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +1 -1
- data/spec/lutaml/model/transformation_builder_spec.rb +2 -2
- data/spec/lutaml/model/xml_decoupling_spec.rb +3 -3
- data/spec/lutaml/model/xsd_patterns_spec.rb +2 -3
- data/spec/lutaml/xml/adapter/order_spec.rb +1 -1
- data/spec/lutaml/xml/clear_parse_state_spec.rb +1 -1
- data/spec/lutaml/xml/content_model_validation_spec.rb +4 -2
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +5 -5
- data/spec/lutaml/xml/enhanced_mapping_spec.rb +2 -1
- data/spec/lutaml/xml/entity_fragmentation_spec.rb +5 -5
- data/spec/lutaml/xml/indent_spec.rb +109 -0
- data/spec/lutaml/xml/line_ending_spec.rb +66 -0
- data/spec/lutaml/xml/mapping_finalization_guard_spec.rb +2 -2
- data/spec/lutaml/xml/model_transform_guard_spec.rb +4 -4
- data/spec/lutaml/xml/namespace_alias_spec.rb +4 -4
- data/spec/lutaml/xml/namespace_aware_parsing_spec.rb +3 -3
- data/spec/lutaml/xml/namespace_bound_element_roundtrip_spec.rb +2 -2
- data/spec/lutaml/xml/namespace_format_preservation_spec.rb +1 -1
- data/spec/lutaml/xml/namespace_inheritance_spec.rb +3 -3
- data/spec/lutaml/xml/namespace_preservation_spec.rb +5 -5
- data/spec/lutaml/xml/opal_xml_spec.rb +145 -0
- data/spec/lutaml/xml/pipeline_integration_spec.rb +145 -0
- data/spec/lutaml/xml/schema_primer_spec.rb +5 -5
- data/spec/lutaml/xml/transformation_spec.rb +20 -20
- data/spec/lutaml/xml/type_namespace/collector_spec.rb +1 -1
- data/spec/lutaml/xml/type_namespace/planner_spec.rb +3 -3
- data/spec/lutaml/xml/xml_spec.rb +64 -13
- data/spec/support/opal.rb +6 -0
- metadata +16 -4
|
@@ -21,20 +21,45 @@ module Lutaml
|
|
|
21
21
|
@finalized
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Set the wrapper key for key-value serialization (JSON, YAML, TOML).
|
|
25
|
+
#
|
|
26
|
+
# When set, serialized output wraps all instances under this key
|
|
27
|
+
# (e.g., `key "items"` produces `{"items": [...]}`).
|
|
28
|
+
# When not called (or called with nil), instances are serialized directly
|
|
29
|
+
# at the top level (e.g., `[...]`).
|
|
30
|
+
#
|
|
31
|
+
# @param name [String, nil] the wrapper key name
|
|
32
|
+
def key(name = nil)
|
|
33
|
+
@key = name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def key_name
|
|
37
|
+
@key
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @deprecated Use {#key} instead. In key-value formats, the wrapper is a key, not a root.
|
|
24
41
|
def root(name = nil)
|
|
25
|
-
@
|
|
42
|
+
@key = name
|
|
26
43
|
end
|
|
27
44
|
|
|
45
|
+
# @deprecated Omit key call instead. Not calling key means no wrapper key.
|
|
28
46
|
def no_root
|
|
29
|
-
@
|
|
47
|
+
@key = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns true when no wrapper key is set (instances serialized at top level).
|
|
51
|
+
def no_key?
|
|
52
|
+
@key.nil?
|
|
30
53
|
end
|
|
31
54
|
|
|
55
|
+
# @deprecated Use {#no_key?} instead.
|
|
32
56
|
def no_root?
|
|
33
|
-
|
|
57
|
+
no_key?
|
|
34
58
|
end
|
|
35
59
|
|
|
60
|
+
# @deprecated Use {#key_name} instead.
|
|
36
61
|
def root_name
|
|
37
|
-
@
|
|
62
|
+
@key
|
|
38
63
|
end
|
|
39
64
|
|
|
40
65
|
def map(
|
|
@@ -104,7 +129,7 @@ module Lutaml
|
|
|
104
129
|
|
|
105
130
|
def map_instances(to:, polymorphic: {})
|
|
106
131
|
@instance = to
|
|
107
|
-
map(
|
|
132
|
+
map(key_name || to, to: to, polymorphic: polymorphic)
|
|
108
133
|
map_to_instance
|
|
109
134
|
end
|
|
110
135
|
|
|
@@ -125,7 +150,7 @@ module Lutaml
|
|
|
125
150
|
def map_to_instance
|
|
126
151
|
return if !instance_mapping?
|
|
127
152
|
|
|
128
|
-
mapping_name = name_for_mapping(nil,
|
|
153
|
+
mapping_name = name_for_mapping(nil, key_name || @instance)
|
|
129
154
|
@mappings[mapping_name].child_mappings = @key_mapping.merge(@value_mapping)
|
|
130
155
|
end
|
|
131
156
|
|
|
@@ -218,17 +243,17 @@ module Lutaml
|
|
|
218
243
|
end
|
|
219
244
|
|
|
220
245
|
# Writers for deep_dup in subclasses
|
|
221
|
-
attr_writer :
|
|
246
|
+
attr_writer :register_mappings
|
|
222
247
|
|
|
223
248
|
def deep_dup
|
|
224
|
-
|
|
249
|
+
dup_instance.tap do |new_mapping|
|
|
225
250
|
new_mapping.mappings = duplicate_mappings
|
|
226
251
|
new_mapping.register_mappings = Lutaml::Model::Utils.deep_dup(@register_mappings)
|
|
227
252
|
end
|
|
228
253
|
end
|
|
229
254
|
|
|
230
|
-
def
|
|
231
|
-
|
|
255
|
+
def dup_instance
|
|
256
|
+
self.class.new(@format)
|
|
232
257
|
end
|
|
233
258
|
|
|
234
259
|
def find_by_to(to)
|
|
@@ -370,15 +370,12 @@ module Lutaml
|
|
|
370
370
|
|
|
371
371
|
# Detect available XML adapter.
|
|
372
372
|
#
|
|
373
|
-
#
|
|
373
|
+
# Delegates to moxml which is the authority on XML adapter
|
|
374
|
+
# availability and platform constraints (Opal, MRI, etc.).
|
|
375
|
+
#
|
|
376
|
+
# @return [Symbol] adapter type name
|
|
374
377
|
def detect_xml_adapter
|
|
375
|
-
|
|
376
|
-
return :nokogiri if Utils.safe_load("nokogiri", :Nokogiri)
|
|
377
|
-
return :ox if Utils.safe_load("ox", :Ox)
|
|
378
|
-
return :oga if Utils.safe_load("oga", :Oga)
|
|
379
|
-
return :rexml if Utils.safe_load("rexml", :REXML)
|
|
380
|
-
|
|
381
|
-
nil
|
|
378
|
+
Moxml::Config.runtime_default_adapter
|
|
382
379
|
end
|
|
383
380
|
|
|
384
381
|
# Detect available TOML adapter.
|
|
@@ -310,8 +310,8 @@ module Lutaml
|
|
|
310
310
|
def to(format, instance, options = {})
|
|
311
311
|
mappings = mappings_for(format)
|
|
312
312
|
|
|
313
|
-
if mappings.no_root? &&
|
|
314
|
-
|
|
313
|
+
if mappings.no_root? && collection_unwrapped_to?(format)
|
|
314
|
+
collection_unwrapped_to(format, mappings, instance, options)
|
|
315
315
|
else
|
|
316
316
|
super(format, instance, options.merge(collection: true))
|
|
317
317
|
end
|
|
@@ -322,7 +322,7 @@ module Lutaml
|
|
|
322
322
|
data = super
|
|
323
323
|
|
|
324
324
|
if !collection_structured_format?(format) && mappings.no_root? && !mappings.root_mapping
|
|
325
|
-
|
|
325
|
+
unwrap_unwrapped_data(data)
|
|
326
326
|
else
|
|
327
327
|
data
|
|
328
328
|
end
|
|
@@ -332,7 +332,7 @@ module Lutaml
|
|
|
332
332
|
mappings = mappings_for(format)
|
|
333
333
|
|
|
334
334
|
if collection_structured_format?(format) && mappings.no_root?
|
|
335
|
-
data =
|
|
335
|
+
data = wrap_unwrapped_input(format, mappings, data)
|
|
336
336
|
end
|
|
337
337
|
|
|
338
338
|
super(format, data, options.merge(from_collection: true))
|
|
@@ -355,21 +355,21 @@ module Lutaml
|
|
|
355
355
|
false
|
|
356
356
|
end
|
|
357
357
|
|
|
358
|
-
# Hook: returns true if this format handles
|
|
358
|
+
# Hook: returns true if this format handles unwrapped serialization specially.
|
|
359
359
|
# XML overrides to return true for :xml format.
|
|
360
|
-
def
|
|
360
|
+
def collection_unwrapped_to?(_format)
|
|
361
361
|
false
|
|
362
362
|
end
|
|
363
363
|
|
|
364
|
-
# Hook for
|
|
364
|
+
# Hook for unwrapped serialization (e.g., XML).
|
|
365
365
|
# XML overrides to serialize each mapping separately.
|
|
366
|
-
def
|
|
366
|
+
def collection_unwrapped_to(_format, _mappings, _instance, _options)
|
|
367
367
|
raise NotImplementedError
|
|
368
368
|
end
|
|
369
369
|
|
|
370
|
-
# Hook for
|
|
370
|
+
# Hook for wrapping unwrapped input (e.g., XML).
|
|
371
371
|
# XML overrides to wrap raw data in a fake root tag.
|
|
372
|
-
def
|
|
372
|
+
def wrap_unwrapped_input(_format, _mappings, data)
|
|
373
373
|
data
|
|
374
374
|
end
|
|
375
375
|
|
|
@@ -379,7 +379,7 @@ module Lutaml
|
|
|
379
379
|
|
|
380
380
|
private
|
|
381
381
|
|
|
382
|
-
def
|
|
382
|
+
def unwrap_unwrapped_data(data)
|
|
383
383
|
# Convert KeyValueElement to Hash if needed
|
|
384
384
|
hash = data.is_a?(Hash) ? data : data.to_hash
|
|
385
385
|
# Handle "__root__" wrapper for key-value formats (created by transformation)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "type_only_mapping_error"
|
|
4
|
+
|
|
1
5
|
module Lutaml
|
|
2
6
|
module Model
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
super("#{model} has `no_root`, it allowed only for reusable models")
|
|
6
|
-
end
|
|
7
|
-
end
|
|
7
|
+
# @deprecated Use {TypeOnlyMappingError} instead.
|
|
8
|
+
NoRootMappingError = TypeOnlyMappingError
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "type_only_namespace_error"
|
|
4
|
+
|
|
1
5
|
module Lutaml
|
|
2
6
|
module Model
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"Cannot assign namespace to `no_root`"
|
|
6
|
-
end
|
|
7
|
-
end
|
|
7
|
+
# @deprecated Use {TypeOnlyNamespaceError} instead.
|
|
8
|
+
NoRootNamespaceError = TypeOnlyNamespaceError
|
|
8
9
|
end
|
|
9
10
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Lutaml
|
|
2
|
+
module Model
|
|
3
|
+
class TypeOnlyMappingError < Error
|
|
4
|
+
def initialize(model)
|
|
5
|
+
super("#{model} is a type-only model (no element declared), " \
|
|
6
|
+
"it can only be used as an embedded type through a parent model.")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @deprecated Use {TypeOnlyMappingError} instead.
|
|
11
|
+
NoRootMappingError = TypeOnlyMappingError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Lutaml
|
|
2
|
+
module Model
|
|
3
|
+
class TypeOnlyNamespaceError < Error
|
|
4
|
+
def to_s
|
|
5
|
+
"Cannot assign namespace to a type-only model (no element declared)."
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @deprecated Use {TypeOnlyNamespaceError} instead.
|
|
10
|
+
NoRootNamespaceError = TypeOnlyNamespaceError
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -3,6 +3,8 @@ module Lutaml
|
|
|
3
3
|
class Mapping
|
|
4
4
|
include DeepDupable
|
|
5
5
|
|
|
6
|
+
attr_writer :mappings
|
|
7
|
+
|
|
6
8
|
def initialize
|
|
7
9
|
@mappings = []
|
|
8
10
|
@listeners = {} # target => [Listener, ...]
|
|
@@ -11,6 +13,16 @@ module Lutaml
|
|
|
11
13
|
@mappings_imported = ::Hash.new { |h, k| h[k] = false }
|
|
12
14
|
end
|
|
13
15
|
|
|
16
|
+
def deep_dup
|
|
17
|
+
duped = self.class.new
|
|
18
|
+
duped.mappings = duplicate_mappings
|
|
19
|
+
duped
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def duplicate_mappings
|
|
23
|
+
Lutaml::Model::Utils.deep_dup(@mappings)
|
|
24
|
+
end
|
|
25
|
+
|
|
14
26
|
# Get listeners for a specific target (element name/key).
|
|
15
27
|
#
|
|
16
28
|
# @param target [String, Symbol] The element name or key
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/model.rb
CHANGED
|
@@ -149,6 +149,7 @@ module Lutaml
|
|
|
149
149
|
"#{__dir__}/model/error/incorrect_sequence_error"
|
|
150
150
|
autoload :ChoiceUpperBoundError,
|
|
151
151
|
"#{__dir__}/model/error/choice_upper_bound_error"
|
|
152
|
+
autoload :TypeOnlyMappingError, "#{__dir__}/model/error/type_only_mapping_error"
|
|
152
153
|
autoload :NoRootMappingError, "#{__dir__}/model/error/no_root_mapping_error"
|
|
153
154
|
autoload :ImportModelWithRootError,
|
|
154
155
|
"#{__dir__}/model/error/import_model_with_root_error"
|
|
@@ -160,6 +161,8 @@ module Lutaml
|
|
|
160
161
|
"#{__dir__}/model/error/choice_lower_bound_error"
|
|
161
162
|
autoload :NoMappingFoundError,
|
|
162
163
|
"#{__dir__}/model/error/no_mapping_found_error"
|
|
164
|
+
autoload :TypeOnlyNamespaceError,
|
|
165
|
+
"#{__dir__}/model/error/type_only_namespace_error"
|
|
163
166
|
autoload :NoRootNamespaceError,
|
|
164
167
|
"#{__dir__}/model/error/no_root_namespace_error"
|
|
165
168
|
autoload :PolymorphicError, "#{__dir__}/model/error/polymorphic_error"
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../document"
|
|
4
|
-
require_relative "../declaration_handler"
|
|
5
|
-
require_relative "../doctype_extractor"
|
|
6
|
-
require_relative "../polymorphic_value_handler"
|
|
7
|
-
require_relative "xml_parser"
|
|
8
|
-
require_relative "xml_serializer"
|
|
9
|
-
require_relative "plan_based_builder"
|
|
10
|
-
require_relative "namespace_uri_collector"
|
|
11
|
-
|
|
12
3
|
module Lutaml
|
|
13
4
|
module Xml
|
|
14
5
|
module Adapter
|
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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)
|
data/lib/lutaml/xml/adapter.rb
CHANGED
|
@@ -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
|
|
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
|
|
169
|
+
def to_xml
|
|
120
170
|
return "" unless @doc.root
|
|
121
171
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|