lutaml-model 0.3.26 → 0.3.28

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dc709174fab4f4df2214f08b536e0b1aded39f0c9f48a18eb5a4a53da3fc4b6
4
- data.tar.gz: 9eea83160b8210f84d4b2f9baf2ab577ca91d2a737eaf98b175d52385e6fb5d3
3
+ metadata.gz: 7fbf76c4bb8fbe07fdaed6235a5e8edcd98dc17ace1d322d0e9a36c7c4a59aef
4
+ data.tar.gz: f63bf29850309ab1250e2713e62fd6ef624ad5c13a824dc6e7822dec77f75369
5
5
  SHA512:
6
- metadata.gz: 1e75326adeb9b8804f1bcc99104707edc3656e870d16c8af22e07e32ead9f4d78cd87eb372ebb834f2a5d942d58819e0fd3800e23a8099cf951360765223cd25
7
- data.tar.gz: ecc145fb2d9250cf855243c7b9cf468919f2530eed03039bd6c7a521fa534327a1a67c5348237b3d5959e20fa0fc2b2cadc87e57139f451610cd2d7d695f251b
6
+ metadata.gz: 58b261437656342dc5894e25dcde477f680e72b6eebd0ae6771a5d97f0212442ae5dab446d0e182f5a4a712516b8781a0849bdcb1fa2ee1fbc831ecf9dc33695
7
+ data.tar.gz: 0af5c64461b6e9f5ab6d67b18ebe81e19db49123b4241b201c786b1632b567290ab55288f7047e01e3c5938316d7e93b8aca511f6f4a3f3759f1d4575fe758ae
data/README.adoc CHANGED
@@ -874,6 +874,7 @@ class Example < Lutaml::Model::Serializable
874
874
  end
875
875
  ----
876
876
 
877
+
877
878
  === XML
878
879
 
879
880
  ==== Setting root element name
@@ -1302,6 +1303,69 @@ end
1302
1303
  ====
1303
1304
 
1304
1305
 
1306
+ ==== Encoding Options in XmlAdapter
1307
+
1308
+ XmlAdapter supports the encoding in the following ways:
1309
+
1310
+ . When encoding is not passed in to_xml:
1311
+ ** Default encoding is UTF-8.
1312
+
1313
+ . When encoding is explicitly passed nil:
1314
+ ** Encoding will be nil, show the HexCode(Nokogiri) or ASCII-8bit(Ox).
1315
+
1316
+ . When encoding is passed with some option:
1317
+ ** Encoding option will be selected as passed.
1318
+
1319
+
1320
+ Syntax:
1321
+
1322
+ [source,ruby]
1323
+ ----
1324
+ Example.new(description: " ∑ is my ∏ moniker µ.").to_xml
1325
+ Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
1326
+ Example.new(description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
1327
+ ----
1328
+
1329
+ [example]
1330
+ ====
1331
+ The following class will parse the XML snippet below:
1332
+
1333
+ [source,ruby]
1334
+ ----
1335
+ class Example < Lutaml::Model::Serializable
1336
+ attribute :name, :string
1337
+ attribute :description, :string
1338
+ attribute :value, :integer
1339
+
1340
+ xml do
1341
+ root 'example'
1342
+ map_element 'name', to: :name
1343
+ map_content to: :description
1344
+ end
1345
+ end
1346
+ ----
1347
+
1348
+ [source,xml]
1349
+ ----
1350
+ <example><name>John &#x0026; Doe</name> &#x2211; is my &#x220F; moniker &#xB5;.</example>
1351
+ ----
1352
+
1353
+ [source,ruby]
1354
+ ----
1355
+ > Example.from_xml(xml)
1356
+ > #<Example:0x0000000104ac7240 @name="John & Doe", @description=" ∑ is my ∏ moniker µ.">
1357
+ > Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml
1358
+ > #<example><name>John &amp; Doe</name> ∑ is my ∏ moniker µ.</example>
1359
+
1360
+ > Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: nil)
1361
+ > #<example><name>John &amp; Doe</name> &#x2211; is my &#x220F; moniker &#xB5;.</example>
1362
+
1363
+ > Example.new(name: "John & Doe", description: " ∑ is my ∏ moniker µ.").to_xml(encoding: "ASCII")
1364
+ > #<example><name>John &amp; Doe</name> &#8721; is my &#8719; moniker &#181;.</example>
1365
+ ----
1366
+ ====
1367
+
1368
+
1305
1369
  ==== Namespaces
1306
1370
 
1307
1371
  [[root-namespace]]
@@ -1726,7 +1790,7 @@ https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C XML standard].
1726
1790
  Key-value data models like JSON, YAML, and TOML all share a similar structure
1727
1791
  where data is stored as key-value pairs.
1728
1792
 
1729
- Lutaml::Model works with these formats in a similar way.
1793
+ `Lutaml::Model` works with these formats in a similar way.
1730
1794
 
1731
1795
  ==== Mapping
1732
1796
 
@@ -1736,12 +1800,85 @@ Syntax:
1736
1800
 
1737
1801
  [source,ruby]
1738
1802
  ----
1739
- json | yaml | toml do
1803
+ json | yaml | toml | key_value do
1740
1804
  map 'key_value_model_attribute_name', to: :name_of_attribute
1741
1805
  end
1742
1806
  ----
1743
1807
 
1744
- .Using the `map` method to define key-value mappings
1808
+
1809
+ ==== Unified mapping
1810
+
1811
+ The `key_value` method is a streamlined way to map all attributes for
1812
+ serialization into key-value formats including JSON, YAML, and TOML.
1813
+
1814
+ If there is no definite differentiation between the key value formats, the
1815
+ `key_value` method simplifies defining mappings and improves code readability.
1816
+
1817
+
1818
+ .Using the `map` method to define the same mappings across all key-value formats
1819
+ [example]
1820
+ ====
1821
+ This example shows how to define a key-value data model with the `key_value`
1822
+ method which maps the same attributes across all key-value formats.
1823
+
1824
+ [source,ruby]
1825
+ ----
1826
+ class CeramicModel < Lutaml::Model::Serializable
1827
+ attribute :color, :string
1828
+ attribute :glaze, :string
1829
+ attribute :description, :string
1830
+
1831
+ key_value do
1832
+ map :color, to: color
1833
+ map :glz, to: :glaze
1834
+ map :desc, to: :description
1835
+ end
1836
+
1837
+ # Equivalent to the JSON, YAML, and TOML mappings.
1838
+ #
1839
+ # json and yaml and toml do
1840
+ # map :id, to: color
1841
+ # map :name, to: :full_name
1842
+ # map :status, to: :current_status
1843
+ # end
1844
+ end
1845
+ ----
1846
+
1847
+ [source,json]
1848
+ ----
1849
+ {
1850
+ "color": "Navy Blue",
1851
+ "glz": "Clear",
1852
+ "desc": "A ceramic with a navy blue color and clear glaze."
1853
+ }
1854
+ ----
1855
+
1856
+ [source,yaml]
1857
+ ----
1858
+ color: Navy Blue
1859
+ glz: Clear
1860
+ desc: A ceramic with a navy blue color and clear glaze.
1861
+ ----
1862
+
1863
+ [source,ruby]
1864
+ ----
1865
+ > CeramicModel.from_json(json)
1866
+ > #<CeramicModel:0x0000000104ac7240 @color="Navy Blue", @glaze="Clear", @description="A ceramic with a navy blue color and clear glaze.">
1867
+ > CeramicModel.new(color: "Navy Blue", glaze: "Clear", description: "A ceramic with a navy blue color and clear glaze.").to_json
1868
+ > #{"color"=>"Navy Blue", "glz"=>"Clear", "desc"=>"A ceramic with a navy blue color and clear glaze."}
1869
+ ----
1870
+ ====
1871
+
1872
+ ==== Specific format mappings
1873
+
1874
+ Specific key value formats can be mapping independently of other formats, including:
1875
+
1876
+ * `json` for the JSON format
1877
+ * `yaml` for the YAML format
1878
+ * `toml` for the TOML format
1879
+
1880
+
1881
+ .Using the `map` method to define key-value mappings per format
1745
1882
  [example]
1746
1883
  ====
1747
1884
  [source,ruby]
@@ -9,6 +9,7 @@ module Lutaml
9
9
  attr_accessor :xml_adapter, :toml_adapter
10
10
 
11
11
  AVAILABLE_FORMATS = %i[xml json yaml toml].freeze
12
+ KEY_VALUE_FORMATS = AVAILABLE_FORMATS - %i[xml]
12
13
 
13
14
  def configure
14
15
  yield self
@@ -151,6 +151,13 @@ module Lutaml
151
151
  end
152
152
  end
153
153
 
154
+ def key_value(&block)
155
+ Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
156
+ mappings[format] ||= KeyValueMapping.new
157
+ mappings[format].instance_eval(&block)
158
+ end
159
+ end
160
+
154
161
  def hash_representation(instance, format, options = {})
155
162
  only = options[:only]
156
163
  except = options[:except]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.26"
5
+ VERSION = "0.3.28"
6
6
  end
7
7
  end
@@ -13,7 +13,15 @@ module Lutaml
13
13
  end
14
14
 
15
15
  def to_xml(options = {})
16
- builder = Builder::Nokogiri.build(encoding: "UTF-8") do |xml|
16
+ builder_options = {}
17
+
18
+ if options.key?(:encoding)
19
+ builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
20
+ else
21
+ builder_options[:encoding] = "UTF-8"
22
+ end
23
+
24
+ builder = Builder::Nokogiri.build(builder_options) do |xml|
17
25
  if root.is_a?(Lutaml::Model::XmlAdapter::NokogiriElement)
18
26
  root.build_xml(xml)
19
27
  else
@@ -35,12 +43,11 @@ module Lutaml
35
43
 
36
44
  def prefix_xml(xml, mapping, options)
37
45
  if options.key?(:namespace_prefix)
38
- options[:namespace_prefix] ? xml[options[:namespace_prefix]] : xml
46
+ xml[options[:namespace_prefix]] if options[:namespace_prefix]
39
47
  elsif mapping.namespace_prefix
40
48
  xml[mapping.namespace_prefix]
41
- else
42
- xml
43
49
  end
50
+ xml
44
51
  end
45
52
 
46
53
  def build_ordered_element(xml, element, options = {})
@@ -14,8 +14,15 @@ module Lutaml
14
14
 
15
15
  def to_xml(options = {})
16
16
  builder = Builder::Ox.build
17
- builder.xml.instruct(:xml, encoding: options[:encoding] || "UTF-8", version: options[:version])
17
+ builder_options = { version: options[:version] }
18
18
 
19
+ if options.key?(:encoding)
20
+ builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
21
+ else
22
+ builder_options[:encoding] = "UTF-8"
23
+ end
24
+
25
+ builder.xml.instruct(:xml, builder_options)
19
26
  if @root.is_a?(Lutaml::Model::XmlAdapter::OxElement)
20
27
  @root.build_xml(builder)
21
28
  elsif ordered?(@root, options)
@@ -66,7 +66,7 @@ module Lutaml
66
66
  options[:tag_name] = rule.name
67
67
 
68
68
  options[:mapper_class] = attribute&.type if attribute
69
- options[:namespace_set] = set_namespace?(rule)
69
+ options[:set_namespace] = set_namespace?(rule)
70
70
 
71
71
  options
72
72
  end
@@ -185,6 +185,7 @@ module Lutaml
185
185
  attributes = options[:xml_attributes] ||= {}
186
186
  attributes = build_attributes(element,
187
187
  xml_mapping, options).merge(attributes)&.compact
188
+
188
189
  if element.respond_to?(:schema_location) && element.schema_location
189
190
  attributes.merge!(element.schema_location.to_xml_attributes)
190
191
  end
@@ -275,7 +276,7 @@ module Lutaml
275
276
  end
276
277
 
277
278
  def set_namespace?(rule)
278
- rule.nil? || !rule.namespace_set? || !rule.namespace.nil?
279
+ rule.nil? || !rule.namespace_set?
279
280
  end
280
281
 
281
282
  def render_element?(rule, element, value)
@@ -336,7 +337,7 @@ module Lutaml
336
337
  end
337
338
 
338
339
  def build_attributes(element, xml_mapping, options = {})
339
- attrs = if options.fetch(:namespace_set, true)
340
+ attrs = if options.fetch(:set_namespace, true)
340
341
  namespace_attributes(xml_mapping)
341
342
  else
342
343
  {}
@@ -148,11 +148,11 @@ RSpec.describe Delegation do
148
148
  end
149
149
 
150
150
  it "provides XML declaration with UTF-8 encoding" \
151
- "if encoding: true option provided" do
151
+ "if encoding: 'UTF-8' option provided" do
152
152
  xml_data = delegation.to_xml(
153
153
  pretty: true,
154
154
  declaration: true,
155
- encoding: true,
155
+ encoding: "UTF-8",
156
156
  )
157
157
  expect(xml_data).to include('<?xml version="1.0" encoding="UTF-8"?>')
158
158
  end
@@ -170,6 +170,15 @@ module MixedContentSpec
170
170
  map_element :value, to: :value
171
171
  end
172
172
  end
173
+
174
+ class HexCode < Lutaml::Model::Serializable
175
+ attribute :content, :string
176
+
177
+ xml do
178
+ root "HexCode"
179
+ map_content to: :content
180
+ end
181
+ end
173
182
  end
174
183
 
175
184
  RSpec.describe "MixedContent" do
@@ -449,7 +458,7 @@ RSpec.describe "MixedContent" do
449
458
 
450
459
  it "serializes special char mixed content correctly" do
451
460
  parsed = MixedContentSpec::SpecialCharContentWithMixedTrue.from_xml(xml)
452
- serialized = parsed.to_xml
461
+ serialized = parsed.to_xml(encoding: "UTF-8")
453
462
 
454
463
  expect(serialized).to include(expected_xml)
455
464
  end
@@ -608,10 +617,68 @@ RSpec.describe "MixedContent" do
608
617
  end
609
618
  end
610
619
  end
620
+
621
+ context "when special char used as full entities, it persist as entities if no encoding provided" do
622
+ let(:xml) do
623
+ <<~XML
624
+ <HexCode>
625
+ &#x2211;computer security&#x220F; type of &#x200B; operation specified &#xB5; by an access right
626
+ </HexCode>
627
+ XML
628
+ end
629
+
630
+ describe ".from_xml" do
631
+ let(:expected_content) { "∑computer security∏ type of ​ operation specified µ by an access right" }
632
+
633
+ it "deserializes special char mixed content correctly" do
634
+ parsed = MixedContentSpec::HexCode.from_xml(xml)
635
+
636
+ expect(parsed.content.strip).to eq(expected_content)
637
+ end
638
+ end
639
+
640
+ describe ".to_xml" do
641
+ context "when default encoding xml" do
642
+ let(:expected_default_encoding_xml) { "∑computer security∏ type of ​ operation specified µ by an access right" }
643
+
644
+ it "serializes special char mixed content correctly with default encoding: UTF-8" do
645
+ parsed = MixedContentSpec::HexCode.from_xml(xml)
646
+ serialized = parsed.to_xml
647
+
648
+ expect(serialized.strip).to include(expected_default_encoding_xml)
649
+ end
650
+ end
651
+
652
+ context "when encoding: nil xml" do
653
+ let(:expected_encoding_nil_nokogiri_xml) { "&#x2211;computer security&#x220F; type of &#x200B; operation specified &#xB5; by an access right" }
654
+ let(:expected_encoding_nil_ox_xml) { "\xE2\x88\x91computer security\xE2\x88\x8F type of \xE2\x80\x8B operation specified \xC2\xB5 by an access right" }
655
+
656
+ it "serializes special char mixed content correctly with encoding: nil to get hexcode" do
657
+ parsed = MixedContentSpec::HexCode.from_xml(xml)
658
+ serialized = parsed.to_xml(encoding: nil)
659
+
660
+ if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
661
+ expected_output = expected_encoding_nil_ox_xml
662
+ expected_output.force_encoding("ASCII-8BIT")
663
+ else
664
+ expected_output = expected_encoding_nil_nokogiri_xml
665
+ end
666
+
667
+ expect(serialized.strip).to include(expected_output)
668
+ end
669
+ end
670
+ end
671
+ end
611
672
  end
612
673
 
613
674
  describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
614
675
  it_behaves_like "mixed content behavior", described_class
676
+
677
+ it "raises error when serializes special char content with false encoding: 'ABC'" do
678
+ parsed = MixedContentSpec::HexCode.from_xml("<HexCode>&#x2211;computer security</HexCode>")
679
+
680
+ expect { parsed.to_xml(encoding: "ABC") }.to raise_error(StandardError, "unknown encoding name - ABC")
681
+ end
615
682
  end
616
683
 
617
684
  describe Lutaml::Model::XmlAdapter::OxAdapter do
@@ -25,6 +25,18 @@ module SerializeableSpec
25
25
  end
26
26
  end
27
27
 
28
+ class KeyValueMapper < Lutaml::Model::Serializable
29
+ attribute :first_name, :string
30
+ attribute :last_name, :string
31
+ attribute :age, :integer
32
+
33
+ key_value do
34
+ map :first_name, to: :first_name
35
+ map :last_name, to: :last_name
36
+ map :age, to: :age
37
+ end
38
+ end
39
+
28
40
  ### XML root mapping
29
41
 
30
42
  class RecordDate < Lutaml::Model::Serializable
@@ -207,6 +219,22 @@ RSpec.describe Lutaml::Model::Serializable do
207
219
  end
208
220
  end
209
221
 
222
+ describe "#key_value" do
223
+ let(:model) { SerializeableSpec::KeyValueMapper }
224
+
225
+ Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
226
+ it "defines 3 mappings for #{format}" do
227
+ expect(model.mappings_for(format).mappings.count).to eq(3)
228
+ end
229
+
230
+ it "defines mappings correctly for #{format}" do
231
+ defined_mappings = model.mappings_for(format).mappings.map(&:name)
232
+
233
+ expect(defined_mappings).to eq(%i[first_name last_name age])
234
+ end
235
+ end
236
+ end
237
+
210
238
  describe "XML root name override" do
211
239
  it "uses root name defined at the component class" do
212
240
  record_date = SerializeableSpec::RecordDate.new(content: "2021-01-01")
@@ -127,6 +127,17 @@ module XmlMapping
127
127
  end
128
128
  end
129
129
 
130
+ class OverrideDefaultNamespacePrefix < Lutaml::Model::Serializable
131
+ attribute :same_element_name, SameNameDifferentNamespace
132
+
133
+ xml do
134
+ root "OverrideDefaultNamespacePrefix"
135
+ map_element :SameElementName, to: :same_element_name,
136
+ namespace: "http://www.omg.org/spec/XMI/20131001",
137
+ prefix: "abc"
138
+ end
139
+ end
140
+
130
141
  class SchemaLocationOrdered < Lutaml::Model::Serializable
131
142
  attribute :content, :string
132
143
  attribute :second, SchemaLocationOrdered
@@ -220,6 +231,30 @@ module XmlMapping
220
231
  prefix: nil
221
232
  end
222
233
  end
234
+
235
+ class Documentation < Lutaml::Model::Serializable
236
+ attribute :content, :string
237
+
238
+ xml do
239
+ root "documentation", mixed: true
240
+ namespace "http://www.w3.org/2001/XMLSchema", "xsd"
241
+
242
+ map_content to: :content
243
+ end
244
+ end
245
+
246
+ class Schema < Lutaml::Model::Serializable
247
+ attribute :documentation, Documentation, collection: true
248
+
249
+ xml do
250
+ root "schema"
251
+ namespace "http://www.w3.org/2001/XMLSchema", "xsd"
252
+
253
+ map_element :documentation, to: :documentation,
254
+ namespace: "http://www.w3.org/2001/XMLSchema",
255
+ prefix: "xsd"
256
+ end
257
+ end
223
258
  end
224
259
 
225
260
  RSpec.describe Lutaml::Model::XmlMapping do
@@ -245,6 +280,25 @@ RSpec.describe Lutaml::Model::XmlMapping do
245
280
  end
246
281
  end
247
282
 
283
+ context "overriding child namespace prefix" do
284
+ let(:input_xml) do
285
+ <<~XML
286
+ <OverrideDefaultNamespacePrefix xmlns:abc="http://www.omg.org/spec/XMI/20131001" xmlns:GML="http://www.sparxsystems.com/profiles/GML/1.0" xmlns:CityGML="http://www.sparxsystems.com/profiles/CityGML/1.0">
287
+ <abc:SameElementName App="hello">
288
+ <GML:ApplicationSchema>GML App</GML:ApplicationSchema>
289
+ <CityGML:ApplicationSchema>CityGML App</CityGML:ApplicationSchema>
290
+ <abc:ApplicationSchema>App</abc:ApplicationSchema>
291
+ </abc:SameElementName>
292
+ </OverrideDefaultNamespacePrefix>
293
+ XML
294
+ end
295
+
296
+ it "expect to round-trips" do
297
+ parsed = XmlMapping::OverrideDefaultNamespacePrefix.from_xml(input_xml)
298
+ expect(parsed.to_xml).to be_equivalent_to(input_xml)
299
+ end
300
+ end
301
+
248
302
  context "with same name elements" do
249
303
  let(:input_xml) do
250
304
  <<~XML
@@ -859,5 +913,19 @@ RSpec.describe Lutaml::Model::XmlMapping do
859
913
  expect(XmlMapping::SpecialCharContentWithMapAll.from_xml(xml).to_xml).to eq(expected_xml)
860
914
  end
861
915
  end
916
+
917
+ context "when mixed content is true and child is content_mapping" do
918
+ let(:xml) do
919
+ <<~XML
920
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
921
+ <xsd:documentation>asdf</xsd:documentation>
922
+ </xsd:schema>
923
+ XML
924
+ end
925
+
926
+ it "round-trips xml" do
927
+ expect(XmlMapping::Schema.from_xml(xml).to_xml).to be_equivalent_to(xml)
928
+ end
929
+ end
862
930
  end
863
931
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.26
4
+ version: 0.3.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-12 00:00:00.000000000 Z
11
+ date: 2024-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor