lutaml-model 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/release.yml +3 -0
  4. data/.gitignore +6 -1
  5. data/.irbrc +1 -0
  6. data/.pryrc +1 -0
  7. data/.rubocop_todo.yml +25 -52
  8. data/README.adoc +2177 -192
  9. data/docs/custom_registers.adoc +228 -0
  10. data/docs/schema_generation.adoc +898 -0
  11. data/docs/schema_import.adoc +364 -0
  12. data/flake.lock +114 -0
  13. data/flake.nix +103 -0
  14. data/lib/lutaml/model/attribute.rb +230 -94
  15. data/lib/lutaml/model/choice.rb +30 -0
  16. data/lib/lutaml/model/collection.rb +194 -0
  17. data/lib/lutaml/model/comparable_model.rb +3 -3
  18. data/lib/lutaml/model/config.rb +26 -3
  19. data/lib/lutaml/model/constants.rb +2 -0
  20. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  21. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  22. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  23. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  24. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  25. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  26. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  27. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  28. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  31. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  32. data/lib/lutaml/model/error.rb +10 -0
  33. data/lib/lutaml/model/errors.rb +36 -0
  34. data/lib/lutaml/model/format_registry.rb +5 -2
  35. data/lib/lutaml/model/global_register.rb +41 -0
  36. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  37. data/lib/lutaml/model/jsonl/document.rb +14 -0
  38. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  39. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  40. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  41. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  42. data/lib/lutaml/model/jsonl.rb +21 -0
  43. data/lib/lutaml/model/key_value_document.rb +3 -2
  44. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  45. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  46. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  47. data/lib/lutaml/model/register.rb +105 -0
  48. data/lib/lutaml/model/registrable.rb +6 -0
  49. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  50. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  51. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  52. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  53. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  54. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  55. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  56. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  57. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  58. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  59. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  60. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  61. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  62. data/lib/lutaml/model/schema/renderer.rb +36 -0
  63. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  64. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  65. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  66. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  67. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  68. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  69. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  71. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  72. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  73. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  74. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  75. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  76. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  77. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  78. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  79. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  80. data/lib/lutaml/model/schema.rb +1 -0
  81. data/lib/lutaml/model/sequence.rb +60 -30
  82. data/lib/lutaml/model/serialize.rb +175 -53
  83. data/lib/lutaml/model/services/base.rb +11 -0
  84. data/lib/lutaml/model/services/logger.rb +2 -2
  85. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  86. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  87. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  88. data/lib/lutaml/model/services/type/validator.rb +43 -0
  89. data/lib/lutaml/model/services/validator.rb +145 -0
  90. data/lib/lutaml/model/services.rb +3 -0
  91. data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
  92. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  93. data/lib/lutaml/model/transform.rb +22 -8
  94. data/lib/lutaml/model/type/boolean.rb +1 -1
  95. data/lib/lutaml/model/type/date.rb +1 -1
  96. data/lib/lutaml/model/type/date_time.rb +1 -1
  97. data/lib/lutaml/model/type/decimal.rb +11 -9
  98. data/lib/lutaml/model/type/float.rb +2 -1
  99. data/lib/lutaml/model/type/integer.rb +24 -21
  100. data/lib/lutaml/model/type/string.rb +4 -2
  101. data/lib/lutaml/model/type/time.rb +1 -1
  102. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  103. data/lib/lutaml/model/type/value.rb +5 -1
  104. data/lib/lutaml/model/type.rb +5 -2
  105. data/lib/lutaml/model/utils.rb +30 -8
  106. data/lib/lutaml/model/validation.rb +6 -4
  107. data/lib/lutaml/model/version.rb +1 -1
  108. data/lib/lutaml/model/xml/document.rb +37 -19
  109. data/lib/lutaml/model/xml/mapping.rb +74 -13
  110. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  111. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  112. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  113. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  114. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  115. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  116. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  120. data/lib/lutaml/model/yamls/document.rb +14 -0
  121. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  122. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  123. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  124. data/lib/lutaml/model/yamls/transform.rb +19 -0
  125. data/lib/lutaml/model/yamls.rb +21 -0
  126. data/lib/lutaml/model.rb +7 -31
  127. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  128. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  129. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  130. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  131. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  132. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  133. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  134. data/spec/lutaml/model/attribute_spec.rb +41 -44
  135. data/spec/lutaml/model/choice_spec.rb +44 -0
  136. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  137. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  138. data/spec/lutaml/model/defaults_spec.rb +5 -1
  139. data/spec/lutaml/model/global_register_spec.rb +108 -0
  140. data/spec/lutaml/model/group_spec.rb +9 -3
  141. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  142. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  143. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  144. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  145. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  146. data/spec/lutaml/model/register_spec.rb +147 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  148. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  149. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  150. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  151. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  152. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  153. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  154. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  155. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  157. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  158. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  159. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  160. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  161. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  162. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  163. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  164. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  165. data/spec/lutaml/model/sequence_spec.rb +36 -0
  166. data/spec/lutaml/model/serializable_spec.rb +31 -0
  167. data/spec/lutaml/model/type_spec.rb +8 -4
  168. data/spec/lutaml/model/utils_spec.rb +3 -3
  169. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  170. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  171. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  172. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  173. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  174. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  175. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  176. data/spec/lutaml/model/yamls_spec.rb +294 -0
  177. data/spec/spec_helper.rb +1 -0
  178. metadata +105 -9
  179. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  180. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  181. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
data/lib/lutaml/model.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "moxml"
4
4
  require_relative "model/uninitialized_class"
5
+ require_relative "model/errors"
5
6
  require_relative "model/services"
6
7
  require_relative "model/version"
7
8
  require_relative "model/type"
@@ -11,13 +12,17 @@ require_relative "model/xml_adapter"
11
12
  require_relative "model/error"
12
13
  require_relative "model/constants"
13
14
  require_relative "model/config"
15
+ require_relative "model/global_register"
16
+ require_relative "model/register"
14
17
  require_relative "model/format_registry"
15
-
18
+ require_relative "model/collection"
16
19
  require_relative "model/key_value_document"
17
20
  require_relative "model/yaml"
21
+ require_relative "model/yamls"
18
22
  require_relative "model/json"
23
+ require_relative "model/jsonl"
19
24
  require_relative "model/toml"
20
- require_relative "model/hash"
25
+ require_relative "model/hash_adapter"
21
26
  require_relative "model/xml"
22
27
 
23
28
  module Lutaml
@@ -30,34 +35,5 @@ module Lutaml
30
35
 
31
36
  class BaseModel < Serializable
32
37
  end
33
-
34
- # Register default adapters
35
- # Lutaml::Model::FormatRegistry.register(
36
- # :json,
37
- # mapping_class: KeyValueMapping,
38
- # adapter_class: JsonAdapter::StandardJsonAdapter,
39
- # transformer: Lutaml::Model::KeyValueTransform,
40
- # )
41
-
42
- # Lutaml::Model::FormatRegistry.register(
43
- # :yaml,
44
- # mapping_class: KeyValueMapping,
45
- # adapter_class: YamlAdapter::StandardYamlAdapter,
46
- # transformer: Lutaml::Model::KeyValueTransform,
47
- # )
48
-
49
- # Lutaml::Model::FormatRegistry.register(
50
- # :toml,
51
- # mapping_class: KeyValueMapping,
52
- # adapter_class: nil,
53
- # transformer: Lutaml::Model::KeyValueTransform,
54
- # )
55
-
56
- # Lutaml::Model::FormatRegistry.register(
57
- # :xml,
58
- # mapping_class: XmlMapping,
59
- # adapter_class: nil,
60
- # transformer: Lutaml::Model::XmlTransform,
61
- # )
62
38
  end
63
39
  end
@@ -5,7 +5,7 @@ require "lutaml/model/xml/oga_adapter"
5
5
 
6
6
  RSpec.describe "LutaML Model Performance" do
7
7
  after do
8
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::NokogiriAdapter
8
+ Lutaml::Model::Config.xml_adapter_type = :nokogiri
9
9
  end
10
10
 
11
11
  let(:large_xml) do
@@ -26,7 +26,6 @@ RSpec.describe "LutaML Model Performance" do
26
26
  map_attribute "id", to: :id
27
27
  map_element "value", to: :value
28
28
  map_element "name", to: :name
29
- map_element "value", to: :value
30
29
  end
31
30
  end
32
31
 
@@ -48,12 +47,12 @@ RSpec.describe "LutaML Model Performance" do
48
47
  end
49
48
 
50
49
  x.report("Ox Adapter") do
51
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::OxAdapter
50
+ Lutaml::Model::Config.xml_adapter_type = :ox
52
51
  Deserializer.from_xml(large_xml)
53
52
  end
54
53
 
55
54
  x.report("Oga Adapter") do
56
- Lutaml::Model::Config.xml_adapter = Lutaml::Model::Xml::OgaAdapter
55
+ Lutaml::Model::Config.xml_adapter_type = :oga
57
56
  Deserializer.from_xml(large_xml)
58
57
  end
59
58
 
@@ -62,7 +61,7 @@ RSpec.describe "LutaML Model Performance" do
62
61
 
63
62
  thresholds = {
64
63
  "Nokogiri Adapter" => 3,
65
- "Ox Adapter" => 7,
64
+ "Ox Adapter" => 6,
66
65
  "Oga Adapter" => 3,
67
66
  }
68
67
 
@@ -0,0 +1,134 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ xmlns:test="http://www.example.com/test" targetNamespace="http://www.example.com/test" elementFormDefault="qualified">
4
+
5
+ <!-- Advanced Simple Types -->
6
+ <xs:simpleType name="StringOrInteger">
7
+ <xs:union memberTypes="xs:string xs:integer"/>
8
+ </xs:simpleType>
9
+
10
+ <xs:simpleType name="ColorList">
11
+ <xs:list itemType="xs:string"/>
12
+ </xs:simpleType>
13
+
14
+ <xs:simpleType name="RestrictedString">
15
+ <xs:restriction base="xs:string">
16
+ <xs:pattern value="[A-Z][a-z]+"/>
17
+ <xs:minLength value="2"/>
18
+ <xs:maxLength value="20"/>
19
+ </xs:restriction>
20
+ </xs:simpleType>
21
+
22
+ <!-- Base Complex Types -->
23
+ <xs:complexType name="BaseType">
24
+ <xs:sequence>
25
+ <xs:element name="id" type="xs:integer"/>
26
+ <xs:element name="name" type="test:RestrictedString"/>
27
+ </xs:sequence>
28
+ <xs:attribute name="version" type="xs:string" use="optional"/>
29
+ </xs:complexType>
30
+
31
+ <!-- Extension using complexContent -->
32
+ <xs:complexType name="ExtendedType">
33
+ <xs:complexContent>
34
+ <xs:extension base="test:BaseType">
35
+ <xs:sequence>
36
+ <xs:element name="description" type="xs:string"/>
37
+ <xs:element name="tags" type="test:ColorList"/>
38
+ </xs:sequence>
39
+ <xs:attribute name="priority" type="xs:integer" use="required"/>
40
+ </xs:extension>
41
+ </xs:complexContent>
42
+ </xs:complexType>
43
+
44
+ <!-- Restriction using complexContent -->
45
+ <xs:complexType name="RestrictedType">
46
+ <xs:complexContent>
47
+ <xs:restriction base="test:BaseType">
48
+ <xs:sequence>
49
+ <xs:element name="id" type="xs:positiveInteger"/>
50
+ <xs:element name="name" type="test:RestrictedString"/>
51
+ </xs:sequence>
52
+ <xs:attribute name="version" type="xs:string" fixed="1.0"/>
53
+ </xs:restriction>
54
+ </xs:complexContent>
55
+ </xs:complexType>
56
+
57
+ <!-- Mixed Content Type -->
58
+ <xs:complexType name="MixedContentType" mixed="true">
59
+ <xs:sequence>
60
+ <xs:element name="bold" type="xs:string" minOccurs="0"/>
61
+ <xs:element name="italic" type="xs:string" minOccurs="0"/>
62
+ </xs:sequence>
63
+ </xs:complexType>
64
+
65
+ <!-- Group Definitions -->
66
+ <xs:group name="CommonElements">
67
+ <xs:sequence>
68
+ <xs:element name="created" type="xs:dateTime"/>
69
+ <xs:element name="modified" type="xs:dateTime"/>
70
+ </xs:sequence>
71
+ </xs:group>
72
+
73
+ <!-- Attribute Group -->
74
+ <xs:attributeGroup name="CommonAttributes">
75
+ <xs:attribute name="id" type="xs:ID" use="required"/>
76
+ <xs:attribute name="type" type="xs:string" use="optional"/>
77
+ </xs:attributeGroup>
78
+
79
+ <!-- Complex Type with Groups -->
80
+ <xs:complexType name="GroupedType">
81
+ <xs:sequence>
82
+ <xs:group ref="test:CommonElements"/>
83
+ <xs:element name="content" type="xs:string"/>
84
+ </xs:sequence>
85
+ <xs:attributeGroup ref="test:CommonAttributes"/>
86
+ </xs:complexType>
87
+
88
+ <!-- Choice Element -->
89
+ <xs:complexType name="ChoiceType">
90
+ <xs:choice minOccurs="1" maxOccurs="unbounded">
91
+ <xs:element name="text" type="xs:string"/>
92
+ <xs:element name="number" type="xs:integer"/>
93
+ <xs:element name="date" type="xs:date"/>
94
+ </xs:choice>
95
+ </xs:complexType>
96
+
97
+ <!-- All Element -->
98
+ <xs:complexType name="AllType">
99
+ <xs:all>
100
+ <xs:element name="first" type="xs:string"/>
101
+ <xs:element name="second" type="xs:integer"/>
102
+ <xs:element name="third" type="xs:boolean"/>
103
+ </xs:all>
104
+ </xs:complexType>
105
+
106
+ <!-- Root Elements -->
107
+ <xs:element name="TestDocument">
108
+ <xs:complexType>
109
+ <xs:sequence>
110
+ <xs:element name="base" type="test:BaseType"/>
111
+ <xs:element name="extended" type="test:ExtendedType"/>
112
+ <xs:element name="restricted" type="test:RestrictedType"/>
113
+ <xs:element name="mixed" type="test:MixedContentType"/>
114
+ <xs:element name="grouped" type="test:GroupedType"/>
115
+ <xs:element name="choice" type="test:ChoiceType"/>
116
+ <xs:element name="all" type="test:AllType"/>
117
+ </xs:sequence>
118
+ </xs:complexType>
119
+ </xs:element>
120
+
121
+ <!-- Alternative Root with Substitution Group -->
122
+ <xs:element name="AlternativeDocument" substitutionGroup="test:TestDocument">
123
+ <xs:complexType>
124
+ <xs:complexContent>
125
+ <xs:extension base="test:TestDocument">
126
+ <xs:sequence>
127
+ <xs:element name="additional" type="xs:string"/>
128
+ </xs:sequence>
129
+ </xs:extension>
130
+ </xs:complexContent>
131
+ </xs:complexType>
132
+ </xs:element>
133
+
134
+ </xs:schema>
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0"?>
2
+ <ProductCatalog version="1.0" lastUpdated="2024-03-20T10:00:00+00:00">
3
+ <categories>
4
+ <category>
5
+ <id>1</id>
6
+ <name>Electronics</name>
7
+ <parentCategory>0</parentCategory>
8
+ <description>Electronic devices and accessories</description>
9
+ </category>
10
+ <category>
11
+ <id>2</id>
12
+ <name>Smartphones</name>
13
+ <parentCategory>1</parentCategory>
14
+ <description>Mobile phones and accessories</description>
15
+ </category>
16
+ <category>
17
+ <id>3</id>
18
+ <name>Laptops</name>
19
+ <parentCategory>1</parentCategory>
20
+ <description>Portable computers and accessories</description>
21
+ </category>
22
+ </categories>
23
+ <products>
24
+ <product id="1" status="active">
25
+ <sku>PHN12345678</sku>
26
+ <name>Smartphone Pro</name>
27
+ <description>Latest smartphone model</description>
28
+ <category>
29
+ <id>2</id>
30
+ <name>Smartphones</name>
31
+ <parentCategory>1</parentCategory>
32
+ <description>Mobile phones and accessories</description>
33
+ </category>
34
+ <price>99.99</price>
35
+ <salePrice>89.99</salePrice>
36
+ <inventory>50</inventory>
37
+ <dimensions>
38
+ <length>14.5</length>
39
+ <width>7.0</width>
40
+ <height>0.7</height>
41
+ <weight>0.18</weight>
42
+ </dimensions>
43
+ <rating>4.5</rating>
44
+ <features>
45
+ <feature>5G Connectivity</feature>
46
+ <feature>128GB Storage</feature>
47
+ <feature>Triple Camera System</feature>
48
+ </features>
49
+ <images>
50
+ <image url="https://example.com/images/smartphone-pro-1.jpg" alt="Smartphone Pro Front View" isPrimary="true"/>
51
+ <image url="https://example.com/images/smartphone-pro-2.jpg" alt="Smartphone Pro Back View"/>
52
+ </images>
53
+ </product>
54
+ </products>
55
+ </ProductCatalog>
@@ -0,0 +1,43 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ProductCatalog version="1.0" lastUpdated="2024-03-20T10:00:00+00:00">
3
+ <categories>
4
+ <category>
5
+ <id>1</id>
6
+ <name>Electronics</name>
7
+ <parentCategory>0</parentCategory>
8
+ <description>Electronic devices and accessories</description>
9
+ </category>
10
+ </categories>
11
+ <products>
12
+ <product id="1" status="active">
13
+ <sku>ABC12345678</sku>
14
+ <name>Smartphone X</name>
15
+ <description>A high-end smartphone with advanced features</description>
16
+ <category>
17
+ <id>1</id>
18
+ <name>Electronics</name>
19
+ <parentCategory>0</parentCategory>
20
+ <description>Electronic devices and accessories</description>
21
+ </category>
22
+ <price>99.99</price>
23
+ <salePrice>89.99</salePrice>
24
+ <inventory>100</inventory>
25
+ <dimensions>
26
+ <length>15.0</length>
27
+ <width>7.5</width>
28
+ <height>0.8</height>
29
+ <weight>0.2</weight>
30
+ </dimensions>
31
+ <rating>4.5</rating>
32
+ <features>
33
+ <feature>5G Connectivity</feature>
34
+ <feature>128GB Storage</feature>
35
+ <feature>Triple Camera System</feature>
36
+ </features>
37
+ <images>
38
+ <image url="https://example.com/images/smartphone-x-1.jpg" alt="Smartphone X Front View" isPrimary="true"/>
39
+ <image url="https://example.com/images/smartphone-x-2.jpg" alt="Smartphone X Back View"/>
40
+ </images>
41
+ </product>
42
+ </products>
43
+ </ProductCatalog>
@@ -0,0 +1,151 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ xmlns:cat="http://www.example.com/catalog" targetNamespace="http://www.example.com/catalog" elementFormDefault="qualified">
4
+
5
+ <!-- Attributes -->
6
+ <xs:attribute name="id" type="xs:integer" use="required"/>
7
+
8
+ <!-- Simple Types -->
9
+ <xs:simpleType name="SKUType">
10
+ <xs:annotation>
11
+ <xs:documentation>Stock Keeping Unit - must be 8-12 alphanumeric characters</xs:documentation>
12
+ </xs:annotation>
13
+ <xs:restriction base="xs:string">
14
+ <xs:pattern value="[A-Z0-9]{8,12}"/>
15
+ </xs:restriction>
16
+ </xs:simpleType>
17
+
18
+ <xs:simpleType name="PriceType">
19
+ <xs:annotation>
20
+ <xs:documentation>Price with 2 decimal places, must be positive</xs:documentation>
21
+ </xs:annotation>
22
+ <xs:restriction base="xs:decimal">
23
+ <xs:maxExclusive value="101.00"/>
24
+ <xs:minInclusive value="0.01"/>
25
+ </xs:restriction>
26
+ </xs:simpleType>
27
+
28
+ <xs:simpleType name="QuantityTypes">
29
+ <xs:annotation>
30
+ <xs:documentation>Quantity must be non-negative</xs:documentation>
31
+ </xs:annotation>
32
+ <xs:restriction base="xs:integer">
33
+ <xs:minInclusive value="0"/>
34
+ </xs:restriction>
35
+ </xs:simpleType>
36
+
37
+ <xs:simpleType name="RatingType">
38
+ <xs:annotation>
39
+ <xs:documentation>Product rating from 1 to 5</xs:documentation>
40
+ </xs:annotation>
41
+ <xs:restriction base="xs:decimal">
42
+ <xs:minInclusive value="1"/>
43
+ <xs:maxInclusive value="5"/>
44
+ </xs:restriction>
45
+ </xs:simpleType>
46
+
47
+ <!-- Complex Types -->
48
+ <xs:complexType name="DimensionsType">
49
+ <xs:sequence>
50
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
51
+ <xs:choice maxOccurs="unbounded">
52
+ <xs:element name="length" type="xs:decimal">
53
+ <xs:annotation>
54
+ <xs:documentation>Length in centimeters</xs:documentation>
55
+ </xs:annotation>
56
+ </xs:element>
57
+ </xs:choice>
58
+ <xs:element name="width" type="xs:decimal">
59
+ <xs:annotation>
60
+ <xs:documentation>Width in centimeters</xs:documentation>
61
+ </xs:annotation>
62
+ </xs:element>
63
+ </xs:choice>
64
+ <xs:element name="height" type="xs:decimal">
65
+ <xs:annotation>
66
+ <xs:documentation>Height in centimeters</xs:documentation>
67
+ </xs:annotation>
68
+ </xs:element>
69
+ <xs:element name="weight" type="xs:decimal">
70
+ <xs:annotation>
71
+ <xs:documentation>Weight in kilograms</xs:documentation>
72
+ </xs:annotation>
73
+ </xs:element>
74
+ </xs:sequence>
75
+ </xs:complexType>
76
+
77
+ <xs:complexType name="CategoryType">
78
+ <xs:sequence>
79
+ <xs:element name="id" type="xs:integer"/>
80
+ <xs:element name="name" type="xs:string"/>
81
+ <xs:element name="parentCategory" type="xs:integer" minOccurs="0"/>
82
+ <xs:element name="description" type="xs:string" minOccurs="0"/>
83
+ </xs:sequence>
84
+ </xs:complexType>
85
+
86
+ <xs:complexType name="ProductType">
87
+ <xs:sequence>
88
+ <xs:element name="sku" type="cat:SKUType"/>
89
+ <xs:element name="name" type="xs:string"/>
90
+ <xs:element name="description" type="xs:string"/>
91
+ <xs:element name="category" type="cat:CategoryType"/>
92
+ <xs:element name="price" type="cat:PriceType"/>
93
+ <xs:element name="salePrice" type="cat:PriceType" minOccurs="0"/>
94
+ <xs:element name="inventory" type="cat:QuantityTypes"/>
95
+ <xs:element name="dimensions" type="cat:DimensionsType"/>
96
+ <xs:element name="rating" type="cat:RatingType" minOccurs="0"/>
97
+ <xs:element name="features" minOccurs="0">
98
+ <xs:complexType>
99
+ <xs:sequence>
100
+ <xs:element name="feature" type="xs:string" maxOccurs="unbounded"/>
101
+ </xs:sequence>
102
+ </xs:complexType>
103
+ </xs:element>
104
+ <xs:element name="images" minOccurs="0">
105
+ <xs:complexType>
106
+ <xs:sequence>
107
+ <xs:element name="image" maxOccurs="unbounded">
108
+ <xs:complexType>
109
+ <xs:attribute name="url" type="xs:anyURI" use="required"/>
110
+ <xs:attribute name="alt" type="xs:string" use="required"/>
111
+ <xs:attribute name="isPrimary" type="xs:boolean" default="false"/>
112
+ </xs:complexType>
113
+ </xs:element>
114
+ </xs:sequence>
115
+ </xs:complexType>
116
+ </xs:element>
117
+ </xs:sequence>
118
+ <!-- using 'ref' for development purposes only -->
119
+ <xs:attribute ref="id"/>
120
+ <xs:attribute name="status" use="required">
121
+ <xs:simpleType>
122
+ <xs:restriction base="xs:string">
123
+ <xs:enumeration value="active"/>
124
+ <xs:enumeration value="inactive"/>
125
+ <xs:enumeration value="discontinued"/>
126
+ </xs:restriction>
127
+ </xs:simpleType>
128
+ </xs:attribute>
129
+ </xs:complexType>
130
+
131
+ <xs:complexType name="ProductCatalog" mixed="true">
132
+ <xs:sequence>
133
+ <xs:element name="categories">
134
+ <xs:complexType>
135
+ <xs:sequence>
136
+ <xs:element name="category" type="cat:CategoryType" maxOccurs="unbounded"/>
137
+ </xs:sequence>
138
+ </xs:complexType>
139
+ </xs:element>
140
+ <xs:element name="products">
141
+ <xs:complexType>
142
+ <xs:sequence>
143
+ <xs:element name="product" type="cat:ProductType" maxOccurs="unbounded"/>
144
+ </xs:sequence>
145
+ </xs:complexType>
146
+ </xs:element>
147
+ </xs:sequence>
148
+ <xs:attribute name="version" type="xs:string" use="required"/>
149
+ <xs:attribute name="lastUpdated" type="xs:dateTime" use="required"/>
150
+ </xs:complexType>
151
+ </xs:schema>
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/specs"
3
+ xmlns="http://example.com/specs" elementFormDefault="qualified">
4
+ <!-- ComplexType SpecBase and ShortSpec are root elements -->
5
+
6
+ <!-- Base type for a specification -->
7
+ <xs:complexType name="spec">
8
+ <xs:sequence>
9
+ <xs:element name="title" type="xs:string"/>
10
+ <xs:element name="version" type="xs:string"/>
11
+ <xs:element name="status" type="status" minOccurs="0" default="draft"/>
12
+ </xs:sequence>
13
+ <xs:attribute name="id" type="xs:ID" use="required"/>
14
+ </xs:complexType>
15
+
16
+ <!-- Restriction: A "ShortSpec" only allows title and version, not status -->
17
+ <xs:complexType name="shortSpec">
18
+ <xs:complexContent>
19
+ <xs:restriction base="spec">
20
+ <xs:sequence>
21
+ <xs:element name="title" type="xs:string"/>
22
+ <xs:element name="version" type="xs:string"/>
23
+ <xs:element name="status" type="status" minOccurs="0" maxOccurs="2"/>
24
+ </xs:sequence>
25
+ <xs:attribute name="id" type="xs:ID" use="required"/>
26
+ </xs:restriction>
27
+ </xs:complexContent>
28
+ </xs:complexType>
29
+
30
+ <!-- Enumeration for status -->
31
+ <xs:simpleType name="status">
32
+ <xs:restriction base="xs:string">
33
+ <xs:enumeration value="draft"/>
34
+ <xs:enumeration value="final"/>
35
+ <xs:enumeration value="archived"/>
36
+ </xs:restriction>
37
+ </xs:simpleType>
38
+ </xs:schema>
@@ -0,0 +1,101 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module AttributeCollection
5
+ class StringParts < Lutaml::Model::Collection
6
+ instances :parts, :string
7
+
8
+ def to_s
9
+ parts.join(" -- ")
10
+ end
11
+ end
12
+
13
+ class BibliographicItem < Lutaml::Model::Serializable
14
+ attribute :title_parts, :string, collection: StringParts
15
+
16
+ xml do
17
+ root "titles"
18
+ map_element "title", to: :title_parts
19
+ end
20
+
21
+ key_value do
22
+ root "titles"
23
+ map_instances to: :title_parts
24
+ end
25
+
26
+ def render_title
27
+ title_parts.to_s
28
+ end
29
+ end
30
+ end
31
+
32
+ RSpec.describe AttributeCollection do
33
+ let(:xml_data) do
34
+ <<~XML
35
+ <titles>
36
+ <title>Title One</title>
37
+ <title>Title Two</title>
38
+ <title>Title Three</title>
39
+ </titles>
40
+ XML
41
+ end
42
+
43
+ let(:yaml_data) do
44
+ <<~YAML
45
+ ---
46
+ titles:
47
+ - Title One
48
+ - Title Two
49
+ - Title Three
50
+ YAML
51
+ end
52
+
53
+ describe "BibliographicItem with custom collection" do
54
+ let(:bib_item) { AttributeCollection::BibliographicItem.from_xml(xml_data) }
55
+
56
+ it "initializes with custom collection class" do
57
+ expect(bib_item.title_parts).to be_a(AttributeCollection::StringParts)
58
+ end
59
+
60
+ it "contains the correct number of title parts" do
61
+ expect(bib_item.title_parts.count).to eq(3)
62
+ end
63
+
64
+ it "contains the correct title parts" do
65
+ expect(bib_item.title_parts.parts).to eq(["Title One", "Title Two", "Title Three"])
66
+ end
67
+
68
+ it "renders title with custom separator" do
69
+ expect(bib_item.render_title).to eq("Title One -- Title Two -- Title Three")
70
+ end
71
+
72
+ it "serializes to XML correctly" do
73
+ expect(bib_item.to_xml.strip).to eq(xml_data.strip)
74
+ end
75
+
76
+ it "deserializes from YAML correctly" do
77
+ yaml_bib_item = AttributeCollection::BibliographicItem.from_yaml(yaml_data)
78
+ expect(yaml_bib_item.title_parts.parts).to eq(["Title One", "Title Two", "Title Three"])
79
+ end
80
+
81
+ it "serializes to YAML correctly" do
82
+ expect(bib_item.to_yaml.strip).to eq(yaml_data.strip)
83
+ end
84
+
85
+ it "can be initialized with an array of strings" do
86
+ bib_item = AttributeCollection::BibliographicItem.new(
87
+ title_parts: AttributeCollection::StringParts.new(
88
+ ["Part One", "Part Two"],
89
+ ),
90
+ )
91
+
92
+ expect(bib_item.title_parts.parts).to eq(["Part One", "Part Two"])
93
+ end
94
+
95
+ it "can be initialized with a custom collection instance" do
96
+ collection = AttributeCollection::StringParts.new(["Custom One", "Custom Two"])
97
+ bib_item = AttributeCollection::BibliographicItem.new(title_parts: collection)
98
+ expect(bib_item.title_parts.parts).to eq(["Custom One", "Custom Two"])
99
+ end
100
+ end
101
+ end