lutaml-model 0.3.25 → 0.3.26
Sign up to get free protection for your applications and to get access to all the features.
- 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
|