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.
@@ -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
- def build_element(builder, element, options = {})
33
- attributes = build_attributes(element.attributes)
34
- builder.element(element.name, attributes) do
35
- element.children.each do |child|
36
- build_element(builder, child, options)
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
- builder.text(element.text) if element.text
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 build_attributes(attributes)
43
- attributes.each_with_object({}) do |(name, attr), hash|
44
- hash[name] = attr.value
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
- def initialize(node)
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
- hash[name.to_s] = Attribute.new(name.to_s, value)
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
- super(node.name.to_s, attributes, parse_children(node), node.text)
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 { |child| child.is_a?(Ox::Element) }.map { |child| OxElement.new(child) }
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 = options[:declaration].is_a?(String) ? options[:declaration] : "1.0"
23
- encoding = options[:encoding].is_a?(String) ? options[:encoding] : (options[:encoding] ? "UTF-8" : nil)
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 :name, :attributes, :children, :text, :namespace, :namespace_prefix
67
+ attr_reader :attributes,
68
+ :children,
69
+ :text,
70
+ :namespace_prefix,
71
+ :parent_document
33
72
 
34
- def initialize(name, attributes = {}, children = [], text = nil, namespace: nil, namespace_prefix: nil)
35
- @name = name
36
- @attributes = attributes.map { |k, v| Attribute.new(k, v) }
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
- @namespace = namespace
40
- @namespace_prefix = namespace_prefix
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
- def map_element(name, to:, render_nil: false, with: {}, delegate: nil, namespace: nil, prefix: nil)
25
- @elements << XmlMappingRule.new(name, to: to, render_nil: render_nil, with: with, delegate: delegate, namespace: namespace, prefix: prefix)
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(name, to:, render_nil: false, with: {}, delegate: nil, namespace: nil, prefix: nil)
29
- @attributes << XmlMappingRule.new(name, to: to, render_nil: render_nil, with: with, delegate: delegate, namespace: namespace, prefix: prefix)
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(nil, to: to, render_nil: render_nil, with: with, delegate: delegate)
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
- def initialize(name, to:, render_nil: false, with: {}, delegate: nil, namespace: nil, prefix: nil)
10
- super(name, to: to, render_nil: render_nil, with: with, delegate: delegate)
11
- @namespace = namespace
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, permitted_classes: [Date, Time, DateTime, Symbol, BigDecimal, Hash, Array])
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 RubyGem that have been added into git.
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