lutaml-model 0.3.25 → 0.3.26
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/.rubocop_todo.yml +1 -1
- data/README.adoc +122 -2
- data/lib/lutaml/model/attribute.rb +34 -19
- data/lib/lutaml/model/error/pattern_not_matched_error.rb +17 -0
- data/lib/lutaml/model/error.rb +1 -0
- data/lib/lutaml/model/mapping_hash.rb +8 -0
- data/lib/lutaml/model/serialize.rb +4 -4
- data/lib/lutaml/model/validation.rb +2 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +7 -1
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +7 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +3 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -7
- data/lib/lutaml/model/xml_adapter/xml_document.rb +6 -6
- data/lib/lutaml/model/xml_mapping.rb +6 -2
- data/lib/lutaml/model/xml_mapping_rule.rb +7 -1
- data/spec/lutaml/model/attribute_spec.rb +27 -0
- data/spec/lutaml/model/cdata_spec.rb +520 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +9 -4
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1dc709174fab4f4df2214f08b536e0b1aded39f0c9f48a18eb5a4a53da3fc4b6
|
4
|
+
data.tar.gz: 9eea83160b8210f84d4b2f9baf2ab577ca91d2a737eaf98b175d52385e6fb5d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e75326adeb9b8804f1bcc99104707edc3656e870d16c8af22e07e32ead9f4d78cd87eb372ebb834f2a5d942d58819e0fd3800e23a8099cf951360765223cd25
|
7
|
+
data.tar.gz: ecc145fb2d9250cf855243c7b9cf468919f2530eed03039bd6c7a521fa534327a1a67c5348237b3d5959e20fa0fc2b2cadc87e57139f451610cd2d7d695f251b
|
data/.rubocop_todo.yml
CHANGED
data/README.adoc
CHANGED
@@ -543,8 +543,15 @@ end
|
|
543
543
|
====
|
544
544
|
|
545
545
|
|
546
|
+
=== Attribute value validation
|
547
|
+
|
548
|
+
==== General
|
549
|
+
|
550
|
+
There are several mechanisms to validate attribute values in Lutaml::Model.
|
551
|
+
|
552
|
+
|
546
553
|
[[attribute-enumeration]]
|
547
|
-
|
554
|
+
==== Values of an enumeration
|
548
555
|
|
549
556
|
An attribute can be defined as an enumeration by using the `values` directive.
|
550
557
|
|
@@ -641,6 +648,44 @@ acceptance of the newly updated component.
|
|
641
648
|
====
|
642
649
|
|
643
650
|
|
651
|
+
==== String values restricted to patterns
|
652
|
+
|
653
|
+
An attribute that accepts a string value accepts value validation using regular
|
654
|
+
expressions.
|
655
|
+
|
656
|
+
Syntax:
|
657
|
+
|
658
|
+
[source,ruby]
|
659
|
+
----
|
660
|
+
attribute :name_of_attribute, :string, pattern: /regex/
|
661
|
+
----
|
662
|
+
|
663
|
+
.Using the `pattern` option to restrict the value of an attribute
|
664
|
+
[example]
|
665
|
+
====
|
666
|
+
In this example, the `color` attribute takes hex color values such as `#ccddee`.
|
667
|
+
|
668
|
+
A regular expression can be used to validate values assigned to the attribute.
|
669
|
+
In this case, it is `/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/`.
|
670
|
+
|
671
|
+
[source,ruby]
|
672
|
+
----
|
673
|
+
class Glaze < Lutaml::Model::Serializable
|
674
|
+
attribute :color, :string, pattern: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/
|
675
|
+
end
|
676
|
+
----
|
677
|
+
|
678
|
+
[source,ruby]
|
679
|
+
----
|
680
|
+
> Glaze.new(color: '#ff0000').color
|
681
|
+
> # "#ff0000"
|
682
|
+
> Glaze.new(color: '#ff000').color
|
683
|
+
> # Lutaml::Model::InvalidValueError: Invalid value for attribute 'color'
|
684
|
+
----
|
685
|
+
====
|
686
|
+
|
687
|
+
|
688
|
+
|
644
689
|
=== Attribute value default and rendering defaults
|
645
690
|
|
646
691
|
Specify default values for attributes using the `default` option.
|
@@ -771,7 +816,8 @@ class Person < Lutaml::Model::Serializable
|
|
771
816
|
end
|
772
817
|
----
|
773
818
|
|
774
|
-
For the following
|
819
|
+
For the following XML snippet:
|
820
|
+
|
775
821
|
[source,xml]
|
776
822
|
----
|
777
823
|
<Person>
|
@@ -1144,6 +1190,80 @@ end
|
|
1144
1190
|
====
|
1145
1191
|
|
1146
1192
|
|
1193
|
+
==== CDATA nodes
|
1194
|
+
|
1195
|
+
CDATA is an XML feature that allows the inclusion of text that may contain
|
1196
|
+
characters that are unescaped in XML.
|
1197
|
+
|
1198
|
+
While CDATA is not preferred in XML, it is sometimes necessary to handle CDATA
|
1199
|
+
nodes for both input and output.
|
1200
|
+
|
1201
|
+
NOTE: The W3C XML Recommendation explicitly encourages escaping characters over
|
1202
|
+
usage of CDATA.
|
1203
|
+
|
1204
|
+
Lutaml::Model supports the handling of CDATA nodes in XML in the following
|
1205
|
+
behavior:
|
1206
|
+
|
1207
|
+
. When an attribute contains a CDATA node with no text:
|
1208
|
+
** On reading: The node (CDATA or text) is read as its value.
|
1209
|
+
** On writing: The value is written as its native type.
|
1210
|
+
|
1211
|
+
. When an XML mapping sets `cdata: true` on `map_element` or `map_content`:
|
1212
|
+
** On reading: The node (CDATA or text) is read as its value.
|
1213
|
+
** On writing: The value is written as a CDATA node.
|
1214
|
+
|
1215
|
+
. When an XML mapping sets `cdata: false` on `map_element` or `map_content`:
|
1216
|
+
** On reading: The node (CDATA or text) is read as its value.
|
1217
|
+
** On writing: The value is written as a text node (string).
|
1218
|
+
|
1219
|
+
|
1220
|
+
Syntax:
|
1221
|
+
|
1222
|
+
[source,ruby]
|
1223
|
+
----
|
1224
|
+
xml do
|
1225
|
+
map_content to: :name_of_attribute, cdata: (true | false)
|
1226
|
+
map_element :name, to: :name, cdata: (true | false)
|
1227
|
+
end
|
1228
|
+
----
|
1229
|
+
|
1230
|
+
.Using `cdata` to map CDATA content
|
1231
|
+
[example]
|
1232
|
+
====
|
1233
|
+
The following class will parse the XML snippet below:
|
1234
|
+
|
1235
|
+
[source,ruby]
|
1236
|
+
----
|
1237
|
+
class Example < Lutaml::Model::Serializable
|
1238
|
+
attribute :name, :string
|
1239
|
+
attribute :description, :string
|
1240
|
+
attribute :title, :string
|
1241
|
+
attribute :note, :string
|
1242
|
+
|
1243
|
+
xml do
|
1244
|
+
root 'example'
|
1245
|
+
map_element :name, to: :name, cdata: true
|
1246
|
+
map_content to: :description, cdata: true
|
1247
|
+
map_element :title, to: :title, cdata: false
|
1248
|
+
map_element :note, to: :note, cdata: false
|
1249
|
+
end
|
1250
|
+
end
|
1251
|
+
----
|
1252
|
+
|
1253
|
+
[source,xml]
|
1254
|
+
----
|
1255
|
+
<example><name><![CDATA[John]]></name><![CDATA[here is the description]]><title><![CDATA[Lutaml]]></title><note>Careful</note></example>
|
1256
|
+
----
|
1257
|
+
|
1258
|
+
[source,ruby]
|
1259
|
+
----
|
1260
|
+
> Example.from_xml(xml)
|
1261
|
+
> #<Example:0x0000000104ac7240 @name="John" @description="here is the description" @title="Lutaml" @note="Careful">
|
1262
|
+
> Example.new(name: "John", description: "here is the description", title: "Lutaml", note: "Careful").to_xml
|
1263
|
+
> #<example><name><![CDATA[John]]></name><![CDATA[here is the description]]><title>Lutaml</title><note>Careful</note></example>
|
1264
|
+
----
|
1265
|
+
====
|
1266
|
+
|
1147
1267
|
|
1148
1268
|
==== Example for mapping
|
1149
1269
|
|
@@ -9,6 +9,7 @@ module Lutaml
|
|
9
9
|
delegate
|
10
10
|
collection
|
11
11
|
values
|
12
|
+
pattern
|
12
13
|
].freeze
|
13
14
|
|
14
15
|
def initialize(name, type, options = {})
|
@@ -87,23 +88,12 @@ module Lutaml
|
|
87
88
|
cast_value(value)
|
88
89
|
end
|
89
90
|
|
90
|
-
def
|
91
|
-
|
91
|
+
def pattern
|
92
|
+
options[:pattern]
|
92
93
|
end
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
# Currently there are 2 validations
|
97
|
-
# 1. Value should be from the values list if they are defined
|
98
|
-
# e.g values: ["foo", "bar"] is set then any other value for this
|
99
|
-
# attribute will raise `Lutaml::Model::InvalidValueError`
|
100
|
-
#
|
101
|
-
# 2. Value count should be between the collection range if defined
|
102
|
-
# e.g if collection: 0..5 is set then the value greater then 5
|
103
|
-
# will raise `Lutaml::Model::CollectionCountOutOfRangeError`
|
104
|
-
def validate_value!(value)
|
105
|
-
valid_value!(value)
|
106
|
-
valid_collection!(value)
|
95
|
+
def enum_values
|
96
|
+
@options.key?(:values) ? @options[:values] : []
|
107
97
|
end
|
108
98
|
|
109
99
|
def valid_value!(value)
|
@@ -123,14 +113,32 @@ module Lutaml
|
|
123
113
|
options[:values].include?(value)
|
124
114
|
end
|
125
115
|
|
126
|
-
def
|
127
|
-
|
128
|
-
return true
|
116
|
+
def valid_pattern!(value)
|
117
|
+
return true unless type == Lutaml::Model::Type::String
|
118
|
+
return true unless pattern
|
119
|
+
|
120
|
+
unless pattern.match?(value)
|
121
|
+
raise Lutaml::Model::PatternNotMatchedError.new(name, pattern, value)
|
122
|
+
end
|
129
123
|
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
# Check if the value to be assigned is valid for the attribute
|
128
|
+
#
|
129
|
+
# Currently there are 2 validations
|
130
|
+
# 1. Value should be from the values list if they are defined
|
131
|
+
# e.g values: ["foo", "bar"] is set then any other value for this
|
132
|
+
# attribute will raise `Lutaml::Model::InvalidValueError`
|
133
|
+
#
|
134
|
+
# 2. Value count should be between the collection range if defined
|
135
|
+
# e.g if collection: 0..5 is set then the value greater then 5
|
136
|
+
# will raise `Lutaml::Model::CollectionCountOutOfRangeError`
|
137
|
+
def validate_value!(value)
|
130
138
|
# Use the default value if the value is nil
|
131
139
|
value = default if value.nil?
|
132
140
|
|
133
|
-
valid_value!(value) && valid_collection!(value)
|
141
|
+
valid_value!(value) && valid_collection!(value) && valid_pattern!(value)
|
134
142
|
end
|
135
143
|
|
136
144
|
def validate_collection_range
|
@@ -229,6 +237,13 @@ module Lutaml
|
|
229
237
|
raise StandardError,
|
230
238
|
"Invalid options given for `#{name}` #{invalid_opts}"
|
231
239
|
end
|
240
|
+
|
241
|
+
if options.key?(:pattern) && type != Lutaml::Model::Type::String
|
242
|
+
raise StandardError,
|
243
|
+
"Invalid option `pattern` given for `#{name}`, `pattern` is only allowed for :string type"
|
244
|
+
end
|
245
|
+
|
246
|
+
true
|
232
247
|
end
|
233
248
|
|
234
249
|
def validate_type!(type)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class PatternNotMatchedError < Error
|
4
|
+
def initialize(attr_name, pattern, value)
|
5
|
+
@attr_name = attr_name
|
6
|
+
@pattern = pattern
|
7
|
+
@value = value
|
8
|
+
|
9
|
+
super()
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{@attr_name}: \"#{@value}\" does not match #{@pattern.inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/lutaml/model/error.rb
CHANGED
@@ -7,6 +7,7 @@ end
|
|
7
7
|
|
8
8
|
require_relative "error/invalid_value_error"
|
9
9
|
require_relative "error/incorrect_mapping_argument_error"
|
10
|
+
require_relative "error/pattern_not_matched_error"
|
10
11
|
require_relative "error/unknown_adapter_type_error"
|
11
12
|
require_relative "error/collection_count_out_of_range_error"
|
12
13
|
require_relative "error/validation_error"
|
@@ -343,7 +343,7 @@ module Lutaml
|
|
343
343
|
value = if rule.raw_mapping?
|
344
344
|
doc.node.inner_xml
|
345
345
|
elsif rule.content_mapping?
|
346
|
-
doc[
|
346
|
+
doc[rule.content_key]
|
347
347
|
elsif doc.key_exist?(rule.namespaced_name(options[:default_namespace]))
|
348
348
|
doc.fetch(rule.namespaced_name(options[:default_namespace]))
|
349
349
|
else
|
@@ -399,12 +399,12 @@ module Lutaml
|
|
399
399
|
|
400
400
|
value = if value.is_a?(Array)
|
401
401
|
value.map do |v|
|
402
|
-
text_hash?(attr, v) ? v
|
402
|
+
text_hash?(attr, v) ? v.text : v
|
403
403
|
end
|
404
404
|
elsif attr&.raw? && value
|
405
405
|
value.node.children.map(&:to_xml).join
|
406
406
|
elsif text_hash?(attr, value)
|
407
|
-
value
|
407
|
+
value.text
|
408
408
|
else
|
409
409
|
value
|
410
410
|
end
|
@@ -428,7 +428,7 @@ module Lutaml
|
|
428
428
|
|
429
429
|
def text_hash?(attr, value)
|
430
430
|
return false unless value.is_a?(Hash)
|
431
|
-
return value.
|
431
|
+
return value.one? && value.text? unless attr
|
432
432
|
|
433
433
|
!(attr.type <= Serialize) && attr.type != Lutaml::Model::Type::Hash
|
434
434
|
end
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -51,7 +51,9 @@ module Lutaml
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
def add_text(element, text)
|
54
|
+
def add_text(element, text, cdata: false)
|
55
|
+
return add_cdata(element, text) if cdata
|
56
|
+
|
55
57
|
if element.is_a?(self.class)
|
56
58
|
element = element.xml.parent
|
57
59
|
end
|
@@ -60,6 +62,10 @@ module Lutaml
|
|
60
62
|
element.add_child(text_node)
|
61
63
|
end
|
62
64
|
|
65
|
+
def add_cdata(element, value)
|
66
|
+
element.cdata(value)
|
67
|
+
end
|
68
|
+
|
63
69
|
def add_namespace_prefix(prefix)
|
64
70
|
xml[prefix] if prefix
|
65
71
|
|
@@ -66,10 +66,16 @@ module Lutaml
|
|
66
66
|
xml.text(text)
|
67
67
|
end
|
68
68
|
|
69
|
-
def add_text(element, text)
|
69
|
+
def add_text(element, text, cdata: false)
|
70
|
+
return element.cdata(text) if cdata
|
71
|
+
|
70
72
|
element << text
|
71
73
|
end
|
72
74
|
|
75
|
+
def add_cdata(element, value)
|
76
|
+
element.cdata(value)
|
77
|
+
end
|
78
|
+
|
73
79
|
# Add XML namespace to document
|
74
80
|
#
|
75
81
|
# Ox doesn't support XML namespaces so we only save the
|
@@ -74,10 +74,12 @@ module Lutaml
|
|
74
74
|
value = attribute_value_for(element, element_rule)
|
75
75
|
|
76
76
|
if element_rule == xml_mapping.content_mapping
|
77
|
+
next if element_rule.cdata && name == "text"
|
78
|
+
|
77
79
|
text = xml_mapping.content_mapping.serialize(element)
|
78
80
|
text = text[curr_index] if text.is_a?(Array)
|
79
81
|
|
80
|
-
next prefixed_xml.
|
82
|
+
next prefixed_xml.add_text(xml, text, cdata: element_rule.cdata) if element.mixed?
|
81
83
|
|
82
84
|
content << text
|
83
85
|
elsif !value.nil? || element_rule.render_nil?
|
@@ -56,15 +56,15 @@ module Lutaml
|
|
56
56
|
mapper_class: mapper_class)
|
57
57
|
value = attribute_value_for(element, element_rule)
|
58
58
|
|
59
|
+
next if element_rule == xml_mapping.content_mapping && element_rule.cdata && name == "text"
|
60
|
+
|
59
61
|
if element_rule == xml_mapping.content_mapping
|
60
62
|
text = element.send(xml_mapping.content_mapping.to)
|
61
63
|
text = text[curr_index] if text.is_a?(Array)
|
62
64
|
|
63
|
-
if element.mixed?
|
64
|
-
|
65
|
-
|
66
|
-
content << text
|
67
|
-
end
|
65
|
+
next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
|
66
|
+
|
67
|
+
content << text
|
68
68
|
elsif !value.nil? || element_rule.render_nil?
|
69
69
|
value = value[curr_index] if attribute_def.collection?
|
70
70
|
|
@@ -88,10 +88,13 @@ module Lutaml
|
|
88
88
|
|
89
89
|
class OxElement < XmlElement
|
90
90
|
def initialize(node, root_node: nil)
|
91
|
-
|
91
|
+
case node
|
92
|
+
when String
|
92
93
|
super("text", {}, [], node, parent_document: root_node)
|
93
|
-
|
94
|
+
when Ox::Comment
|
94
95
|
super("comment", {}, [], node.value, parent_document: root_node)
|
96
|
+
when Ox::CData
|
97
|
+
super("#cdata-section", {}, [], node.value, parent_document: root_node)
|
95
98
|
else
|
96
99
|
namespace_attributes(node.attributes).each do |(name, value)|
|
97
100
|
if root_node
|
@@ -152,16 +152,16 @@ module Lutaml
|
|
152
152
|
)
|
153
153
|
elsif rule.prefix_set?
|
154
154
|
xml.create_and_add_element(rule.name, prefix: prefix) do
|
155
|
-
add_value(xml, value, attribute)
|
155
|
+
add_value(xml, value, attribute, cdata: rule.cdata)
|
156
156
|
end
|
157
157
|
else
|
158
158
|
xml.create_and_add_element(rule.name) do
|
159
|
-
add_value(xml, value, attribute)
|
159
|
+
add_value(xml, value, attribute, cdata: rule.cdata)
|
160
160
|
end
|
161
161
|
end
|
162
162
|
end
|
163
163
|
|
164
|
-
def add_value(xml, value, attribute)
|
164
|
+
def add_value(xml, value, attribute, cdata: false)
|
165
165
|
if !value.nil?
|
166
166
|
serialized_value = attribute.type.serialize(value)
|
167
167
|
|
@@ -172,7 +172,7 @@ module Lutaml
|
|
172
172
|
end
|
173
173
|
end
|
174
174
|
else
|
175
|
-
xml.add_text(xml, serialized_value)
|
175
|
+
xml.add_text(xml, serialized_value, cdata: cdata)
|
176
176
|
end
|
177
177
|
end
|
178
178
|
end
|
@@ -244,7 +244,7 @@ module Lutaml
|
|
244
244
|
value = attribute_value_for(element, rule)
|
245
245
|
return unless render_element?(rule, element, value)
|
246
246
|
|
247
|
-
xml.add_text(xml, value)
|
247
|
+
xml.add_text(xml, value, cdata: rule.cdata)
|
248
248
|
end
|
249
249
|
|
250
250
|
def process_content_mapping(element, content_rule, xml)
|
@@ -261,7 +261,7 @@ module Lutaml
|
|
261
261
|
text = content_rule.serialize(element)
|
262
262
|
text = text.join if text.is_a?(Array)
|
263
263
|
|
264
|
-
xml.add_text(xml, text)
|
264
|
+
xml.add_text(xml, text, cdata: content_rule.cdata)
|
265
265
|
end
|
266
266
|
end
|
267
267
|
|
@@ -47,6 +47,7 @@ module Lutaml
|
|
47
47
|
render_default: false,
|
48
48
|
with: {},
|
49
49
|
delegate: nil,
|
50
|
+
cdata: false,
|
50
51
|
namespace: (namespace_set = false
|
51
52
|
nil),
|
52
53
|
prefix: (prefix_set = false
|
@@ -61,6 +62,7 @@ module Lutaml
|
|
61
62
|
render_default: render_default,
|
62
63
|
with: with,
|
63
64
|
delegate: delegate,
|
65
|
+
cdata: cdata,
|
64
66
|
namespace: namespace,
|
65
67
|
default_namespace: namespace_uri,
|
66
68
|
prefix: prefix,
|
@@ -109,7 +111,8 @@ module Lutaml
|
|
109
111
|
render_default: false,
|
110
112
|
with: {},
|
111
113
|
delegate: nil,
|
112
|
-
mixed: false
|
114
|
+
mixed: false,
|
115
|
+
cdata: false
|
113
116
|
)
|
114
117
|
validate!("content", to, with)
|
115
118
|
|
@@ -121,6 +124,7 @@ module Lutaml
|
|
121
124
|
with: with,
|
122
125
|
delegate: delegate,
|
123
126
|
mixed_content: mixed,
|
127
|
+
cdata: cdata,
|
124
128
|
)
|
125
129
|
end
|
126
130
|
|
@@ -211,7 +215,7 @@ module Lutaml
|
|
211
215
|
end
|
212
216
|
|
213
217
|
def find_by_name(name)
|
214
|
-
if name.to_s
|
218
|
+
if ["text", "#cdata-section"].include?(name.to_s)
|
215
219
|
content_mapping
|
216
220
|
else
|
217
221
|
mappings.detect do |rule|
|
@@ -3,7 +3,7 @@ require_relative "mapping_rule"
|
|
3
3
|
module Lutaml
|
4
4
|
module Model
|
5
5
|
class XmlMappingRule < MappingRule
|
6
|
-
attr_reader :namespace, :prefix, :mixed_content, :default_namespace
|
6
|
+
attr_reader :namespace, :prefix, :mixed_content, :default_namespace, :cdata
|
7
7
|
|
8
8
|
def initialize(
|
9
9
|
name,
|
@@ -15,6 +15,7 @@ module Lutaml
|
|
15
15
|
namespace: nil,
|
16
16
|
prefix: nil,
|
17
17
|
mixed_content: false,
|
18
|
+
cdata: false,
|
18
19
|
namespace_set: false,
|
19
20
|
prefix_set: false,
|
20
21
|
attribute: false,
|
@@ -38,6 +39,7 @@ module Lutaml
|
|
38
39
|
end
|
39
40
|
@prefix = prefix
|
40
41
|
@mixed_content = mixed_content
|
42
|
+
@cdata = cdata
|
41
43
|
|
42
44
|
@default_namespace = default_namespace
|
43
45
|
|
@@ -61,6 +63,10 @@ module Lutaml
|
|
61
63
|
name == "__raw_mapping"
|
62
64
|
end
|
63
65
|
|
66
|
+
def content_key
|
67
|
+
cdata ? "#cdata-section" : "text"
|
68
|
+
end
|
69
|
+
|
64
70
|
def mixed_content?
|
65
71
|
!!@mixed_content
|
66
72
|
end
|
@@ -33,6 +33,33 @@ RSpec.describe Lutaml::Model::Attribute do
|
|
33
33
|
.to("avatar.png")
|
34
34
|
end
|
35
35
|
|
36
|
+
describe "#validate_options!" do
|
37
|
+
let(:validate_options) { name_attr.method(:validate_options!) }
|
38
|
+
|
39
|
+
Lutaml::Model::Attribute::ALLOWED_OPTIONS.each do |option|
|
40
|
+
it "return true if option is `#{option}`" do
|
41
|
+
expect(validate_options.call({ option => "value" })).to be(true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raise exception if option is not allowed" do
|
46
|
+
expect do
|
47
|
+
validate_options.call({ foo: "bar" })
|
48
|
+
end.to raise_error(StandardError, "Invalid options given for `name` [:foo]")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "raise exception if pattern is given with non string type" do
|
52
|
+
age_attr = described_class.new("age", :integer)
|
53
|
+
|
54
|
+
expect do
|
55
|
+
age_attr.send(:validate_options!, { pattern: /[A-Za-z ]/ })
|
56
|
+
end.to raise_error(
|
57
|
+
StandardError,
|
58
|
+
"Invalid option `pattern` given for `age`, `pattern` is only allowed for :string type",
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
36
63
|
describe "#validate_type!" do
|
37
64
|
let(:validate_type) { name_attr.method(:validate_type!) }
|
38
65
|
|
@@ -0,0 +1,520 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model"
|
3
|
+
require "lutaml/model/xml_adapter/nokogiri_adapter"
|
4
|
+
require "lutaml/model/xml_adapter/ox_adapter"
|
5
|
+
|
6
|
+
module CDATA
|
7
|
+
class Beta < Lutaml::Model::Serializable
|
8
|
+
attribute :element1, :string
|
9
|
+
|
10
|
+
xml do
|
11
|
+
root "beta"
|
12
|
+
map_content to: :element1, cdata: true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Alpha < Lutaml::Model::Serializable
|
17
|
+
attribute :element1, :string
|
18
|
+
attribute :element2, :string
|
19
|
+
attribute :element3, :string
|
20
|
+
attribute :beta, Beta
|
21
|
+
|
22
|
+
xml do
|
23
|
+
root "alpha"
|
24
|
+
|
25
|
+
map_element "element1", to: :element1, cdata: false
|
26
|
+
map_element "element2", to: :element2, cdata: true
|
27
|
+
map_element "element3", to: :element3, cdata: false
|
28
|
+
map_element "beta", to: :beta
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Address < Lutaml::Model::Serializable
|
33
|
+
attribute :street, :string
|
34
|
+
attribute :city, :string
|
35
|
+
attribute :house, :string
|
36
|
+
attribute :address, Address
|
37
|
+
|
38
|
+
xml do
|
39
|
+
root "address"
|
40
|
+
map_element "street", to: :street
|
41
|
+
map_element "city", with: { from: :city_from_xml, to: :city_to_xml }, cdata: true
|
42
|
+
map_element "house", with: { from: :house_from_xml, to: :house_to_xml }, cdata: false
|
43
|
+
map_element "address", to: :address
|
44
|
+
end
|
45
|
+
|
46
|
+
def house_from_xml(model, node)
|
47
|
+
model.house = node
|
48
|
+
end
|
49
|
+
|
50
|
+
def house_to_xml(model, _parent, doc)
|
51
|
+
doc.create_and_add_element("house") do |element|
|
52
|
+
element.add_text(element, model.house, cdata: false)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def city_from_xml(model, node)
|
57
|
+
model.city = node
|
58
|
+
end
|
59
|
+
|
60
|
+
def city_to_xml(model, _parent, doc)
|
61
|
+
doc.create_and_add_element("city") do |element|
|
62
|
+
element.add_text(element, model.city, cdata: true)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class CustomModelChild
|
68
|
+
attr_accessor :street, :city
|
69
|
+
end
|
70
|
+
|
71
|
+
class CustomModelParent
|
72
|
+
attr_accessor :first_name, :middle_name, :last_name, :child_mapper
|
73
|
+
|
74
|
+
def name
|
75
|
+
"#{first_name} #{last_name}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class CustomModelChildMapper < Lutaml::Model::Serializable
|
80
|
+
model CustomModelChild
|
81
|
+
|
82
|
+
attribute :street, Lutaml::Model::Type::String
|
83
|
+
attribute :city, Lutaml::Model::Type::String
|
84
|
+
|
85
|
+
xml do
|
86
|
+
map_element :street, to: :street, cdata: true
|
87
|
+
map_element :city, to: :city, cdata: true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class CustomModelParentMapper < Lutaml::Model::Serializable
|
92
|
+
model CustomModelParent
|
93
|
+
|
94
|
+
attribute :first_name, Lutaml::Model::Type::String
|
95
|
+
attribute :middle_name, Lutaml::Model::Type::String
|
96
|
+
attribute :last_name, Lutaml::Model::Type::String
|
97
|
+
attribute :child_mapper, CustomModelChildMapper
|
98
|
+
|
99
|
+
xml do
|
100
|
+
root "CustomModelParent"
|
101
|
+
map_element :first_name, to: :first_name, cdata: true
|
102
|
+
map_element :middle_name, to: :middle_name, cdata: true
|
103
|
+
map_element :last_name, to: :last_name, cdata: false
|
104
|
+
map_element :CustomModelChild, with: { to: :child_to_xml, from: :child_from_xml }, cdata: true
|
105
|
+
end
|
106
|
+
|
107
|
+
def child_to_xml(model, _parent, doc)
|
108
|
+
doc.create_and_add_element("CustomModelChild") do |child_el|
|
109
|
+
child_el.create_and_add_element("street") do |street_el|
|
110
|
+
street_el.add_text(street_el, model.child_mapper.street, cdata: true)
|
111
|
+
end
|
112
|
+
child_el.create_and_add_element("city") do |city_el|
|
113
|
+
city_el.add_text(city_el, model.child_mapper.city, cdata: true)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def child_from_xml(model, value)
|
119
|
+
model.child_mapper ||= CustomModelChild.new
|
120
|
+
|
121
|
+
model.child_mapper.street = value["street"].text
|
122
|
+
model.child_mapper.city = value["city"].text
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class RootMixedContent < Lutaml::Model::Serializable
|
127
|
+
attribute :id, :string
|
128
|
+
attribute :bold, :string, collection: true
|
129
|
+
attribute :italic, :string, collection: true
|
130
|
+
attribute :underline, :string
|
131
|
+
attribute :content, :string
|
132
|
+
|
133
|
+
xml do
|
134
|
+
root "RootMixedContent", mixed: true
|
135
|
+
map_attribute :id, to: :id
|
136
|
+
map_element :bold, to: :bold, cdata: true
|
137
|
+
map_element :italic, to: :italic, cdata: true
|
138
|
+
map_element :underline, to: :underline, cdata: true
|
139
|
+
map_content to: :content, cdata: true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class RootMixedContentNested < Lutaml::Model::Serializable
|
144
|
+
attribute :id, :string
|
145
|
+
attribute :data, :string
|
146
|
+
attribute :content, RootMixedContent
|
147
|
+
attribute :sup, :string, collection: true
|
148
|
+
attribute :sub, :string, collection: true
|
149
|
+
|
150
|
+
xml do
|
151
|
+
root "RootMixedContentNested", mixed: true
|
152
|
+
map_content to: :data, cdata: true
|
153
|
+
map_attribute :id, to: :id
|
154
|
+
map_element :sup, to: :sup, cdata: true
|
155
|
+
map_element :sub, to: :sub, cdata: false
|
156
|
+
map_element "MixedContent", to: :content
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class DefaultValue < Lutaml::Model::Serializable
|
161
|
+
attribute :name, :string, default: -> { "Default Value" }
|
162
|
+
attribute :temperature, :integer, default: -> { 1050 }
|
163
|
+
attribute :opacity, :string, default: -> { "Opaque" }
|
164
|
+
attribute :content, :string, default: -> { " " }
|
165
|
+
|
166
|
+
xml do
|
167
|
+
root "DefaultValue"
|
168
|
+
map_element "name", to: :name, render_default: true, cdata: true
|
169
|
+
map_element "temperature", to: :temperature, render_default: true, cdata: true
|
170
|
+
map_element "opacity", to: :opacity, cdata: false, render_default: true
|
171
|
+
map_content to: :content, cdata: true, render_default: true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
RSpec.describe "CDATA" do
|
177
|
+
let(:parent_mapper) { CDATA::CustomModelParentMapper }
|
178
|
+
let(:child_mapper) { CDATA::CustomModelChildMapper }
|
179
|
+
let(:parent_model) { CDATA::CustomModelParent }
|
180
|
+
let(:child_model) { CDATA::CustomModelChild }
|
181
|
+
|
182
|
+
shared_examples "cdata behavior" do |adapter_class|
|
183
|
+
around do |example|
|
184
|
+
old_adapter = Lutaml::Model::Config.xml_adapter
|
185
|
+
Lutaml::Model::Config.xml_adapter = adapter_class
|
186
|
+
example.run
|
187
|
+
ensure
|
188
|
+
Lutaml::Model::Config.xml_adapter = old_adapter
|
189
|
+
end
|
190
|
+
|
191
|
+
context "with CDATA option" do
|
192
|
+
let(:xml) do
|
193
|
+
<<~XML.strip
|
194
|
+
<alpha>
|
195
|
+
<element1><![CDATA[foo]]></element1>
|
196
|
+
<element2><![CDATA[one]]></element2>
|
197
|
+
<element2><![CDATA[two]]></element2>
|
198
|
+
<element2><![CDATA[three]]></element2>
|
199
|
+
<element3>bar</element3>
|
200
|
+
<beta><![CDATA[child]]></beta>
|
201
|
+
</alpha>
|
202
|
+
XML
|
203
|
+
end
|
204
|
+
|
205
|
+
let(:expected_xml) do
|
206
|
+
<<~XML.strip
|
207
|
+
<alpha>
|
208
|
+
<element1>foo</element1>
|
209
|
+
<element2>
|
210
|
+
<![CDATA[one]]>
|
211
|
+
</element2>
|
212
|
+
<element2>
|
213
|
+
<![CDATA[two]]>
|
214
|
+
</element2>
|
215
|
+
<element2>
|
216
|
+
<![CDATA[three]]>
|
217
|
+
</element2>
|
218
|
+
<element3>bar</element3>
|
219
|
+
<beta>
|
220
|
+
<![CDATA[child]]>
|
221
|
+
</beta>
|
222
|
+
</alpha>
|
223
|
+
XML
|
224
|
+
end
|
225
|
+
|
226
|
+
it "maps xml to object" do
|
227
|
+
instance = CDATA::Alpha.from_xml(xml)
|
228
|
+
|
229
|
+
expect(instance.element1).to eq("foo")
|
230
|
+
expect(instance.element2).to eq(%w[one two three])
|
231
|
+
expect(instance.element3).to eq("bar")
|
232
|
+
expect(instance.beta.element1).to eq("child")
|
233
|
+
end
|
234
|
+
|
235
|
+
it "converts objects to xml" do
|
236
|
+
instance = CDATA::Alpha.new(
|
237
|
+
element1: "foo",
|
238
|
+
element2: %w[one two three],
|
239
|
+
element3: "bar",
|
240
|
+
beta: CDATA::Beta.new(element1: "child"),
|
241
|
+
)
|
242
|
+
|
243
|
+
expect(instance.to_xml).to be_equivalent_to(expected_xml)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "with custom methods" do
|
248
|
+
let(:xml) do
|
249
|
+
<<~XML
|
250
|
+
<address>
|
251
|
+
<street>A</street>
|
252
|
+
<city><![CDATA[B]]></city>
|
253
|
+
<house><![CDATA[H]]></house>
|
254
|
+
<address>
|
255
|
+
<street>C</street>
|
256
|
+
<city><![CDATA[D]]></city>
|
257
|
+
<house><![CDATA[G]]></house>
|
258
|
+
</address>
|
259
|
+
</address>
|
260
|
+
XML
|
261
|
+
end
|
262
|
+
|
263
|
+
let(:expected_xml) do
|
264
|
+
<<~XML
|
265
|
+
<address>
|
266
|
+
<street>A</street>
|
267
|
+
<city>
|
268
|
+
<![CDATA[B]]>
|
269
|
+
</city>
|
270
|
+
<house>H</house>
|
271
|
+
<address>
|
272
|
+
<street>C</street>
|
273
|
+
<city>
|
274
|
+
<![CDATA[D]]>
|
275
|
+
</city>
|
276
|
+
<house>G</house>
|
277
|
+
</address>
|
278
|
+
</address>
|
279
|
+
XML
|
280
|
+
end
|
281
|
+
|
282
|
+
it "round-trips XML" do
|
283
|
+
model = CDATA::Address.from_xml(xml)
|
284
|
+
expect(model.to_xml).to be_equivalent_to(expected_xml)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context "with custom models" do
|
289
|
+
let(:input_xml) do
|
290
|
+
<<~XML
|
291
|
+
<CustomModelParent>
|
292
|
+
<first_name><![CDATA[John]]></first_name>
|
293
|
+
<last_name><![CDATA[Doe]]></last_name>
|
294
|
+
<CustomModelChild>
|
295
|
+
<street><![CDATA[Oxford Street]]></street>
|
296
|
+
<city><![CDATA[London]]></city>
|
297
|
+
</CustomModelChild>
|
298
|
+
</CustomModelParent>
|
299
|
+
XML
|
300
|
+
end
|
301
|
+
|
302
|
+
let(:expected_nokogiri_xml) do
|
303
|
+
<<~XML
|
304
|
+
<CustomModelParent>
|
305
|
+
<first_name><![CDATA[John]]></first_name>
|
306
|
+
<last_name>Doe</last_name>
|
307
|
+
<CustomModelChild>
|
308
|
+
<street><![CDATA[Oxford Street]]></street>
|
309
|
+
<city><![CDATA[London]]></city>
|
310
|
+
</CustomModelChild>
|
311
|
+
</CustomModelParent>
|
312
|
+
XML
|
313
|
+
end
|
314
|
+
|
315
|
+
let(:expected_ox_xml) do
|
316
|
+
<<~XML
|
317
|
+
<CustomModelParent>
|
318
|
+
<first_name>
|
319
|
+
<![CDATA[John]]>
|
320
|
+
</first_name>
|
321
|
+
<last_name>Doe</last_name>
|
322
|
+
<CustomModelChild>
|
323
|
+
<street>
|
324
|
+
<![CDATA[Oxford Street]]>
|
325
|
+
</street>
|
326
|
+
<city>
|
327
|
+
<![CDATA[London]]>
|
328
|
+
</city>
|
329
|
+
</CustomModelChild>
|
330
|
+
</CustomModelParent>
|
331
|
+
XML
|
332
|
+
end
|
333
|
+
|
334
|
+
describe ".from_xml" do
|
335
|
+
it "maps XML content to custom model using custom methods" do
|
336
|
+
instance = parent_mapper.from_xml(input_xml)
|
337
|
+
|
338
|
+
expect(instance.class).to eq(parent_model)
|
339
|
+
expect(instance.first_name).to eq("John")
|
340
|
+
expect(instance.last_name).to eq("Doe")
|
341
|
+
expect(instance.name).to eq("John Doe")
|
342
|
+
|
343
|
+
expect(instance.child_mapper.class).to eq(child_model)
|
344
|
+
expect(instance.child_mapper.street).to eq("Oxford Street")
|
345
|
+
expect(instance.child_mapper.city).to eq("London")
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe ".to_xml" do
|
350
|
+
it "with correct model converts objects to xml using custom methods" do
|
351
|
+
instance = parent_mapper.from_xml(input_xml)
|
352
|
+
result_xml = parent_mapper.to_xml(instance)
|
353
|
+
|
354
|
+
expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
|
355
|
+
|
356
|
+
expect(result_xml.strip).to eq(expected_output.strip)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context "when mixed: true is set for nested content" do
|
362
|
+
let(:xml) do
|
363
|
+
<<~XML
|
364
|
+
<RootMixedContentNested id="outer123">
|
365
|
+
<![CDATA[The following text is about the Moon.]]>
|
366
|
+
<MixedContent id="inner456">
|
367
|
+
<![CDATA[The Earth's Moon rings like a ]]>
|
368
|
+
<bold><![CDATA[bell]]></bold>
|
369
|
+
<![CDATA[ when struck by meteroids. Distanced from the Earth by ]]>
|
370
|
+
<italic><![CDATA[384,400 km]]></italic>,
|
371
|
+
<![CDATA[ ,its surface is covered in ]]>
|
372
|
+
<underline><![CDATA[craters]]></underline>.
|
373
|
+
<![CDATA[ .Ain't that ]]>
|
374
|
+
<bold><![CDATA[cool]]></bold>
|
375
|
+
<![CDATA[ ? ]]>
|
376
|
+
</MixedContent>
|
377
|
+
<sup><![CDATA[1]]></sup>: <![CDATA[The Moon is not a planet.]]>
|
378
|
+
<sup><![CDATA[2]]></sup>: <![CDATA[The Moon's atmosphere is mainly composed of helium in the form of He]]><sub><![CDATA[2]]></sub>.
|
379
|
+
</RootMixedContentNested>
|
380
|
+
XML
|
381
|
+
end
|
382
|
+
|
383
|
+
expected_xml = "<RootMixedContentNested id=\"outer123\"><![CDATA[The following text is about the Moon.]]><MixedContent id=\"inner456\"><![CDATA[The Earth's Moon rings like a ]]><bold><![CDATA[bell]]></bold><![CDATA[ when struck by meteroids. Distanced from the Earth by ]]><italic><![CDATA[384,400 km]]></italic><![CDATA[ ,its surface is covered in ]]><underline><![CDATA[craters]]></underline><![CDATA[ .Ain't that ]]><bold><![CDATA[cool]]></bold><![CDATA[ ? ]]></MixedContent><sup><![CDATA[1]]></sup><![CDATA[The Moon is not a planet.]]><sup><![CDATA[2]]></sup><![CDATA[The Moon's atmosphere is mainly composed of helium in the form of He]]><sub>2</sub></RootMixedContentNested>"
|
384
|
+
|
385
|
+
expected_ox_xml = <<~XML
|
386
|
+
<RootMixedContentNested id="outer123">
|
387
|
+
<![CDATA[The following text is about the Moon.]]>
|
388
|
+
<MixedContent id="inner456">
|
389
|
+
<![CDATA[The Earth's Moon rings like a ]]>
|
390
|
+
<bold>
|
391
|
+
<![CDATA[bell]]>
|
392
|
+
</bold>
|
393
|
+
<![CDATA[ when struck by meteroids. Distanced from the Earth by ]]>
|
394
|
+
<italic>
|
395
|
+
<![CDATA[384,400 km]]>
|
396
|
+
</italic>
|
397
|
+
<![CDATA[ ,its surface is covered in ]]>
|
398
|
+
<underline>
|
399
|
+
<![CDATA[craters]]>
|
400
|
+
</underline>
|
401
|
+
<![CDATA[ .Ain't that ]]>
|
402
|
+
<bold>
|
403
|
+
<![CDATA[cool]]>
|
404
|
+
</bold>
|
405
|
+
<![CDATA[ ? ]]>
|
406
|
+
</MixedContent>
|
407
|
+
<sup>
|
408
|
+
<![CDATA[1]]>
|
409
|
+
</sup>
|
410
|
+
<![CDATA[The Moon is not a planet.]]>
|
411
|
+
<sup>
|
412
|
+
<![CDATA[2]]>
|
413
|
+
</sup>
|
414
|
+
<![CDATA[The Moon's atmosphere is mainly composed of helium in the form of He]]>
|
415
|
+
<sub>2</sub>
|
416
|
+
</RootMixedContentNested>
|
417
|
+
XML
|
418
|
+
|
419
|
+
it "deserializes and serializes mixed content correctly" do
|
420
|
+
parsed = CDATA::RootMixedContentNested.from_xml(xml)
|
421
|
+
|
422
|
+
expected_content = [
|
423
|
+
"The Earth's Moon rings like a ",
|
424
|
+
" when struck by meteroids. Distanced from the Earth by ",
|
425
|
+
" ,its surface is covered in ",
|
426
|
+
" .Ain't that ",
|
427
|
+
" ? ",
|
428
|
+
]
|
429
|
+
|
430
|
+
expect(parsed.id).to eq("outer123")
|
431
|
+
expect(parsed.sup).to eq(["1", "2"])
|
432
|
+
expect(parsed.sub).to eq(["2"])
|
433
|
+
expect(parsed.content.id).to eq("inner456")
|
434
|
+
expect(parsed.content.bold).to eq(["bell", "cool"])
|
435
|
+
expect(parsed.content.italic).to eq(["384,400 km"])
|
436
|
+
expect(parsed.content.underline).to eq("craters")
|
437
|
+
|
438
|
+
parsed.content.content.each_with_index do |content, index|
|
439
|
+
expected_output = expected_content[index]
|
440
|
+
|
441
|
+
# due to the difference in capturing
|
442
|
+
# newlines in ox and nokogiri adapters
|
443
|
+
if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
|
444
|
+
expected_xml = expected_ox_xml
|
445
|
+
end
|
446
|
+
|
447
|
+
expect(content).to eq(expected_output)
|
448
|
+
end
|
449
|
+
|
450
|
+
serialized = parsed.to_xml
|
451
|
+
expect(serialized).to eq(expected_xml)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context "when defualt: true is set for attributes default values" do
|
456
|
+
let(:xml) do
|
457
|
+
<<~XML
|
458
|
+
<DefaultValue>
|
459
|
+
<![CDATA[The following text is about the Moon]]>
|
460
|
+
<temperature>
|
461
|
+
<![CDATA[500]]>
|
462
|
+
</temperature>
|
463
|
+
<![CDATA[The Moon's atmosphere is mainly composed of helium in the form]]>
|
464
|
+
</DefaultValue>
|
465
|
+
XML
|
466
|
+
end
|
467
|
+
|
468
|
+
expected_xml = "<DefaultValue><name><![CDATA[Default Value]]></name><temperature><![CDATA[500]]></temperature><opacity>Opaque</opacity><![CDATA[The following text is about the MoonThe Moon's atmosphere is mainly composed of helium in the form]]></DefaultValue>"
|
469
|
+
|
470
|
+
expected_ox_xml = <<~XML
|
471
|
+
<DefaultValue>
|
472
|
+
<name>
|
473
|
+
<![CDATA[Default Value]]>
|
474
|
+
</name>
|
475
|
+
<temperature>
|
476
|
+
<![CDATA[500]]>
|
477
|
+
</temperature>
|
478
|
+
<opacity>Opaque</opacity>
|
479
|
+
<![CDATA[The following text is about the MoonThe Moon's atmosphere is mainly composed of helium in the form]]>
|
480
|
+
</DefaultValue>
|
481
|
+
XML
|
482
|
+
|
483
|
+
it "deserializes and serializes mixed content correctly" do
|
484
|
+
parsed = CDATA::DefaultValue.from_xml(xml)
|
485
|
+
|
486
|
+
expected_content = [
|
487
|
+
"The following text is about the Moon",
|
488
|
+
"The Moon's atmosphere is mainly composed of helium in the form",
|
489
|
+
]
|
490
|
+
|
491
|
+
expect(parsed.name).to eq("Default Value")
|
492
|
+
expect(parsed.opacity).to eq("Opaque")
|
493
|
+
expect(parsed.temperature).to eq(500)
|
494
|
+
|
495
|
+
parsed.content.each_with_index do |content, index|
|
496
|
+
expected_output = expected_content[index]
|
497
|
+
|
498
|
+
# due to the difference in capturing
|
499
|
+
# newlines in ox and nokogiri adapters
|
500
|
+
if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
|
501
|
+
expected_xml = expected_ox_xml
|
502
|
+
end
|
503
|
+
|
504
|
+
expect(content).to eq(expected_output)
|
505
|
+
end
|
506
|
+
|
507
|
+
serialized = parsed.to_xml
|
508
|
+
expect(serialized).to eq(expected_xml)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
|
514
|
+
it_behaves_like "cdata behavior", described_class
|
515
|
+
end
|
516
|
+
|
517
|
+
describe Lutaml::Model::XmlAdapter::OxAdapter do
|
518
|
+
it_behaves_like "cdata behavior", described_class
|
519
|
+
end
|
520
|
+
end
|
@@ -2,6 +2,7 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
class TestSerializable < Lutaml::Model::Serializable
|
4
4
|
attribute :name, :string, values: ["Alice", "Bob", "Charlie"]
|
5
|
+
attribute :email, :string, pattern: /.*?\S+@.+\.\S+/
|
5
6
|
attribute :age, :integer, collection: 1..3
|
6
7
|
|
7
8
|
xml do
|
@@ -27,9 +28,12 @@ class TestSerializable < Lutaml::Model::Serializable
|
|
27
28
|
end
|
28
29
|
|
29
30
|
RSpec.describe Lutaml::Model::Serializable do
|
30
|
-
let(:valid_instance)
|
31
|
+
let(:valid_instance) do
|
32
|
+
TestSerializable.new(name: "Alice", age: [30], email: "alice@gmail.com")
|
33
|
+
end
|
34
|
+
|
31
35
|
let(:invalid_instance) do
|
32
|
-
TestSerializable.new(name: "David", age: [25, 30, 35, 40])
|
36
|
+
TestSerializable.new(name: "David", age: [25, 30, 35, 40], email: "david@gmail")
|
33
37
|
end
|
34
38
|
|
35
39
|
describe "serialization methods" do
|
@@ -66,8 +70,9 @@ RSpec.describe Lutaml::Model::Serializable do
|
|
66
70
|
it "returns errors for invalid attributes" do
|
67
71
|
errors = invalid_instance.validate
|
68
72
|
expect(errors).not_to be_empty
|
69
|
-
expect(errors
|
70
|
-
expect(errors
|
73
|
+
expect(errors[0]).to be_a(Lutaml::Model::InvalidValueError)
|
74
|
+
expect(errors[1]).to be_a(Lutaml::Model::PatternNotMatchedError)
|
75
|
+
expect(errors[2]).to be_a(Lutaml::Model::CollectionCountOutOfRangeError)
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lutaml-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose Inc.
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/lutaml/model/error/collection_count_out_of_range_error.rb
|
60
60
|
- lib/lutaml/model/error/incorrect_mapping_argument_error.rb
|
61
61
|
- lib/lutaml/model/error/invalid_value_error.rb
|
62
|
+
- lib/lutaml/model/error/pattern_not_matched_error.rb
|
62
63
|
- lib/lutaml/model/error/type_error.rb
|
63
64
|
- lib/lutaml/model/error/type_not_enabled_error.rb
|
64
65
|
- lib/lutaml/model/error/unknown_adapter_type_error.rb
|
@@ -127,6 +128,7 @@ files:
|
|
127
128
|
- spec/fixtures/vase.rb
|
128
129
|
- spec/fixtures/xml/special_char.xml
|
129
130
|
- spec/lutaml/model/attribute_spec.rb
|
131
|
+
- spec/lutaml/model/cdata_spec.rb
|
130
132
|
- spec/lutaml/model/collection_spec.rb
|
131
133
|
- spec/lutaml/model/comparable_model_spec.rb
|
132
134
|
- spec/lutaml/model/custom_model_spec.rb
|