lutaml-model 0.7.1 → 0.7.7

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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/dependent-tests.yml +4 -0
  4. data/.github/workflows/rake.yml +12 -0
  5. data/.github/workflows/release.yml +3 -0
  6. data/.gitignore +6 -1
  7. data/.irbrc +1 -0
  8. data/.pryrc +1 -0
  9. data/.rubocop.yml +1 -1
  10. data/.rubocop_todo.yml +58 -84
  11. data/Gemfile +4 -1
  12. data/README.adoc +3115 -365
  13. data/RELEASE_NOTES.adoc +346 -0
  14. data/docs/custom_adapters.adoc +144 -0
  15. data/docs/custom_registers.adoc +228 -0
  16. data/docs/schema_generation.adoc +898 -0
  17. data/docs/schema_import.adoc +364 -0
  18. data/flake.lock +114 -0
  19. data/flake.nix +103 -0
  20. data/lib/lutaml/model/attribute.rb +242 -100
  21. data/lib/lutaml/model/choice.rb +30 -0
  22. data/lib/lutaml/model/collection.rb +212 -0
  23. data/lib/lutaml/model/comparable_model.rb +3 -3
  24. data/lib/lutaml/model/config.rb +71 -42
  25. data/lib/lutaml/model/constants.rb +2 -0
  26. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  27. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  28. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  29. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  30. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  31. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  32. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  33. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  34. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  35. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  36. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  37. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  38. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  39. data/lib/lutaml/model/error.rb +10 -0
  40. data/lib/lutaml/model/errors.rb +36 -0
  41. data/lib/lutaml/model/format_registry.rb +44 -0
  42. data/lib/lutaml/model/global_register.rb +41 -0
  43. data/lib/lutaml/model/hash_adapter/document.rb +11 -0
  44. data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
  45. data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
  47. data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
  48. data/lib/lutaml/model/hash_adapter.rb +21 -0
  49. data/lib/lutaml/model/json/document.rb +11 -0
  50. data/lib/lutaml/model/json/mapping.rb +19 -0
  51. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  52. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  53. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  54. data/lib/lutaml/model/json/transform.rb +8 -0
  55. data/lib/lutaml/model/json.rb +21 -0
  56. data/lib/lutaml/model/jsonl/document.rb +14 -0
  57. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  58. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  59. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  60. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  61. data/lib/lutaml/model/jsonl.rb +21 -0
  62. data/lib/lutaml/model/key_value_document.rb +28 -0
  63. data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
  64. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
  65. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  66. data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
  67. data/lib/lutaml/model/register.rb +105 -0
  68. data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
  69. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  70. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  71. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  72. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  73. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  74. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  75. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  76. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  77. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  78. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  79. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  80. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  81. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  82. data/lib/lutaml/model/schema/renderer.rb +36 -0
  83. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  84. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  85. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  86. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  87. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  88. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  89. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  90. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  91. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  92. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  93. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  94. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  95. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  96. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  97. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  98. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  99. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  100. data/lib/lutaml/model/schema.rb +1 -0
  101. data/lib/lutaml/model/sequence.rb +60 -30
  102. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  103. data/lib/lutaml/model/serialize.rb +296 -548
  104. data/lib/lutaml/model/services/base.rb +11 -0
  105. data/lib/lutaml/model/services/logger.rb +54 -0
  106. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  107. data/lib/lutaml/model/services/transformer.rb +48 -0
  108. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  109. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  110. data/lib/lutaml/model/services/type/validator.rb +43 -0
  111. data/lib/lutaml/model/services/validator.rb +145 -0
  112. data/lib/lutaml/model/services.rb +5 -0
  113. data/lib/lutaml/model/toml/document.rb +11 -0
  114. data/lib/lutaml/model/toml/mapping.rb +27 -0
  115. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  116. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  117. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  118. data/lib/lutaml/model/toml/transform.rb +8 -0
  119. data/lib/lutaml/model/toml.rb +30 -0
  120. data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
  121. data/lib/lutaml/model/transform/xml_transform.rb +228 -0
  122. data/lib/lutaml/model/transform.rb +93 -0
  123. data/lib/lutaml/model/type/boolean.rb +1 -1
  124. data/lib/lutaml/model/type/date.rb +1 -1
  125. data/lib/lutaml/model/type/date_time.rb +1 -1
  126. data/lib/lutaml/model/type/decimal.rb +11 -9
  127. data/lib/lutaml/model/type/float.rb +2 -1
  128. data/lib/lutaml/model/type/integer.rb +24 -21
  129. data/lib/lutaml/model/type/string.rb +4 -2
  130. data/lib/lutaml/model/type/time.rb +1 -1
  131. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  132. data/lib/lutaml/model/type/value.rb +11 -10
  133. data/lib/lutaml/model/type.rb +5 -2
  134. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  135. data/lib/lutaml/model/utils.rb +56 -4
  136. data/lib/lutaml/model/validation.rb +6 -4
  137. data/lib/lutaml/model/version.rb +1 -1
  138. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  139. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  140. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  141. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
  142. data/lib/lutaml/model/xml/element.rb +32 -0
  143. data/lib/lutaml/model/xml/mapping.rb +471 -0
  144. data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
  145. data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
  146. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  147. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
  148. data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
  149. data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
  150. data/lib/lutaml/model/xml/transform.rb +8 -0
  151. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  152. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
  153. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  154. data/lib/lutaml/model/xml.rb +31 -0
  155. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  156. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  157. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  158. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  159. data/lib/lutaml/model/yaml/document.rb +10 -0
  160. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  161. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  162. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  163. data/lib/lutaml/model/yaml/transform.rb +8 -0
  164. data/lib/lutaml/model/yaml.rb +21 -0
  165. data/lib/lutaml/model/yamls/document.rb +14 -0
  166. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  167. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  168. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  169. data/lib/lutaml/model/yamls/transform.rb +19 -0
  170. data/lib/lutaml/model/yamls.rb +21 -0
  171. data/lib/lutaml/model.rb +15 -4
  172. data/lutaml-model.gemspec +0 -4
  173. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
  174. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  175. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  176. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  177. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  178. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  179. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  180. data/spec/lutaml/model/attribute_spec.rb +41 -44
  181. data/spec/lutaml/model/cdata_spec.rb +7 -7
  182. data/spec/lutaml/model/choice_spec.rb +44 -0
  183. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  184. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  185. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  186. data/spec/lutaml/model/global_register_spec.rb +108 -0
  187. data/spec/lutaml/model/group_spec.rb +27 -10
  188. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  189. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  190. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  191. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  192. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  193. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  194. data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
  195. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  196. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  197. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  198. data/spec/lutaml/model/register/xml_spec.rb +185 -0
  199. data/spec/lutaml/model/register_spec.rb +147 -0
  200. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  201. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  202. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  203. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  204. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  205. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  206. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  207. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  208. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  209. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  210. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  211. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  212. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  213. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  214. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  215. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  216. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  217. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  218. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  219. data/spec/lutaml/model/sequence_spec.rb +36 -0
  220. data/spec/lutaml/model/serializable_spec.rb +201 -94
  221. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  222. data/spec/lutaml/model/toml_spec.rb +51 -0
  223. data/spec/lutaml/model/transformation_spec.rb +72 -15
  224. data/spec/lutaml/model/type_spec.rb +8 -4
  225. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  226. data/spec/lutaml/model/utils_spec.rb +3 -3
  227. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  228. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  229. data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
  230. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  231. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  232. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  233. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  234. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
  235. data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
  236. data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
  237. data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
  238. data/spec/lutaml/model/xml_spec.rb +63 -0
  239. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  240. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  241. data/spec/lutaml/model/yamls_spec.rb +294 -0
  242. data/spec/spec_helper.rb +4 -3
  243. metadata +162 -60
  244. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  245. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  246. data/lib/lutaml/model/loggable.rb +0 -15
  247. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  248. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  249. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  250. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  251. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  252. data/lib/lutaml/model/mapping.rb +0 -14
  253. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  254. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  255. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  256. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  257. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  258. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  259. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,234 @@
1
+ require "nokogiri"
2
+ require_relative "document"
3
+ require_relative "builder/nokogiri"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Xml
8
+ class NokogiriAdapter < Document
9
+ def self.parse(xml, options = {})
10
+ parsed = Nokogiri::XML(xml, nil, encoding(xml, options))
11
+ @root = NokogiriElement.new(parsed.root)
12
+ new(@root, parsed.encoding)
13
+ end
14
+
15
+ def to_xml(options = {})
16
+ builder_options = {}
17
+
18
+ if options.key?(:encoding)
19
+ builder_options[:encoding] = options[:encoding] unless options[:encoding].nil?
20
+ elsif options.key?(:parse_encoding)
21
+ builder_options[:encoding] = options[:parse_encoding]
22
+ else
23
+ builder_options[:encoding] = "UTF-8"
24
+ end
25
+
26
+ builder = Builder::Nokogiri.build(builder_options) do |xml|
27
+ if root.is_a?(Lutaml::Model::Xml::NokogiriElement)
28
+ root.build_xml(xml)
29
+ else
30
+ mapper_class = options[:mapper_class] || @root.class
31
+ options[:xml_attributes] =
32
+ build_namespace_attributes(mapper_class)
33
+ build_element(xml, @root, options)
34
+ end
35
+ end
36
+
37
+ xml_options = {}
38
+ xml_options[:indent] = 2 if options[:pretty]
39
+
40
+ xml_data = builder.doc.root.to_xml(xml_options)
41
+ options[:declaration] ? declaration(options) + xml_data : xml_data
42
+ end
43
+
44
+ private
45
+
46
+ def prefix_xml(xml, mapping, options)
47
+ if options.key?(:namespace_prefix)
48
+ xml[options[:namespace_prefix]] if options[:namespace_prefix]
49
+ elsif mapping.namespace_prefix
50
+ xml[mapping.namespace_prefix]
51
+ end
52
+ xml
53
+ end
54
+
55
+ def build_ordered_element(xml, element, options = {})
56
+ mapper_class = determine_mapper_class(element, options)
57
+ xml_mapping = mapper_class.mappings_for(:xml)
58
+ return xml unless xml_mapping
59
+
60
+ attributes = build_attributes(element, xml_mapping)&.compact
61
+
62
+ prefixed_xml = prefix_xml(xml, xml_mapping, options)
63
+
64
+ tag_name = options[:tag_name] || xml_mapping.root_element
65
+ tag_name = "#{tag_name}_" if prefixed_xml.respond_to?(tag_name)
66
+ prefixed_xml.public_send(tag_name, attributes) do
67
+ if options.key?(:namespace_prefix) && !options[:namespace_prefix]
68
+ xml.parent.namespace = nil
69
+ end
70
+
71
+ index_hash = {}
72
+ content = []
73
+
74
+ element.element_order.each do |object|
75
+ object_key = "#{object.name}-#{object.type}"
76
+ index_hash[object_key] ||= -1
77
+ curr_index = index_hash[object_key] += 1
78
+
79
+ element_rule = xml_mapping.find_by_name(object.name, type: object.type)
80
+ next if element_rule.nil?
81
+
82
+ attribute_def = attribute_definition_for(element, element_rule,
83
+ mapper_class: mapper_class)
84
+ value = attribute_value_for(element, element_rule)
85
+
86
+ if element_rule == xml_mapping.content_mapping
87
+ next if element_rule.cdata && object.text?
88
+
89
+ text = xml_mapping.content_mapping.serialize(element)
90
+ text = text[curr_index] if text.is_a?(Array)
91
+
92
+ next prefixed_xml.add_text(xml, text, cdata: element_rule.cdata) if element.mixed?
93
+
94
+ content << text
95
+ elsif !value.nil? || element_rule.render_nil?
96
+ value = value[curr_index] if attribute_def.collection?
97
+
98
+ add_to_xml(
99
+ xml,
100
+ element,
101
+ element_rule.prefix,
102
+ value,
103
+ options.merge(
104
+ attribute: attribute_def,
105
+ rule: element_rule,
106
+ mapper_class: mapper_class,
107
+ ),
108
+ )
109
+ end
110
+ end
111
+
112
+ prefixed_xml.text content.join
113
+ end
114
+ end
115
+ end
116
+
117
+ class NokogiriElement < XmlElement
118
+ def initialize(node, root_node: nil, default_namespace: nil)
119
+ if root_node
120
+ node.namespaces.each do |prefix, name|
121
+ namespace = XmlNamespace.new(name, prefix)
122
+
123
+ root_node.add_namespace(namespace)
124
+ end
125
+ end
126
+
127
+ attributes = {}
128
+
129
+ # Using `attribute_nodes` instead of `attributes` because
130
+ # `attribute_nodes` handles name collisions as well
131
+ # More info: https://devdocs.io/nokogiri/nokogiri/xml/node#method-i-attributes
132
+ node.attribute_nodes.each do |attr|
133
+ name = if attr.namespace
134
+ "#{attr.namespace.prefix}:#{attr.name}"
135
+ else
136
+ attr.name
137
+ end
138
+
139
+ attributes[name] = XmlAttribute.new(
140
+ name,
141
+ attr.value,
142
+ namespace: attr.namespace&.href,
143
+ namespace_prefix: attr.namespace&.prefix,
144
+ )
145
+ end
146
+
147
+ if root_node.nil? && !node.namespace&.prefix
148
+ default_namespace = node.namespace&.href
149
+ end
150
+
151
+ super(
152
+ node,
153
+ attributes,
154
+ parse_all_children(node, root_node: root_node || self,
155
+ default_namespace: default_namespace),
156
+ node.text,
157
+ name: node.name,
158
+ parent_document: root_node,
159
+ namespace_prefix: node.namespace&.prefix,
160
+ default_namespace: default_namespace
161
+ )
162
+ end
163
+
164
+ def text?
165
+ # false
166
+ children.empty? && text.length.positive?
167
+ end
168
+
169
+ def to_xml
170
+ return text if text?
171
+
172
+ build_xml.doc.root.to_xml
173
+ end
174
+
175
+ def inner_xml
176
+ children.map(&:to_xml).join
177
+ end
178
+
179
+ def build_xml(builder = nil)
180
+ builder ||= Builder::Nokogiri.build
181
+
182
+ if name == "text"
183
+ builder.text(text)
184
+ else
185
+ builder.public_send(name, build_attributes(self)) do |xml|
186
+ children.each do |child|
187
+ child.build_xml(xml)
188
+ end
189
+ end
190
+ end
191
+
192
+ builder
193
+ end
194
+
195
+ private
196
+
197
+ def parse_children(node, root_node: nil)
198
+ node.children.select(&:element?).map do |child|
199
+ NokogiriElement.new(child, root_node: root_node)
200
+ end
201
+ end
202
+
203
+ def parse_all_children(node, root_node: nil, default_namespace: nil)
204
+ node.children.map do |child|
205
+ NokogiriElement.new(child, root_node: root_node,
206
+ default_namespace: default_namespace)
207
+ end
208
+ end
209
+
210
+ def build_attributes(node, _options = {})
211
+ attrs = node.attributes.transform_values(&:value)
212
+
213
+ attrs.merge(build_namespace_attributes(node))
214
+ end
215
+
216
+ def build_namespace_attributes(node)
217
+ namespace_attrs = {}
218
+
219
+ node.own_namespaces.each_value do |namespace|
220
+ namespace_attrs[namespace.attr_name] = namespace.uri
221
+ end
222
+
223
+ node.children.each do |child|
224
+ namespace_attrs = namespace_attrs.merge(
225
+ build_namespace_attributes(child),
226
+ )
227
+ end
228
+
229
+ namespace_attrs
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  module Oga
7
7
  class Document < ::Oga::XML::Document
8
8
  def initialize(options = {})
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../xml_element"
4
+
3
5
  module Lutaml
4
6
  module Model
5
- module XmlAdapter
7
+ module Xml
6
8
  module Oga
7
9
  class Element < XmlElement
8
10
  def initialize(node, parent: nil)
@@ -17,11 +19,14 @@ module Lutaml
17
19
  when Moxml::Text
18
20
  node.content
19
21
  end
22
+
23
+ name = OgaAdapter.name_of(node)
20
24
  super(
21
- OgaAdapter.name_of(node),
25
+ name,
22
26
  Hash(attributes),
23
27
  Array(children),
24
28
  text,
29
+ name: name,
25
30
  parent_document: parent,
26
31
  namespace_prefix: namespace_name,
27
32
  )
@@ -0,0 +1,172 @@
1
+ require "oga"
2
+ require "moxml/adapter/oga"
3
+ require_relative "document"
4
+ require_relative "oga/document"
5
+ require_relative "oga/element"
6
+ require_relative "builder/oga"
7
+
8
+ module Lutaml
9
+ module Model
10
+ module Xml
11
+ class OgaAdapter < Document
12
+ TEXT_CLASSES = [Moxml::Text, Moxml::Cdata].freeze
13
+
14
+ def self.parse(xml, options = {})
15
+ parsed = Moxml::Adapter::Oga.parse(xml)
16
+ @root = Oga::Element.new(parsed.children.first)
17
+ new(@root, encoding(xml, options))
18
+ end
19
+
20
+ def to_xml(options = {})
21
+ builder_options = {}
22
+ builder_options[:encoding] = if options.key?(:encoding)
23
+ options[:encoding]
24
+ elsif options.key?(:parse_encoding)
25
+ options[:parse_encoding]
26
+ else
27
+ "UTF-8"
28
+ end
29
+
30
+ builder = Builder::Oga.build(builder_options) do |xml|
31
+ if @root.is_a?(Oga::Element)
32
+ @root.build_xml(xml)
33
+ else
34
+ build_element(xml, @root, options)
35
+ end
36
+ end
37
+ xml_data = builder.to_xml
38
+ options[:declaration] ? declaration(options) + xml_data : xml_data
39
+ end
40
+
41
+ def attributes_hash(element)
42
+ result = Lutaml::Model::MappingHash.new
43
+
44
+ element.attributes.each do |attr|
45
+ if attr.name == "schemaLocation"
46
+ result["__schema_location"] = {
47
+ namespace: attr.namespace,
48
+ prefix: attr.namespace.prefix,
49
+ schema_location: attr.value,
50
+ }
51
+ else
52
+ result[self.class.namespaced_attr_name(attr)] = attr.value
53
+ end
54
+ end
55
+
56
+ result
57
+ end
58
+
59
+ def self.name_of(element)
60
+ case element
61
+ when Moxml::Text
62
+ "text"
63
+ when Moxml::Cdata
64
+ "cdata"
65
+ else
66
+ element.name
67
+ end
68
+ end
69
+
70
+ def self.prefixed_name_of(node)
71
+ return name_of(node) if TEXT_CLASSES.include?(node.class)
72
+
73
+ [node&.namespace&.prefix, node.name].compact.join(":")
74
+ end
75
+
76
+ def self.text_of(element)
77
+ element.content
78
+ end
79
+
80
+ def self.namespaced_attr_name(attribute)
81
+ attr_ns = attribute.namespace
82
+ attr_name = attribute.name
83
+ return attr_name unless attr_ns
84
+
85
+ prefix = attr_name == "lang" ? attr_ns.prefix : attr_ns.uri
86
+ [prefix, attr_name].compact.join(":")
87
+ end
88
+
89
+ def self.namespaced_name_of(node)
90
+ return name_of(node) unless node.respond_to?(:namespace)
91
+
92
+ [node&.namespace&.uri, node.name].compact.join(":")
93
+ end
94
+
95
+ def order
96
+ children.map do |child|
97
+ type = child.text? ? "Text" : "Element"
98
+ Element.new(type, child.unprefixed_name)
99
+ end
100
+ end
101
+
102
+ def self.order_of(element)
103
+ element.children.map do |child|
104
+ instance_args = if TEXT_CLASSES.include?(child.class)
105
+ ["Text", "text"]
106
+ else
107
+ ["Element", name_of(child)]
108
+ end
109
+ Element.new(*instance_args)
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def build_ordered_element(builder, element, options = {})
116
+ mapper_class = determine_mapper_class(element, options)
117
+ xml_mapping = mapper_class.mappings_for(:xml)
118
+ return xml unless xml_mapping
119
+
120
+ attributes = build_attributes(element, xml_mapping).compact
121
+
122
+ tag_name = options[:tag_name] || xml_mapping.root_element
123
+ builder.create_and_add_element(tag_name,
124
+ attributes: attributes) do |el|
125
+ index_hash = {}
126
+ content = []
127
+
128
+ element.element_order.each do |object|
129
+ object_key = "#{object.name}-#{object.type}"
130
+ index_hash[object_key] ||= -1
131
+ curr_index = index_hash[object_key] += 1
132
+
133
+ element_rule = xml_mapping.find_by_name(object.name, type: object.type)
134
+ next if element_rule.nil?
135
+
136
+ attribute_def = attribute_definition_for(element, element_rule,
137
+ mapper_class: mapper_class)
138
+ value = attribute_value_for(element, element_rule)
139
+
140
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
141
+
142
+ if element_rule == xml_mapping.content_mapping
143
+ text = xml_mapping.content_mapping.serialize(element)
144
+ text = text[curr_index] if text.is_a?(Array)
145
+
146
+ next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
147
+
148
+ content << text
149
+ elsif !value.nil? || element_rule.render_nil?
150
+ value = value[curr_index] if attribute_def.collection?
151
+
152
+ add_to_xml(
153
+ el,
154
+ element,
155
+ nil,
156
+ value,
157
+ options.merge(
158
+ attribute: attribute_def,
159
+ rule: element_rule,
160
+ mapper_class: mapper_class,
161
+ ),
162
+ )
163
+ end
164
+ end
165
+
166
+ el.add_text(el, content.join)
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,229 @@
1
+ require "ox"
2
+ require_relative "document"
3
+ require_relative "builder/ox"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Xml
8
+ class OxAdapter < Document
9
+ def self.parse(xml, options = {})
10
+ Ox.default_options = Ox.default_options.merge(encoding: encoding(xml, options))
11
+
12
+ parsed = Ox.parse(xml)
13
+ @root = OxElement.new(parsed)
14
+ new(@root, Ox.default_options[:encoding])
15
+ end
16
+
17
+ def to_xml(options = {})
18
+ builder_options = { version: options[:version] }
19
+
20
+ builder_options[:encoding] = if options.key?(:encoding)
21
+ options[:encoding] unless options[:encoding].nil?
22
+ elsif options.key?(:parse_encoding)
23
+ options[:parse_encoding]
24
+ else
25
+ "UTF-8"
26
+ end
27
+
28
+ builder = Builder::Ox.build(builder_options)
29
+ builder.xml.instruct(:xml, encoding: builder_options[:encoding])
30
+
31
+ if @root.is_a?(Lutaml::Model::Xml::OxElement)
32
+ @root.build_xml(builder)
33
+ elsif ordered?(@root, options)
34
+ build_ordered_element(builder, @root, options)
35
+ else
36
+ mapper_class = options[:mapper_class] || @root.class
37
+ options[:xml_attributes] = build_namespace_attributes(mapper_class)
38
+ build_element(builder, @root, options)
39
+ end
40
+
41
+ xml_data = builder.xml.to_s
42
+ stripped_data = xml_data.lines.drop(1).join
43
+ options[:declaration] ? declaration(options) + stripped_data : stripped_data
44
+ end
45
+
46
+ private
47
+
48
+ def build_ordered_element(builder, element, options = {})
49
+ mapper_class = determine_mapper_class(element, options)
50
+ xml_mapping = mapper_class.mappings_for(:xml)
51
+ return xml unless xml_mapping
52
+
53
+ attributes = build_attributes(element, xml_mapping).compact
54
+
55
+ tag_name = options[:tag_name] || xml_mapping.root_element
56
+ builder.create_and_add_element(tag_name,
57
+ attributes: attributes) do |el|
58
+ index_hash = {}
59
+ content = []
60
+
61
+ element.element_order.each do |object|
62
+ object_key = "#{object.name}-#{object.type}"
63
+ index_hash[object_key] ||= -1
64
+ curr_index = index_hash[object_key] += 1
65
+
66
+ element_rule = xml_mapping.find_by_name(object.name, type: object.type)
67
+ next if element_rule.nil?
68
+
69
+ attribute_def = attribute_definition_for(element, element_rule,
70
+ mapper_class: mapper_class)
71
+ value = attribute_value_for(element, element_rule)
72
+
73
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && object.text?
74
+
75
+ if element_rule == xml_mapping.content_mapping
76
+ text = element.send(xml_mapping.content_mapping.to)
77
+ text = text[curr_index] if text.is_a?(Array)
78
+
79
+ next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
80
+
81
+ content << text
82
+ elsif !value.nil? || element_rule.render_nil?
83
+ value = value[curr_index] if attribute_def.collection?
84
+
85
+ add_to_xml(
86
+ el,
87
+ element,
88
+ nil,
89
+ value,
90
+ options.merge(
91
+ attribute: attribute_def,
92
+ rule: element_rule,
93
+ ),
94
+ )
95
+ end
96
+ end
97
+
98
+ el.add_text(el, content.join)
99
+ end
100
+ end
101
+ end
102
+
103
+ class OxElement < XmlElement
104
+ def initialize(node, root_node: nil)
105
+ case node
106
+ when String
107
+ super("text", {}, [], node, parent_document: root_node, name: "text")
108
+ when Ox::Comment
109
+ super("comment", {}, [], node.value, parent_document: root_node, name: "comment")
110
+ when Ox::CData
111
+ super("#cdata-section", {}, [], node.value, parent_document: root_node, name: "#cdata-section")
112
+ else
113
+ namespace_attributes(node.attributes).each do |(name, value)|
114
+ if root_node
115
+ root_node.add_namespace(XmlNamespace.new(value, name))
116
+ else
117
+ add_namespace(XmlNamespace.new(value, name))
118
+ end
119
+ end
120
+
121
+ attributes = node.attributes.each_with_object({}) do |(name, value), hash|
122
+ next if attribute_is_namespace?(name)
123
+
124
+ namespace_prefix = name.to_s.split(":").first
125
+ if (n = name.to_s.split(":")).length > 1
126
+ namespace = (root_node || self).namespaces[namespace_prefix]&.uri
127
+ namespace ||= XML_NAMESPACE_URI
128
+ prefix = n.first
129
+ end
130
+
131
+ hash[name.to_s] = XmlAttribute.new(
132
+ name.to_s,
133
+ value,
134
+ namespace: namespace,
135
+ namespace_prefix: prefix,
136
+ )
137
+ end
138
+
139
+ prefix, name = separate_name_and_prefix(node)
140
+
141
+ super(
142
+ node,
143
+ attributes,
144
+ parse_children(node, root_node: root_node || self),
145
+ node.text,
146
+ parent_document: root_node,
147
+ name: name,
148
+ namespace_prefix: prefix,
149
+ )
150
+ end
151
+ end
152
+
153
+ def separate_name_and_prefix(node)
154
+ name = node.name.to_s
155
+
156
+ return [nil, name] unless name.include?(":")
157
+
158
+ prefix, _, name = name.partition(":")
159
+ [prefix, name]
160
+ end
161
+
162
+ def to_xml
163
+ return text if text?
164
+
165
+ build_xml.xml.to_s
166
+ end
167
+
168
+ def inner_xml
169
+ # Ox builder by default, adds a newline at the end, so `chomp` is used
170
+ children.map { |child| child.to_xml.chomp }.join
171
+ end
172
+
173
+ def build_xml(builder = nil)
174
+ builder ||= Builder::Ox.build
175
+ attrs = build_attributes(self)
176
+
177
+ if text?
178
+ builder.add_text(builder, text)
179
+ else
180
+ builder.create_and_add_element(name, attributes: attrs) do |el|
181
+ children.each { |child| child.build_xml(el) }
182
+ end
183
+ end
184
+
185
+ builder
186
+ end
187
+
188
+ def namespace_attributes(attributes)
189
+ attributes.select { |attr| attribute_is_namespace?(attr) }
190
+ end
191
+
192
+ def text?
193
+ # false
194
+ children.empty? && text&.length&.positive?
195
+ end
196
+
197
+ def build_attributes(node)
198
+ attrs = node.attributes.transform_values(&:value)
199
+
200
+ node.own_namespaces.each_value do |namespace|
201
+ attrs[namespace.attr_name] = namespace.uri
202
+ end
203
+
204
+ attrs
205
+ end
206
+
207
+ def nodes
208
+ children
209
+ end
210
+
211
+ def cdata
212
+ super || cdata_children.first&.text
213
+ end
214
+
215
+ def text
216
+ super || cdata
217
+ end
218
+
219
+ private
220
+
221
+ def parse_children(node, root_node: nil)
222
+ node.nodes.map do |child|
223
+ OxElement.new(child, root_node: root_node)
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,8 @@
1
+ module Lutaml
2
+ module Model
3
+ module Xml
4
+ class Transform < Lutaml::Model::XmlTransform
5
+ end
6
+ end
7
+ end
8
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- module XmlAdapter
5
+ module Xml
6
6
  # Represents an XML attribute
7
7
  class XmlAttribute
8
8
  attr_reader :name, :value, :namespace, :namespace_prefix