lutaml-model 0.3.30 → 0.5.0

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +34 -18
  3. data/README.adoc +172 -8
  4. data/lib/lutaml/model/attribute.rb +6 -2
  5. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  6. data/lib/lutaml/model/key_value_mapping_rule.rb +3 -1
  7. data/lib/lutaml/model/mapping_rule.rb +14 -2
  8. data/lib/lutaml/model/serialize.rb +174 -61
  9. data/lib/lutaml/model/type/decimal.rb +5 -0
  10. data/lib/lutaml/model/type/time.rb +4 -4
  11. data/lib/lutaml/model/utils.rb +1 -1
  12. data/lib/lutaml/model/validation.rb +6 -2
  13. data/lib/lutaml/model/version.rb +1 -1
  14. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
  15. data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
  16. data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
  17. data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
  18. data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
  19. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
  20. data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -9
  21. data/lib/lutaml/model/xml_mapping.rb +0 -1
  22. data/lib/lutaml/model/xml_mapping_rule.rb +16 -4
  23. data/spec/address_spec.rb +1 -0
  24. data/spec/fixtures/sample_model.rb +7 -0
  25. data/spec/lutaml/model/custom_model_spec.rb +47 -1
  26. data/spec/lutaml/model/custom_serialization_spec.rb +16 -0
  27. data/spec/lutaml/model/enum_spec.rb +131 -0
  28. data/spec/lutaml/model/included_spec.rb +192 -0
  29. data/spec/lutaml/model/mixed_content_spec.rb +48 -32
  30. data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
  31. data/spec/lutaml/model/ordered_content_spec.rb +1 -1
  32. data/spec/lutaml/model/render_nil_spec.rb +3 -2
  33. data/spec/lutaml/model/serializable_spec.rb +3 -3
  34. data/spec/lutaml/model/type/boolean_spec.rb +62 -0
  35. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
  36. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +1 -1
  37. data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
  38. data/spec/lutaml/model/xml_mapping_spec.rb +24 -9
  39. data/spec/sample_model_spec.rb +114 -0
  40. metadata +9 -2
@@ -21,6 +21,7 @@ module Lutaml
21
21
 
22
22
  def self.included(base)
23
23
  base.extend(ClassMethods)
24
+ base.initialize_attrs(base)
24
25
  end
25
26
 
26
27
  module ClassMethods
@@ -28,14 +29,18 @@ module Lutaml
28
29
 
29
30
  def inherited(subclass)
30
31
  super
32
+ subclass.initialize_attrs(self)
33
+ end
31
34
 
32
- @mappings ||= {}
33
- @attributes ||= {}
35
+ def included(base)
36
+ base.extend(ClassMethods)
37
+ base.initialize_attrs(self)
38
+ end
34
39
 
35
- subclass.instance_variable_set(:@attributes,
36
- Utils.deep_dup(@attributes))
37
- subclass.instance_variable_set(:@mappings, Utils.deep_dup(@mappings))
38
- subclass.instance_variable_set(:@model, subclass)
40
+ def initialize_attrs(source_class)
41
+ @mappings = Utils.deep_dup(source_class.instance_variable_get(:@mappings)) || {}
42
+ @attributes = Utils.deep_dup(source_class.instance_variable_get(:@attributes)) || {}
43
+ instance_variable_set(:@model, self)
39
44
  end
40
45
 
41
46
  def model(klass = nil)
@@ -81,16 +86,104 @@ module Lutaml
81
86
  attr = Attribute.new(name, type, options)
82
87
  attributes[name] = attr
83
88
 
84
- define_method(name) do
85
- instance_variable_get(:"@#{name}")
89
+ if attr.enum?
90
+ add_enum_methods_to_model(
91
+ model,
92
+ name,
93
+ options[:values],
94
+ collection: options[:collection],
95
+ )
96
+ else
97
+ define_method(name) do
98
+ instance_variable_get(:"@#{name}")
99
+ end
100
+
101
+ define_method(:"#{name}=") do |value|
102
+ value_set_for(name)
103
+ instance_variable_set(:"@#{name}", attr.cast_value(value))
104
+ end
86
105
  end
106
+ end
107
+
108
+ def add_enum_methods_to_model(klass, enum_name, values, collection: false)
109
+ add_enum_getter_if_not_defined(klass, enum_name, collection)
110
+ add_enum_setter_if_not_defined(klass, enum_name, values, collection)
87
111
 
88
- define_method(:"#{name}=") do |value|
89
- value_set_for(name)
90
- instance_variable_set(:"@#{name}", attr.cast_value(value))
112
+ return unless values.all?(::String)
113
+
114
+ values.each do |value|
115
+ Utils.add_method_if_not_defined(klass, "#{value}?") do
116
+ curr_value = public_send(:"#{enum_name}")
117
+
118
+ if collection
119
+ curr_value.include?(value)
120
+ else
121
+ curr_value == value
122
+ end
123
+ end
124
+
125
+ Utils.add_method_if_not_defined(klass, value.to_s) do
126
+ public_send(:"#{value}?")
127
+ end
128
+
129
+ Utils.add_method_if_not_defined(klass, "#{value}=") do |val|
130
+ value_set_for(enum_name)
131
+ enum_vals = public_send(:"#{enum_name}")
132
+
133
+ enum_vals = if !!val
134
+ if collection
135
+ enum_vals << value
136
+ else
137
+ [value]
138
+ end
139
+ elsif collection
140
+ enum_vals.delete(value)
141
+ enum_vals
142
+ else
143
+ []
144
+ end
145
+
146
+ instance_variable_set(:"@#{enum_name}", enum_vals)
147
+ end
148
+
149
+ Utils.add_method_if_not_defined(klass, "#{value}!") do
150
+ public_send(:"#{value}=", true)
151
+ end
91
152
  end
92
153
  end
93
154
 
155
+ def add_enum_getter_if_not_defined(klass, enum_name, collection)
156
+ Utils.add_method_if_not_defined(klass, enum_name) do
157
+ i = instance_variable_get(:"@#{enum_name}") || []
158
+
159
+ if !collection && i.is_a?(Array)
160
+ i.first
161
+ else
162
+ i.uniq
163
+ end
164
+ end
165
+ end
166
+
167
+ def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
168
+ Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
169
+ value = [value] unless value.is_a?(Array)
170
+
171
+ value_set_for(enum_name)
172
+
173
+ if collection
174
+ curr_value = public_send(:"#{enum_name}")
175
+
176
+ instance_variable_set(:"@#{enum_name}", curr_value + value)
177
+ else
178
+ instance_variable_set(:"@#{enum_name}", value)
179
+ end
180
+ end
181
+ end
182
+
183
+ def enums
184
+ attributes.select { |_, attr| attr.enum? }
185
+ end
186
+
94
187
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
95
188
  define_method(format) do |&block|
96
189
  klass = format == :xml ? XmlMapping : KeyValueMapping
@@ -135,7 +228,7 @@ module Lutaml
135
228
  end
136
229
  end
137
230
 
138
- define_method(:"as_#{format}") do |instance|
231
+ define_method(:"as_#{format}") do |instance, options = {}|
139
232
  if instance.is_a?(Array)
140
233
  return instance.map { |item| public_send(:"as_#{format}", item) }
141
234
  end
@@ -147,7 +240,7 @@ module Lutaml
147
240
 
148
241
  return instance if format == :xml
149
242
 
150
- hash_representation(instance, format)
243
+ hash_representation(instance, format, options)
151
244
  end
152
245
  end
153
246
 
@@ -166,7 +259,7 @@ module Lutaml
166
259
  mappings.each_with_object({}) do |rule, hash|
167
260
  name = rule.to
168
261
  next if except&.include?(name) || (only && !only.include?(name))
169
- next if !rule.render_default? && instance.using_default?(rule.to)
262
+ next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
170
263
 
171
264
  next handle_delegate(instance, rule, hash, format) if rule.delegate
172
265
 
@@ -176,15 +269,18 @@ module Lutaml
176
269
 
177
270
  value = instance.send(name)
178
271
 
179
- next if value.nil? && !rule.render_nil
180
-
181
272
  attribute = attributes[name]
182
273
 
183
- hash[rule.from] = if rule.child_mappings
184
- generate_hash_from_child_mappings(value, rule.child_mappings)
185
- else
186
- attribute.serialize(value, format, options)
187
- end
274
+ value = if rule.child_mappings
275
+ generate_hash_from_child_mappings(value, rule.child_mappings)
276
+ else
277
+ attribute.serialize(value, format, options)
278
+ end
279
+
280
+ next if Utils.blank?(value) && !rule.render_nil
281
+
282
+ rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
283
+ hash[rule_from_name] = value
188
284
  end
189
285
  end
190
286
 
@@ -194,39 +290,14 @@ module Lutaml
194
290
  return if value.nil? && !rule.render_nil
195
291
 
196
292
  attribute = instance.send(rule.delegate).class.attributes[name]
197
- hash[rule.from] = attribute.serialize(value, format)
293
+ rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
294
+ hash[rule_from_name] = attribute.serialize(value, format)
198
295
  end
199
296
 
200
297
  def mappings_for(format)
201
298
  mappings[format] || default_mappings(format)
202
299
  end
203
300
 
204
- def attr_value(attrs, name, attr_rule)
205
- value = if attrs.key?(name.to_sym)
206
- attrs[name.to_sym]
207
- elsif attrs.key?(name.to_s)
208
- attrs[name.to_s]
209
- else
210
- attr_rule.default
211
- end
212
-
213
- if attr_rule.collection? || value.is_a?(Array)
214
- (value || []).map do |v|
215
- if v.is_a?(Hash)
216
- attr_rule.type.new(v)
217
- else
218
- # TODO: This code is problematic because Type.cast does not know
219
- # about all the types.
220
- Lutaml::Model::Type.cast(v, attr_rule.type)
221
- end
222
- end
223
- else
224
- # TODO: This code is problematic because Type.cast does not know
225
- # about all the types.
226
- Lutaml::Model::Type.cast(value, attr_rule.type)
227
- end
228
- end
229
-
230
301
  def default_mappings(format)
231
302
  klass = format == :xml ? XmlMapping : KeyValueMapping
232
303
 
@@ -325,8 +396,7 @@ module Lutaml
325
396
  mappings = mappings_for(:xml).mappings
326
397
 
327
398
  if doc.is_a?(Array)
328
- raise "May be `collection: true` is" \
329
- "missing for #{self} in #{options[:caller_class]}"
399
+ raise "May be `collection: true` is missing for #{self} in #{options[:caller_class]}"
330
400
  end
331
401
 
332
402
  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
@@ -350,17 +420,18 @@ module Lutaml
350
420
 
351
421
  attr = attribute_for_rule(rule)
352
422
 
423
+ namespaced_names = rule.namespaced_names(options[:default_namespace])
424
+
353
425
  value = if rule.raw_mapping?
354
426
  doc.node.inner_xml
355
427
  elsif rule.content_mapping?
356
428
  doc[rule.content_key]
357
- elsif doc.key_exist?(rule.namespaced_name(options[:default_namespace]))
358
- doc.fetch(rule.namespaced_name(options[:default_namespace]))
429
+ elsif key = (namespaced_names & doc.keys).first
430
+ doc[key]
359
431
  else
360
432
  defaults_used << rule.to
361
433
  attr&.default || rule.to_value_for(instance)
362
434
  end
363
-
364
435
  value = normalize_xml_value(value, rule, attr, options)
365
436
  rule.deserialize(instance, value, attributes, self)
366
437
  end
@@ -379,13 +450,19 @@ module Lutaml
379
450
 
380
451
  attr = attribute_for_rule(rule)
381
452
 
382
- value = if doc.key?(rule.name) || doc.key?(rule.name.to_sym)
383
- doc[rule.name] || doc[rule.name.to_sym]
384
- else
385
- attr.default
386
- end
453
+ names = rule.multiple_mappings? ? rule.name : [rule.name]
387
454
 
388
- if rule.custom_methods[:from]
455
+ value = names.collect do |rule_name|
456
+ if doc.key?(rule_name.to_s)
457
+ doc[rule_name.to_s]
458
+ elsif doc.key?(rule_name.to_sym)
459
+ doc[rule_name.to_sym]
460
+ else
461
+ attr&.default
462
+ end
463
+ end.compact.first
464
+
465
+ if rule.using_custom_methods?
389
466
  if Utils.present?(value)
390
467
  value = new.send(rule.custom_methods[:from], instance, value)
391
468
  end
@@ -479,7 +556,7 @@ module Lutaml
479
556
 
480
557
  self.class.attributes.each do |name, attr|
481
558
  value = if attrs.key?(name) || attrs.key?(name.to_s)
482
- self.class.attr_value(attrs, name, attr)
559
+ attr_value(attrs, name, attr)
483
560
  else
484
561
  using_default_for(name)
485
562
  attr.default
@@ -490,7 +567,35 @@ module Lutaml
490
567
  value = []
491
568
  end
492
569
 
493
- instance_variable_set(:"@#{name}", self.class.ensure_utf8(value))
570
+ default = using_default?(name)
571
+ public_send(:"#{name}=", self.class.ensure_utf8(value))
572
+ using_default_for(name) if default
573
+ end
574
+ end
575
+
576
+ def attr_value(attrs, name, attr_rule)
577
+ value = if attrs.key?(name.to_sym)
578
+ attrs[name.to_sym]
579
+ elsif attrs.key?(name.to_s)
580
+ attrs[name.to_s]
581
+ else
582
+ attr_rule.default
583
+ end
584
+
585
+ if attr_rule.collection? || value.is_a?(Array)
586
+ (value || []).map do |v|
587
+ if v.is_a?(Hash)
588
+ attr_rule.type.new(v)
589
+ else
590
+ # TODO: This code is problematic because Type.cast does not know
591
+ # about all the types.
592
+ Lutaml::Model::Type.cast(v, attr_rule.type)
593
+ end
594
+ end
595
+ else
596
+ # TODO: This code is problematic because Type.cast does not know
597
+ # about all the types.
598
+ Lutaml::Model::Type.cast(value, attr_rule.type)
494
599
  end
495
600
  end
496
601
 
@@ -539,6 +644,14 @@ module Lutaml
539
644
  hash[key.to_sym] || hash[key.to_s]
540
645
  end
541
646
 
647
+ def pretty_print_instance_variables
648
+ (instance_variables - %i[@using_default]).sort
649
+ end
650
+
651
+ def to_yaml_hash
652
+ self.class.as_yaml(self)
653
+ end
654
+
542
655
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
543
656
  define_method(:"to_#{format}") do |options = {}|
544
657
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
@@ -36,6 +36,11 @@ module Lutaml
36
36
  raise TypeNotEnabledError.new("Decimal", value)
37
37
  end
38
38
  end
39
+
40
+ # Override to avoid serializing ruby object in YAML
41
+ def to_yaml
42
+ value&.to_s("F")
43
+ end
39
44
  end
40
45
  end
41
46
  end
@@ -34,10 +34,10 @@ module Lutaml
34
34
  # value&.iso8601
35
35
  # end
36
36
 
37
- # # YAML timestamp format (native)
38
- # def to_yaml
39
- # value
40
- # end
37
+ # YAML timestamp format (native)
38
+ def to_yaml
39
+ value&.iso8601
40
+ end
41
41
 
42
42
  # # TOML time format (HH:MM:SS.mmm)
43
43
  # def to_toml
@@ -39,7 +39,7 @@ module Lutaml
39
39
  end
40
40
 
41
41
  def blank?(value)
42
- value.respond_to?(:empty?) ? value.empty? : !value
42
+ value.respond_to?(:empty?) ? value.empty? : value.nil?
43
43
  end
44
44
 
45
45
  def add_method_if_not_defined(klass, method_name, &block)
@@ -4,9 +4,13 @@ module Lutaml
4
4
  def validate
5
5
  errors = []
6
6
  self.class.attributes.each do |name, attr|
7
- value = instance_variable_get(:"@#{name}")
7
+ value = public_send(:"#{name}")
8
8
  begin
9
- attr.validate_value!(value)
9
+ if value.respond_to?(:validate!)
10
+ value.validate!
11
+ else
12
+ attr.validate_value!(value)
13
+ end
10
14
  rescue Lutaml::Model::InvalidValueError,
11
15
  Lutaml::Model::CollectionCountOutOfRangeError,
12
16
  PatternNotMatchedError => e
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.30"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -39,6 +39,7 @@ module Lutaml
39
39
  )
40
40
  add_namespace_prefix(prefix)
41
41
 
42
+ element_name = element_name.first if element_name.is_a?(Array)
42
43
  element_name = "#{element_name}_" if respond_to?(element_name)
43
44
 
44
45
  if block_given?
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ module Builder
7
+ class Oga
8
+ def self.build(options = {}, &block)
9
+ if block_given?
10
+ XmlAdapter::Builder::Oga.new(options, &block)
11
+ else
12
+ XmlAdapter::Builder::Oga.new(options)
13
+ end
14
+ end
15
+
16
+ attr_reader :document, :current_node, :options
17
+
18
+ def initialize(options = {})
19
+ @document = XmlAdapter::Oga::Document.new
20
+ @current_node = @document
21
+ @options = options
22
+ yield(self) if block_given?
23
+ end
24
+
25
+ def create_element(name, attributes = {}, &block)
26
+ if @current_namespace && !name.start_with?("#{@current_namespace}:")
27
+ name = "#{@current_namespace}:#{name}"
28
+ end
29
+
30
+ if block_given?
31
+ element(name, attributes, &block)
32
+ else
33
+ element(name, attributes)
34
+ end
35
+ end
36
+
37
+ def element(name, attributes = {})
38
+ oga_element = ::Oga::XML::Element.new(name: name)
39
+ if block_given?
40
+ element_attributes(oga_element, attributes)
41
+ @current_node.children << oga_element
42
+ # Save previous node to reset the pointer for the rest of the iteration
43
+ previous_node = @current_node
44
+ # Set current node to new element as pointer for the block
45
+ @current_node = oga_element
46
+ yield(self)
47
+ # Reset the pointer for the rest of the iterations
48
+ @current_node = previous_node
49
+ end
50
+ oga_element
51
+ end
52
+
53
+ def add_element(oga_element, child)
54
+ if child.is_a?(String)
55
+ current_element = oga_element.is_a?(XmlAdapter::Oga::Document) ? current_node : oga_element
56
+ add_xml_fragment(current_element, child)
57
+ elsif oga_element.is_a?(XmlAdapter::Oga::Document)
58
+ oga_element.children.last.children << child
59
+ else
60
+ oga_element.children << child
61
+ end
62
+ end
63
+
64
+ def add_attribute(element, name, value)
65
+ attribute = ::Oga::XML::Attribute.new(
66
+ name: name,
67
+ value: value.to_s,
68
+ )
69
+ if element.is_a?(XmlAdapter::Oga::Document)
70
+ element.children.last.attributes << attribute
71
+ else
72
+ element.attributes << attribute
73
+ end
74
+ end
75
+
76
+ def create_and_add_element(
77
+ element_name,
78
+ prefix: (prefix_unset = true
79
+ nil),
80
+ attributes: {},
81
+ &block
82
+ )
83
+ @current_namespace = nil if prefix.nil? && !prefix_unset
84
+ prefixed_name = if prefix
85
+ "#{prefix}:#{element_name}"
86
+ elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
87
+ "#{@current_namespace}:#{element_name}"
88
+ else
89
+ element_name
90
+ end
91
+
92
+ if block_given?
93
+ element(prefixed_name, attributes, &block)
94
+ else
95
+ element(prefixed_name, attributes)
96
+ end
97
+ end
98
+
99
+ def <<(text)
100
+ @current_node.text(text)
101
+ end
102
+
103
+ def add_xml_fragment(element, content)
104
+ fragment = "<fragment>#{content}</fragment>"
105
+ parsed_fragment = ::Oga.parse_xml(fragment)
106
+ parsed_children = parsed_fragment.children.first.children
107
+ if element.is_a?(XmlAdapter::Oga::Document)
108
+ element.children.last.children += parsed_children
109
+ else
110
+ element.children += parsed_children
111
+ end
112
+ end
113
+
114
+ def add_text(element, text, cdata: false)
115
+ return add_cdata(element, text) if cdata
116
+
117
+ oga_text = ::Oga::XML::Text.new(text: text.to_s)
118
+ if element.is_a?(XmlAdapter::Oga::Document)
119
+ children = element.children
120
+ children.empty? ? children << oga_text : children.last.children << oga_text
121
+ else
122
+ element.children << oga_text
123
+ end
124
+ end
125
+
126
+ def add_cdata(element, value)
127
+ oga_cdata = ::Oga::XML::CData.new(text: value.to_s)
128
+ if element.is_a?(XmlAdapter::Oga::Document)
129
+ element.children.last.children << oga_cdata
130
+ else
131
+ element.children << oga_cdata
132
+ end
133
+ end
134
+
135
+ def add_namespace_prefix(prefix)
136
+ @current_namespace = prefix
137
+ self
138
+ end
139
+
140
+ def parent
141
+ @document
142
+ end
143
+
144
+ def text(value = nil)
145
+ return @current_node.inner_text if value.nil?
146
+
147
+ str = value.is_a?(Array) ? value.join : value
148
+ @current_node.children << ::Oga::XML::Text.new(text: str)
149
+ end
150
+
151
+ def method_missing(method_name, *args)
152
+ if block_given?
153
+ @current_node.public_send(method_name, *args) do
154
+ yield(self)
155
+ end
156
+ else
157
+ @current_node.public_send(method_name, *args)
158
+ end
159
+ end
160
+
161
+ def respond_to_missing?(method_name, include_private = false)
162
+ @current_node.respond_to?(method_name) || super
163
+ end
164
+
165
+ private
166
+
167
+ def element_attributes(oga_element, attributes)
168
+ oga_element.attributes = attributes.map do |name, value|
169
+ ::Oga::XML::Attribute.new(
170
+ name: name,
171
+ value: value,
172
+ element: oga_element,
173
+ )
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -43,6 +43,7 @@ module Lutaml
43
43
  end
44
44
 
45
45
  def create_and_add_element(element_name, prefix: nil, attributes: {})
46
+ element_name = element_name.first if element_name.is_a?(Array)
46
47
  prefixed_name = if prefix
47
48
  "#{prefix}:#{element_name}"
48
49
  elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ module Oga
7
+ class Document < ::Oga::XML::Document
8
+ def initialize(options = {})
9
+ super
10
+ end
11
+
12
+ def text(value = nil)
13
+ children << ::Oga::XML::Text.new(text: value)
14
+ self
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end