lutaml-model 0.3.26 → 0.3.28

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 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