lutaml-model 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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