lutaml-model 0.3.1 → 0.3.3
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/.rubocop.yml +0 -5
- data/.rubocop_todo.yml +21 -101
- data/Gemfile +3 -18
- data/README.adoc +145 -6
- data/lib/lutaml/model/attribute.rb +15 -2
- data/lib/lutaml/model/config.rb +0 -1
- data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
- data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
- data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
- data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
- data/lib/lutaml/model/json_adapter.rb +1 -31
- data/lib/lutaml/model/key_value_mapping.rb +0 -1
- data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
- data/lib/lutaml/model/mapping_hash.rb +0 -2
- data/lib/lutaml/model/mapping_rule.rb +0 -1
- data/lib/lutaml/model/schema/json_schema.rb +0 -1
- data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
- data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
- data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
- data/lib/lutaml/model/schema.rb +0 -1
- data/lib/lutaml/model/serializable.rb +0 -1
- data/lib/lutaml/model/serialize.rb +22 -4
- data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
- data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +2 -3
- data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
- data/lib/lutaml/model/toml_adapter.rb +0 -31
- data/lib/lutaml/model/type/date_time.rb +20 -0
- data/lib/lutaml/model/type/json.rb +34 -0
- data/lib/lutaml/model/type/time_without_date.rb +4 -3
- data/lib/lutaml/model/type.rb +61 -124
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +9 -8
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +14 -13
- data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
- data/lib/lutaml/model/xml_adapter/xml_document.rb +185 -0
- data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
- data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
- data/lib/lutaml/model/xml_adapter.rb +0 -285
- data/lib/lutaml/model/xml_mapping.rb +7 -7
- data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
- data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
- data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
- data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
- data/lib/lutaml/model/yaml_adapter.rb +1 -27
- data/lib/lutaml/model.rb +0 -5
- metadata +17 -5
- data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -1,11 +1,10 @@
|
|
1
|
-
# lib/lutaml/model/xml_adapter/ox_adapter.rb
|
2
1
|
require "ox"
|
3
|
-
require_relative "
|
2
|
+
require_relative "xml_document"
|
4
3
|
|
5
4
|
module Lutaml
|
6
5
|
module Model
|
7
6
|
module XmlAdapter
|
8
|
-
class
|
7
|
+
class OxAdapter < XmlDocument
|
9
8
|
def self.parse(xml)
|
10
9
|
parsed = Ox.parse(xml)
|
11
10
|
root = OxElement.new(parsed)
|
@@ -37,12 +36,13 @@ module Lutaml
|
|
37
36
|
|
38
37
|
attributes = build_attributes(element, xml_mapping).compact
|
39
38
|
|
39
|
+
tag_name = options[:tag_name] || xml_mapping.root_element
|
40
40
|
prefixed_name = if options.key?(:namespace_prefix)
|
41
|
-
[options[:namespace_prefix],
|
41
|
+
[options[:namespace_prefix], tag_name].compact.join(":")
|
42
42
|
elsif xml_mapping.namespace_prefix
|
43
|
-
"#{xml_mapping.namespace_prefix}:#{
|
43
|
+
"#{xml_mapping.namespace_prefix}:#{tag_name}"
|
44
44
|
else
|
45
|
-
|
45
|
+
tag_name
|
46
46
|
end
|
47
47
|
|
48
48
|
builder.element(prefixed_name, attributes) do |el|
|
@@ -85,7 +85,8 @@ module Lutaml
|
|
85
85
|
|
86
86
|
attributes = build_attributes(element, xml_mapping).compact
|
87
87
|
|
88
|
-
|
88
|
+
tag_name = options[:tag_name] || xml_mapping.root_element
|
89
|
+
builder.element(tag_name, attributes) do |el|
|
89
90
|
index_hash = {}
|
90
91
|
|
91
92
|
element.element_order.each do |name|
|
@@ -137,16 +138,16 @@ module Lutaml
|
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
140
|
-
class OxElement <
|
141
|
+
class OxElement < XmlElement
|
141
142
|
def initialize(node, root_node: nil)
|
142
143
|
if node.is_a?(String)
|
143
144
|
super("text", {}, [], node, parent_document: root_node)
|
144
145
|
else
|
145
146
|
namespace_attributes(node.attributes).each do |(name, value)|
|
146
147
|
if root_node
|
147
|
-
root_node.add_namespace(
|
148
|
+
root_node.add_namespace(XmlNamespace.new(value, name))
|
148
149
|
else
|
149
|
-
add_namespace(
|
150
|
+
add_namespace(XmlNamespace.new(value, name))
|
150
151
|
end
|
151
152
|
end
|
152
153
|
|
@@ -156,11 +157,11 @@ module Lutaml
|
|
156
157
|
namespace_prefix = name.to_s.split(":").first
|
157
158
|
if (n = name.to_s.split(":")).length > 1
|
158
159
|
namespace = (root_node || self).namespaces[namespace_prefix]&.uri
|
159
|
-
namespace ||=
|
160
|
+
namespace ||= XML_NAMESPACE_URI
|
160
161
|
prefix = n.first
|
161
162
|
end
|
162
163
|
|
163
|
-
hash[name.to_s] =
|
164
|
+
hash[name.to_s] = XmlAttribute.new(
|
164
165
|
name.to_s,
|
165
166
|
value,
|
166
167
|
namespace: namespace,
|
@@ -173,7 +174,7 @@ module Lutaml
|
|
173
174
|
attributes,
|
174
175
|
parse_children(node, root_node: root_node || self),
|
175
176
|
node.text,
|
176
|
-
parent_document: root_node
|
177
|
+
parent_document: root_node,
|
177
178
|
)
|
178
179
|
end
|
179
180
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module XmlAdapter
|
6
|
+
# Represents an XML attribute
|
7
|
+
class XmlAttribute
|
8
|
+
attr_reader :name, :value, :namespace, :namespace_prefix
|
9
|
+
|
10
|
+
def initialize(name, value, namespace: nil, namespace_prefix: nil)
|
11
|
+
@name = name
|
12
|
+
@value = value
|
13
|
+
@namespace = namespace
|
14
|
+
@namespace_prefix = namespace_prefix
|
15
|
+
end
|
16
|
+
|
17
|
+
def unprefixed_name
|
18
|
+
if namespace_prefix
|
19
|
+
name.split(":").last
|
20
|
+
else
|
21
|
+
name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require_relative "../mapping_hash"
|
2
|
+
require_relative "xml_element"
|
3
|
+
require_relative "xml_attribute"
|
4
|
+
require_relative "xml_namespace"
|
5
|
+
|
6
|
+
module Lutaml
|
7
|
+
module Model
|
8
|
+
module XmlAdapter
|
9
|
+
class XmlDocument
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
def initialize(root)
|
13
|
+
@root = root
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(xml)
|
17
|
+
raise NotImplementedError, "Subclasses must implement `parse`."
|
18
|
+
end
|
19
|
+
|
20
|
+
def children
|
21
|
+
@root.children
|
22
|
+
end
|
23
|
+
|
24
|
+
def declaration(options)
|
25
|
+
version = "1.0"
|
26
|
+
version = options[:declaration] if options[:declaration].is_a?(String)
|
27
|
+
|
28
|
+
encoding = options[:encoding] ? "UTF-8" : nil
|
29
|
+
encoding = options[:encoding] if options[:encoding].is_a?(String)
|
30
|
+
|
31
|
+
declaration = "<?xml version=\"#{version}\""
|
32
|
+
declaration += " encoding=\"#{encoding}\"" if encoding
|
33
|
+
declaration += "?>\n"
|
34
|
+
declaration
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
parse_element(@root)
|
39
|
+
end
|
40
|
+
|
41
|
+
def order
|
42
|
+
@root.order
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_nested_elements(builder, value, rule: nil, attribute: nil)
|
46
|
+
options = build_options_for_nested_elements(attribute, rule)
|
47
|
+
|
48
|
+
case value
|
49
|
+
when Array
|
50
|
+
value.each { |val| build_element(builder, val, options) }
|
51
|
+
else
|
52
|
+
build_element(builder, value, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_options_for_nested_elements(attribute, rule)
|
57
|
+
return {} unless rule
|
58
|
+
|
59
|
+
options = {}
|
60
|
+
|
61
|
+
options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
|
62
|
+
options[:mixed_content] = rule.mixed_content
|
63
|
+
options[:tag_name] = rule.name
|
64
|
+
|
65
|
+
options[:mapper_class] = attribute&.type if attribute
|
66
|
+
|
67
|
+
options
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_element(element)
|
71
|
+
result = Lutaml::Model::MappingHash.new
|
72
|
+
result.item_order = element.order
|
73
|
+
|
74
|
+
element.children.each_with_object(result) do |child, hash|
|
75
|
+
value = child.text? ? child.text : parse_element(child)
|
76
|
+
|
77
|
+
if hash[child.unprefixed_name]
|
78
|
+
hash[child.unprefixed_name] =
|
79
|
+
[hash[child.unprefixed_name], value].flatten
|
80
|
+
else
|
81
|
+
hash[child.unprefixed_name] = value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
element.attributes.each_value do |attr|
|
86
|
+
result[attr.unprefixed_name] = attr.value
|
87
|
+
end
|
88
|
+
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_element(xml, element, options = {})
|
93
|
+
if ordered?(element, options)
|
94
|
+
build_ordered_element(xml, element, options)
|
95
|
+
else
|
96
|
+
build_unordered_element(xml, element, options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def ordered?(element, options = {})
|
101
|
+
return false unless element.respond_to?(:element_order)
|
102
|
+
return element.ordered? if element.respond_to?(:ordered?)
|
103
|
+
return options[:mixed_content] if options.key?(:mixed_content)
|
104
|
+
|
105
|
+
mapper_class = options[:mapper_class]
|
106
|
+
mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_namespace_attributes(klass, processed = {})
|
110
|
+
xml_mappings = klass.mappings_for(:xml)
|
111
|
+
attributes = klass.attributes
|
112
|
+
|
113
|
+
attrs = {}
|
114
|
+
|
115
|
+
if xml_mappings.namespace_prefix
|
116
|
+
attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
|
117
|
+
xml_mappings.namespace_uri
|
118
|
+
end
|
119
|
+
|
120
|
+
xml_mappings.mappings.each do |mapping_rule|
|
121
|
+
processed[klass] ||= {}
|
122
|
+
|
123
|
+
next if processed[klass][mapping_rule.name]
|
124
|
+
|
125
|
+
processed[klass][mapping_rule.name] = true
|
126
|
+
|
127
|
+
type = if mapping_rule.delegate
|
128
|
+
attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
|
129
|
+
else
|
130
|
+
attributes[mapping_rule.to].type
|
131
|
+
end
|
132
|
+
|
133
|
+
if type <= Lutaml::Model::Serialize
|
134
|
+
attrs = attrs.merge(build_namespace_attributes(type, processed))
|
135
|
+
end
|
136
|
+
|
137
|
+
if mapping_rule.namespace
|
138
|
+
attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
attrs
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_attributes(element, xml_mapping)
|
146
|
+
attrs = namespace_attributes(xml_mapping)
|
147
|
+
|
148
|
+
xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
|
149
|
+
if mapping_rule.namespace
|
150
|
+
hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
151
|
+
end
|
152
|
+
|
153
|
+
hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
|
154
|
+
end
|
155
|
+
|
156
|
+
xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
|
157
|
+
if mapping_rule.namespace
|
158
|
+
hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def attribute_definition_for(element, rule, mapper_class: nil)
|
164
|
+
klass = mapper_class || element.class
|
165
|
+
return klass.attributes[rule.to] unless rule.delegate
|
166
|
+
|
167
|
+
element.send(rule.delegate).class.attributes[rule.to]
|
168
|
+
end
|
169
|
+
|
170
|
+
def attribute_value_for(element, rule)
|
171
|
+
return element.send(rule.to) unless rule.delegate
|
172
|
+
|
173
|
+
element.send(rule.delegate).send(rule.to)
|
174
|
+
end
|
175
|
+
|
176
|
+
def namespace_attributes(xml_mapping)
|
177
|
+
return {} unless xml_mapping.namespace_uri
|
178
|
+
|
179
|
+
key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
|
180
|
+
{ key => xml_mapping.namespace_uri }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative "xml_attribute"
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module XmlAdapter
|
6
|
+
class XmlElement
|
7
|
+
attr_reader :attributes,
|
8
|
+
:children,
|
9
|
+
:text,
|
10
|
+
:namespace_prefix,
|
11
|
+
:parent_document
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
name,
|
15
|
+
attributes = {},
|
16
|
+
children = [],
|
17
|
+
text = nil,
|
18
|
+
parent_document: nil,
|
19
|
+
namespace_prefix: nil
|
20
|
+
)
|
21
|
+
@name = extract_name(name)
|
22
|
+
@namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
|
23
|
+
@attributes = attributes # .map { |k, v| XmlAttribute.new(k, v) }
|
24
|
+
@children = children
|
25
|
+
@text = text
|
26
|
+
@parent_document = parent_document
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
if namespace_prefix
|
31
|
+
"#{namespace_prefix}:#{@name}"
|
32
|
+
else
|
33
|
+
@name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def unprefixed_name
|
38
|
+
@name
|
39
|
+
end
|
40
|
+
|
41
|
+
def document
|
42
|
+
XmlDocument.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def namespaces
|
46
|
+
@namespaces || @parent_document&.namespaces || {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def own_namespaces
|
50
|
+
@namespaces || {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def namespace
|
54
|
+
return default_namespace unless namespace_prefix
|
55
|
+
|
56
|
+
namespaces[namespace_prefix]
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute_is_namespace?(name)
|
60
|
+
name.to_s.start_with?("xmlns")
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_namespace(namespace)
|
64
|
+
@namespaces ||= {}
|
65
|
+
@namespaces[namespace.prefix] = namespace
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_namespace
|
69
|
+
namespaces[nil] || @parent_document&.namespaces&.dig(nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_name(name)
|
73
|
+
n = name.to_s.split(":")
|
74
|
+
return name if n.length <= 1
|
75
|
+
|
76
|
+
n[1..].join(":")
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_namespace_prefix(name)
|
80
|
+
n = name.to_s.split(":")
|
81
|
+
return if n.length <= 1
|
82
|
+
|
83
|
+
n.first
|
84
|
+
end
|
85
|
+
|
86
|
+
def order
|
87
|
+
children.each_with_object([]) do |child, arr|
|
88
|
+
arr << child.unprefixed_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_striing_literal: true
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module XmlAdapter
|
6
|
+
class XmlNamespace
|
7
|
+
# Return name
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
attr_accessor :uri
|
13
|
+
|
14
|
+
# Return prefix
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
attr_accessor :prefix
|
20
|
+
|
21
|
+
# Initialize instance
|
22
|
+
#
|
23
|
+
# @param [String, nil] name
|
24
|
+
# @param [String, nil] prefix
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
def initialize(uri = nil, prefix = nil)
|
28
|
+
@uri = uri
|
29
|
+
@prefix = normalize_prefix(prefix)
|
30
|
+
end
|
31
|
+
|
32
|
+
def normalize_prefix(prefix)
|
33
|
+
normalized_prefix = prefix.to_s.gsub(/xmlns:?/, "")
|
34
|
+
return if normalized_prefix.empty?
|
35
|
+
|
36
|
+
normalized_prefix
|
37
|
+
end
|
38
|
+
|
39
|
+
def attr_name
|
40
|
+
if prefix && !prefix.empty?
|
41
|
+
"xmlns:#{prefix}"
|
42
|
+
else
|
43
|
+
"xmlns"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|