lutaml-model 0.1.0 → 0.2.1
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/rake.yml +1 -1
- data/.github/workflows/release.yml +2 -2
- data/.rubocop.yml +1 -1
- data/Gemfile +20 -0
- data/lib/lutaml/model/attribute.rb +3 -1
- data/lib/lutaml/model/key_value_mapping.rb +7 -1
- data/lib/lutaml/model/mapping_rule.rb +9 -0
- data/lib/lutaml/model/schema/json_schema.rb +10 -20
- data/lib/lutaml/model/schema/relaxng_schema.rb +9 -19
- data/lib/lutaml/model/schema/xsd_schema.rb +11 -24
- data/lib/lutaml/model/schema/yaml_schema.rb +11 -21
- data/lib/lutaml/model/serialize.rb +181 -25
- data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +3 -2
- data/lib/lutaml/model/type.rb +138 -39
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +177 -47
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +5 -8
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +132 -20
- data/lib/lutaml/model/xml_adapter.rb +109 -8
- data/lib/lutaml/model/xml_mapping.rb +53 -5
- data/lib/lutaml/model/xml_mapping_rule.rb +27 -3
- data/lib/lutaml/model/xml_namespace.rb +47 -0
- data/lib/lutaml/model/yaml_adapter.rb +3 -1
- data/lutaml-model.gemspec +2 -15
- metadata +9 -149
- data/.github/workflows/main.yml +0 -27
@@ -12,36 +12,83 @@ module Lutaml
|
|
12
12
|
new(root)
|
13
13
|
end
|
14
14
|
|
15
|
-
def initialize(root)
|
16
|
-
@root = root
|
17
|
-
end
|
18
|
-
|
19
15
|
def to_h
|
20
|
-
{ @root.name => parse_element(@root) }
|
16
|
+
# { @root.name => parse_element(@root) }
|
17
|
+
parse_element(@root)
|
21
18
|
end
|
22
19
|
|
23
20
|
def to_xml(options = {})
|
24
21
|
builder = Ox::Builder.new
|
25
22
|
build_element(builder, @root, options)
|
26
|
-
xml_data = Ox.dump(builder)
|
23
|
+
# xml_data = Ox.dump(builder)
|
24
|
+
xml_data = builder.to_s
|
27
25
|
options[:declaration] ? declaration(options) + xml_data : xml_data
|
28
26
|
end
|
29
27
|
|
30
28
|
private
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
# rubocop:disable Layout/LineLength
|
31
|
+
# rubocop:disable Layout/MethodLength
|
32
|
+
# rubocop:disable Metrics/AbcSize
|
33
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
34
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
35
|
+
def build_element(builder, element, _options = {})
|
36
|
+
return element.to_xml(builder) if element.is_a?(Lutaml::Model::XmlAdapter::OxElement)
|
37
|
+
|
38
|
+
xml_mapping = element.class.mappings_for(:xml)
|
39
|
+
return xml unless xml_mapping
|
40
|
+
|
41
|
+
attributes = build_attributes(element, xml_mapping)
|
42
|
+
|
43
|
+
builder.element(xml_mapping.root_element, attributes) do |el|
|
44
|
+
xml_mapping.elements.each do |element_rule|
|
45
|
+
if element_rule.delegate
|
46
|
+
attribute_def = element.send(element_rule.delegate).class.attributes[element_rule.to]
|
47
|
+
value = element.send(element_rule.delegate).send(element_rule.to)
|
48
|
+
else
|
49
|
+
attribute_def = element.class.attributes[element_rule.to]
|
50
|
+
value = element.send(element_rule.to)
|
51
|
+
end
|
52
|
+
|
53
|
+
val = if attribute_def.collection?
|
54
|
+
value
|
55
|
+
elsif value || element_rule.render_nil?
|
56
|
+
[value]
|
57
|
+
else
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
|
61
|
+
val.each do |v|
|
62
|
+
if attribute_def&.type&.<= Lutaml::Model::Serialize
|
63
|
+
handle_nested_elements(el, element_rule, v)
|
64
|
+
else
|
65
|
+
builder.element(element_rule.name) do |el|
|
66
|
+
el.text(attribute_def.type.serialize(v)) if v
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
37
70
|
end
|
38
|
-
|
71
|
+
# if element.children.any?
|
72
|
+
# element.children.each do |child|
|
73
|
+
# build_element(el, child, options)
|
74
|
+
# end
|
75
|
+
# elsif element.text
|
76
|
+
# el.text(element.text)
|
77
|
+
# end
|
39
78
|
end
|
40
79
|
end
|
80
|
+
# rubocop:enable Layout/LineLength
|
81
|
+
# rubocop:enable Layout/MethodLength
|
82
|
+
# rubocop:enable Metrics/AbcSize
|
83
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
84
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
41
85
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
86
|
+
def handle_nested_elements(builder, _element_rule, value)
|
87
|
+
case value
|
88
|
+
when Array
|
89
|
+
value.each { |val| build_element(builder, val) }
|
90
|
+
else
|
91
|
+
build_element(builder, value)
|
45
92
|
end
|
46
93
|
end
|
47
94
|
|
@@ -49,6 +96,7 @@ module Lutaml
|
|
49
96
|
result = { "_text" => element.text }
|
50
97
|
element.nodes.each do |child|
|
51
98
|
next if child.is_a?(Ox::Raw) || child.is_a?(Ox::Comment)
|
99
|
+
|
52
100
|
result[child.name] ||= []
|
53
101
|
result[child.name] << parse_element(child)
|
54
102
|
end
|
@@ -57,17 +105,81 @@ module Lutaml
|
|
57
105
|
end
|
58
106
|
|
59
107
|
class OxElement < Element
|
60
|
-
|
108
|
+
# rubocop:disable Metrics/AbcSize
|
109
|
+
# rubocop:disable Metrics/MethodLength
|
110
|
+
# rubocop:disable Layout/LineLength
|
111
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
112
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
113
|
+
def initialize(node, root_node: nil)
|
61
114
|
attributes = node.attributes.each_with_object({}) do |(name, value), hash|
|
62
|
-
|
115
|
+
if attribute_is_namespace?(name)
|
116
|
+
if root_node
|
117
|
+
root_node.add_namespace(Lutaml::Model::XmlNamespace.new(value,
|
118
|
+
name))
|
119
|
+
else
|
120
|
+
add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
|
121
|
+
end
|
122
|
+
else
|
123
|
+
namespace_prefix = name.to_s.split(":").first
|
124
|
+
if root_node && (n = root_node.namespaces[namespace_prefix])
|
125
|
+
namespace = n.uri
|
126
|
+
prefix = n.prefix
|
127
|
+
end
|
128
|
+
|
129
|
+
hash[name.to_s] = Attribute.new(
|
130
|
+
name.to_s,
|
131
|
+
value,
|
132
|
+
namespace: namespace,
|
133
|
+
namespace_prefix: prefix,
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
super(
|
139
|
+
node.name.to_s,
|
140
|
+
attributes,
|
141
|
+
parse_children(node, root_node: root_node || self),
|
142
|
+
node.text,
|
143
|
+
parent_document: root_node
|
144
|
+
)
|
145
|
+
end
|
146
|
+
# rubocop:enable Metrics/AbcSize
|
147
|
+
# rubocop:enable Metrics/MethodLength
|
148
|
+
# rubocop:enable Layout/LineLength
|
149
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
150
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
151
|
+
|
152
|
+
def to_xml(builder = nil)
|
153
|
+
builder ||= Ox::Builder.new
|
154
|
+
attrs = build_attributes(self)
|
155
|
+
|
156
|
+
builder.element(name, attrs) do |el|
|
157
|
+
if children.any?
|
158
|
+
children.each { |child| child.to_xml(el) }
|
159
|
+
elsif text
|
160
|
+
el.text(text)
|
161
|
+
end
|
63
162
|
end
|
64
|
-
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_attributes(node)
|
166
|
+
attrs = node.attributes.transform_values(&:value)
|
167
|
+
|
168
|
+
node.own_namespaces.each_value do |namespace|
|
169
|
+
attrs[namespace.attr_name] = namespace.uri
|
170
|
+
end
|
171
|
+
|
172
|
+
attrs
|
65
173
|
end
|
66
174
|
|
67
175
|
private
|
68
176
|
|
69
|
-
def parse_children(node)
|
70
|
-
node.nodes.select
|
177
|
+
def parse_children(node, root_node: nil)
|
178
|
+
node.nodes.select do |child|
|
179
|
+
child.is_a?(Ox::Element)
|
180
|
+
end.map do |child|
|
181
|
+
OxElement.new(child, root_node: root_node)
|
182
|
+
end
|
71
183
|
end
|
72
184
|
end
|
73
185
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# lib/lutaml/model/xml_adapter.rb
|
2
2
|
|
3
|
+
require_relative "xml_namespace"
|
4
|
+
|
3
5
|
module Lutaml
|
4
6
|
module Model
|
5
7
|
module XmlAdapter
|
@@ -19,30 +21,129 @@ module Lutaml
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def declaration(options)
|
22
|
-
version =
|
23
|
-
|
24
|
+
version = "1.0"
|
25
|
+
version = options[:declaration] if options[:declaration].is_a?(String)
|
26
|
+
|
27
|
+
encoding = options[:encoding] ? "UTF-8" : nil
|
28
|
+
encoding = options[:encoding] if options[:encoding].is_a?(String)
|
29
|
+
|
24
30
|
declaration = "<?xml version=\"#{version}\""
|
25
31
|
declaration += " encoding=\"#{encoding}\"" if encoding
|
26
32
|
declaration += "?>\n"
|
27
33
|
declaration
|
28
34
|
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/AbcSize
|
37
|
+
# rubocop:disable Metrics/MethodLength
|
38
|
+
def build_attributes(element, xml_mapping)
|
39
|
+
attrs = namespace_attributes(xml_mapping)
|
40
|
+
|
41
|
+
xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
|
42
|
+
if mapping_rule.namespace
|
43
|
+
hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
44
|
+
end
|
45
|
+
|
46
|
+
hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
|
47
|
+
end
|
48
|
+
|
49
|
+
xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
|
50
|
+
if mapping_rule.namespace
|
51
|
+
hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# rubocop:enable Metrics/AbcSize
|
56
|
+
# rubocop:enable Metrics/MethodLength
|
57
|
+
|
58
|
+
def namespace_attributes(xml_mapping)
|
59
|
+
return {} unless xml_mapping.namespace_uri
|
60
|
+
|
61
|
+
key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
|
62
|
+
{ key => xml_mapping.namespace_uri }
|
63
|
+
end
|
29
64
|
end
|
30
65
|
|
31
66
|
class Element
|
32
|
-
attr_reader :
|
67
|
+
attr_reader :attributes,
|
68
|
+
:children,
|
69
|
+
:text,
|
70
|
+
:namespace_prefix,
|
71
|
+
:parent_document
|
33
72
|
|
34
|
-
|
35
|
-
|
36
|
-
|
73
|
+
# rubocop:disable Metrics/ParameterLists
|
74
|
+
def initialize(
|
75
|
+
name,
|
76
|
+
attributes = {},
|
77
|
+
children = [],
|
78
|
+
text = nil,
|
79
|
+
parent_document: nil,
|
80
|
+
namespace_prefix: nil
|
81
|
+
)
|
82
|
+
@name = extract_name(name)
|
83
|
+
@namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
|
84
|
+
@attributes = attributes # .map { |k, v| Attribute.new(k, v) }
|
37
85
|
@children = children
|
38
86
|
@text = text
|
39
|
-
@
|
40
|
-
|
87
|
+
@parent_document = parent_document
|
88
|
+
end
|
89
|
+
# rubocop:enable Metrics/ParameterLists
|
90
|
+
|
91
|
+
def name
|
92
|
+
if namespace_prefix && namespaces[namespace_prefix]
|
93
|
+
"#{namespace_prefix}:#{@name}"
|
94
|
+
else
|
95
|
+
@name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def unprefixed_name
|
100
|
+
@name
|
41
101
|
end
|
42
102
|
|
43
103
|
def document
|
44
104
|
Document.new(self)
|
45
105
|
end
|
106
|
+
|
107
|
+
def namespaces
|
108
|
+
@namespaces || @parent_document&.namespaces || {}
|
109
|
+
end
|
110
|
+
|
111
|
+
def own_namespaces
|
112
|
+
@namespaces || {}
|
113
|
+
end
|
114
|
+
|
115
|
+
def namespace
|
116
|
+
return default_namespace unless namespace_prefix
|
117
|
+
|
118
|
+
namespaces[namespace_prefix]
|
119
|
+
end
|
120
|
+
|
121
|
+
def attribute_is_namespace?(name)
|
122
|
+
name.to_s.start_with?("xmlns")
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_namespace(namespace)
|
126
|
+
@namespaces ||= {}
|
127
|
+
@namespaces[namespace.prefix] = namespace
|
128
|
+
end
|
129
|
+
|
130
|
+
def default_namespace
|
131
|
+
namespaces[nil] || @parent_document&.namespaces&.dig(nil)
|
132
|
+
end
|
133
|
+
|
134
|
+
def extract_name(name)
|
135
|
+
n = name.to_s.split(":")
|
136
|
+
return name if n.length <= 1
|
137
|
+
|
138
|
+
n[1..].join(":")
|
139
|
+
end
|
140
|
+
|
141
|
+
def extract_namespace_prefix(name)
|
142
|
+
n = name.to_s.split(":")
|
143
|
+
return if n.length <= 1
|
144
|
+
|
145
|
+
n.first
|
146
|
+
end
|
46
147
|
end
|
47
148
|
|
48
149
|
class Attribute
|
@@ -16,21 +16,69 @@ module Lutaml
|
|
16
16
|
@root_element = name
|
17
17
|
end
|
18
18
|
|
19
|
+
def prefixed_root
|
20
|
+
if namespace_uri && namespace_prefix
|
21
|
+
"#{namespace_prefix}:#{root_element}"
|
22
|
+
else
|
23
|
+
root_element
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
19
27
|
def namespace(uri, prefix = nil)
|
20
28
|
@namespace_uri = uri
|
21
29
|
@namespace_prefix = prefix
|
22
30
|
end
|
23
31
|
|
24
|
-
|
25
|
-
|
32
|
+
# rubocop:disable Metrics/ParameterLists
|
33
|
+
def map_element(
|
34
|
+
name,
|
35
|
+
to:,
|
36
|
+
render_nil: false,
|
37
|
+
with: {},
|
38
|
+
delegate: nil,
|
39
|
+
namespace: nil,
|
40
|
+
prefix: nil
|
41
|
+
)
|
42
|
+
@elements << XmlMappingRule.new(
|
43
|
+
name,
|
44
|
+
to: to,
|
45
|
+
render_nil: render_nil,
|
46
|
+
with: with,
|
47
|
+
delegate: delegate,
|
48
|
+
namespace: namespace,
|
49
|
+
prefix: prefix,
|
50
|
+
)
|
26
51
|
end
|
27
52
|
|
28
|
-
def map_attribute(
|
29
|
-
|
53
|
+
def map_attribute(
|
54
|
+
name,
|
55
|
+
to:,
|
56
|
+
render_nil: false,
|
57
|
+
with: {},
|
58
|
+
delegate: nil,
|
59
|
+
namespace: nil,
|
60
|
+
prefix: nil
|
61
|
+
)
|
62
|
+
@attributes << XmlMappingRule.new(
|
63
|
+
name,
|
64
|
+
to: to,
|
65
|
+
render_nil: render_nil,
|
66
|
+
with: with,
|
67
|
+
delegate: delegate,
|
68
|
+
namespace: namespace,
|
69
|
+
prefix: prefix,
|
70
|
+
)
|
30
71
|
end
|
72
|
+
# rubocop:enable Metrics/ParameterLists
|
31
73
|
|
32
74
|
def map_content(to:, render_nil: false, with: {}, delegate: nil)
|
33
|
-
@content_mapping = XmlMappingRule.new(
|
75
|
+
@content_mapping = XmlMappingRule.new(
|
76
|
+
nil,
|
77
|
+
to: to,
|
78
|
+
render_nil: render_nil,
|
79
|
+
with: with,
|
80
|
+
delegate: delegate,
|
81
|
+
)
|
34
82
|
end
|
35
83
|
|
36
84
|
def elements
|
@@ -6,11 +6,35 @@ module Lutaml
|
|
6
6
|
class XmlMappingRule < MappingRule
|
7
7
|
attr_reader :namespace, :prefix
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
# rubocop:disable Metrics/ParameterLists
|
11
|
+
def initialize(
|
12
|
+
name,
|
13
|
+
to:,
|
14
|
+
render_nil: false,
|
15
|
+
with: {},
|
16
|
+
delegate: nil,
|
17
|
+
namespace: nil,
|
18
|
+
prefix: nil
|
19
|
+
)
|
20
|
+
super(
|
21
|
+
name,
|
22
|
+
to: to,
|
23
|
+
render_nil: render_nil,
|
24
|
+
with: with,
|
25
|
+
delegate: delegate
|
26
|
+
)
|
27
|
+
|
28
|
+
@namespace = if namespace.to_s == "inherit"
|
29
|
+
# we are using inherit_namespace in xml builder by
|
30
|
+
# default so no need to do anything here.
|
31
|
+
else
|
32
|
+
namespace
|
33
|
+
end
|
12
34
|
@prefix = prefix
|
13
35
|
end
|
36
|
+
# rubocop:enable Metrics/MethodLength
|
37
|
+
# rubocop:enable Metrics/ParameterLists
|
14
38
|
end
|
15
39
|
end
|
16
40
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_striing_literal: true
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
class XmlNamespace
|
6
|
+
# Return name
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
attr_accessor :uri
|
12
|
+
|
13
|
+
# Return prefix
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
attr_accessor :prefix
|
19
|
+
|
20
|
+
# Initialize instance
|
21
|
+
#
|
22
|
+
# @param [String, nil] name
|
23
|
+
# @param [String, nil] prefix
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
def initialize(uri = nil, prefix = nil)
|
27
|
+
@uri = uri
|
28
|
+
@prefix = normalize_prefix(prefix)
|
29
|
+
end
|
30
|
+
|
31
|
+
def normalize_prefix(prefix)
|
32
|
+
normalized_prefix = prefix.to_s.gsub(/xmlns:?/, "")
|
33
|
+
return if normalized_prefix.empty?
|
34
|
+
|
35
|
+
normalized_prefix
|
36
|
+
end
|
37
|
+
|
38
|
+
def attr_name
|
39
|
+
if prefix && !prefix.empty?
|
40
|
+
"xmlns:#{prefix}"
|
41
|
+
else
|
42
|
+
"xmlns"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -16,7 +16,9 @@ module Lutaml
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.parse(yaml)
|
19
|
-
YAML.safe_load(yaml,
|
19
|
+
YAML.safe_load(yaml,
|
20
|
+
permitted_classes: [Date, Time, DateTime, Symbol,
|
21
|
+
BigDecimal, Hash, Array])
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
data/lutaml-model.gemspec
CHANGED
@@ -18,33 +18,20 @@ Gem::Specification.new do |spec|
|
|
18
18
|
|
19
19
|
spec.bindir = "bin"
|
20
20
|
spec.require_paths = ["lib"]
|
21
|
-
spec.files = `git ls-files`.split("\n")
|
22
|
-
spec.test_files = `git ls-files -- {spec}/*`.split("\n")
|
23
21
|
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
24
22
|
|
25
23
|
# Specify which files should be added to the gem when it is released.
|
26
|
-
# The `git ls-files -z` loads the files in the
|
24
|
+
# The `git ls-files -z` loads the files in the
|
25
|
+
# RubyGem that have been added into git.
|
27
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
27
|
`git ls-files -z`.split("\x0").reject do |f|
|
29
28
|
f.match(%r{^(test|spec|features)/})
|
30
29
|
end
|
31
30
|
end
|
32
|
-
spec.bindir = "exe"
|
33
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
-
spec.require_paths = ["lib"]
|
35
32
|
|
36
33
|
# spec.add_runtime_dependency "expressir"
|
37
34
|
# spec.add_runtime_dependency "metanorma-cli"
|
38
35
|
# spec.add_runtime_dependency "shale"
|
39
36
|
# spec.add_runtime_dependency "thor", ">= 0.20"
|
40
|
-
spec.add_development_dependency "multi_json"
|
41
|
-
spec.add_development_dependency "tomlib"
|
42
|
-
spec.add_development_dependency "toml-rb"
|
43
|
-
spec.add_development_dependency "oga"
|
44
|
-
spec.add_development_dependency "ox"
|
45
|
-
spec.add_development_dependency "nokogiri"
|
46
|
-
spec.add_development_dependency "rubocop"
|
47
|
-
spec.add_development_dependency "rubocop-performance"
|
48
|
-
spec.add_development_dependency "rubocop-rails"
|
49
|
-
spec.add_development_dependency "equivalent-xml"
|
50
37
|
end
|