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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -5
  3. data/.rubocop_todo.yml +21 -101
  4. data/Gemfile +3 -18
  5. data/README.adoc +145 -6
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  9. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  10. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  11. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  12. data/lib/lutaml/model/json_adapter.rb +1 -31
  13. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  14. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  15. data/lib/lutaml/model/mapping_hash.rb +0 -2
  16. data/lib/lutaml/model/mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  18. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  19. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  21. data/lib/lutaml/model/schema.rb +0 -1
  22. data/lib/lutaml/model/serializable.rb +0 -1
  23. data/lib/lutaml/model/serialize.rb +22 -4
  24. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  25. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  26. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +2 -3
  27. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  28. data/lib/lutaml/model/toml_adapter.rb +0 -31
  29. data/lib/lutaml/model/type/date_time.rb +20 -0
  30. data/lib/lutaml/model/type/json.rb +34 -0
  31. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  32. data/lib/lutaml/model/type.rb +61 -124
  33. data/lib/lutaml/model/version.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +9 -8
  35. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  36. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +14 -13
  37. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  38. data/lib/lutaml/model/xml_adapter/xml_document.rb +185 -0
  39. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  40. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  41. data/lib/lutaml/model/xml_adapter.rb +0 -285
  42. data/lib/lutaml/model/xml_mapping.rb +7 -7
  43. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  44. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  45. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  46. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  47. data/lib/lutaml/model/yaml_adapter.rb +1 -27
  48. data/lib/lutaml/model.rb +0 -5
  49. metadata +17 -5
  50. 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 "../xml_adapter"
2
+ require_relative "xml_document"
4
3
 
5
4
  module Lutaml
6
5
  module Model
7
6
  module XmlAdapter
8
- class OxDocument < Document
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], xml_mapping.root_element].compact.join(":")
41
+ [options[:namespace_prefix], tag_name].compact.join(":")
42
42
  elsif xml_mapping.namespace_prefix
43
- "#{xml_mapping.namespace_prefix}:#{xml_mapping.root_element}"
43
+ "#{xml_mapping.namespace_prefix}:#{tag_name}"
44
44
  else
45
- xml_mapping.root_element
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
- builder.element(xml_mapping.root_element, attributes) do |el|
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 < Element
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(Lutaml::Model::XmlNamespace.new(value, name))
148
+ root_node.add_namespace(XmlNamespace.new(value, name))
148
149
  else
149
- add_namespace(Lutaml::Model::XmlNamespace.new(value, name))
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 ||= Lutaml::Model::XmlAdapter::XML_NAMESPACE_URI
160
+ namespace ||= XML_NAMESPACE_URI
160
161
  prefix = n.first
161
162
  end
162
163
 
163
- hash[name.to_s] = Attribute.new(
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