lutaml-model 0.8.10 → 0.8.12

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +1 -0
  3. data/.github/workflows/opal.yml +31 -0
  4. data/.rspec-opal +5 -0
  5. data/.rubocop_todo.yml +68 -7
  6. data/README.adoc +53 -1
  7. data/docs/_guides/index.adoc +4 -0
  8. data/docs/_guides/jsonld-serialization.adoc +3 -1
  9. data/docs/_guides/opal.adoc +221 -0
  10. data/docs/_guides/rdf-serialization.adoc +94 -8
  11. data/docs/_guides/turtle-serialization.adoc +17 -4
  12. data/docs/_guides/xml_mappings/07_best_practices.adoc +2 -1
  13. data/docs/_pages/configuration.adoc +9 -4
  14. data/docs/_pages/index.adoc +1 -0
  15. data/docs/_pages/serialization_adapters.adoc +3 -2
  16. data/docs/index.adoc +1 -0
  17. data/lib/lutaml/hash_format/adapter/mapping.rb +2 -4
  18. data/lib/lutaml/json/adapter/mapping.rb +2 -4
  19. data/lib/lutaml/jsonl/adapter/mapping.rb +2 -4
  20. data/lib/lutaml/jsonld/transform.rb +70 -24
  21. data/lib/lutaml/key_value/adapter/hash/mapping.rb +2 -4
  22. data/lib/lutaml/key_value/adapter/json/mapping.rb +2 -4
  23. data/lib/lutaml/key_value/adapter/jsonl/mapping.rb +2 -4
  24. data/lib/lutaml/key_value/adapter/toml/mapping.rb +2 -4
  25. data/lib/lutaml/key_value/adapter/yaml/mapping.rb +2 -4
  26. data/lib/lutaml/key_value/adapter/yamls/mapping.rb +2 -4
  27. data/lib/lutaml/key_value/mapping.rb +4 -4
  28. data/lib/lutaml/model/adapter_resolver.rb +5 -8
  29. data/lib/lutaml/model/mapping/mapping.rb +12 -0
  30. data/lib/lutaml/model/store.rb +51 -4
  31. data/lib/lutaml/model/version.rb +1 -1
  32. data/lib/lutaml/rdf/mapping.rb +19 -13
  33. data/lib/lutaml/rdf/mapping_rule.rb +19 -2
  34. data/lib/lutaml/rdf/member_rule.rb +19 -2
  35. data/lib/lutaml/rdf/transform.rb +20 -11
  36. data/lib/lutaml/toml/adapter/mapping.rb +2 -4
  37. data/lib/lutaml/turtle/transform.rb +125 -53
  38. data/lib/lutaml/xml/schema/xsd.rb +5 -4
  39. data/lib/lutaml/xml/schema.rb +8 -5
  40. data/lib/lutaml/xml/xml_orderable.rb +17 -0
  41. data/lib/lutaml/xml.rb +8 -11
  42. data/lib/lutaml/yaml/adapter/mapping.rb +2 -4
  43. data/lib/lutaml/yamls/adapter/mapping.rb +7 -3
  44. data/lutaml-model.gemspec +1 -1
  45. data/spec/lutaml/jsonld/transform_spec.rb +239 -0
  46. data/spec/lutaml/model/opal_smoke_spec.rb +117 -0
  47. data/spec/lutaml/model/store_spec.rb +156 -2
  48. data/spec/lutaml/rdf/mapping_rule_spec.rb +97 -0
  49. data/spec/lutaml/rdf/mapping_spec.rb +74 -4
  50. data/spec/lutaml/rdf/member_rule_spec.rb +41 -0
  51. data/spec/lutaml/rdf/rdf_transform_spec.rb +95 -29
  52. data/spec/lutaml/turtle/mapping_spec.rb +2 -2
  53. data/spec/lutaml/turtle/transform_spec.rb +315 -0
  54. data/spec/lutaml/xml/opal_xml_spec.rb +145 -0
  55. data/spec/lutaml/xml/xml_spec.rb +64 -13
  56. data/spec/support/opal.rb +6 -0
  57. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1dea5a70c106b45dafcf2950dda9a8a1c2d8f2d37a4d32ec9a610e461b97a91
4
- data.tar.gz: ca6332ea17ae83c4473a5143d454f155006ad369759dab98c48a1ba825aa6d74
3
+ metadata.gz: 86f1ceda6e01c3d47e303f092a24062a0e051faaf8a859bb47d2723670088d61
4
+ data.tar.gz: 92b43d69b6d49244c4187859e497fdbbce11d913f11425e8dfdfed1493f97e5a
5
5
  SHA512:
6
- metadata.gz: 01c6623cc80b904875abe68601be5f4b76f441a0b6d0a599325db9454152072c4548985b29e12656b79cffeb28a965ddbf2d3191c6dc617503d914324cee3d35
7
- data.tar.gz: 38c58ea41a62c035f56ba5a6ae758ed45e861417d7b41dba4003418600f36a88b6ab1a6ab753bd3ff54c3c45a2ed25ac13a68c774c9f488e9785d3d5c44894c6
6
+ metadata.gz: 33612bb8b97cc3667b88a5638288155319281de507ca4b8ca6386ea6b9e3a79b747d3b0441d92b4083441310a7962449c511e1445eb3779c2bf872b1d2ebdca1
7
+ data.tar.gz: f51445ae0f41d649c89e21c43f8cb1738a146457a35fac1fb5f927f6829d7360e3d115b55d7ea24549c33b03074c381028b89fc10e4ef87e703cd7f51bd36bf6
@@ -29,6 +29,7 @@
29
29
  "relaton/loc_mods",
30
30
  "relaton/w3c_api",
31
31
  "ukiryu/ukiryu",
32
+ "unitsml/unitsdb-ruby",
32
33
  "unitsml/unitsml-ruby"
33
34
  ]
34
35
  }
@@ -0,0 +1,31 @@
1
+ name: opal
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ submodules: "recursive"
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: "3.3"
20
+ bundler-cache: true
21
+
22
+ - name: Set up Node.js
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: "18"
26
+
27
+ - name: Run Opal tests
28
+ # TODO: Remove continue-on-error once REXML adapter is verified
29
+ # under actual Opal runtime (REXML compiles to JS via Opal's stdlib)
30
+ run: bundle exec rake spec:opal
31
+ continue-on-error: true
data/.rspec-opal ADDED
@@ -0,0 +1,5 @@
1
+ --default-path=spec
2
+ --pattern='spec/lutaml/model/opal_smoke_spec.rb,spec/lutaml/xml/opal_xml_spec.rb'
3
+ -I lib
4
+ --require=spec_helper
5
+ --require=support/opal
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-18 04:34:59 UTC using RuboCop version 1.86.0.
3
+ # on 2026-05-27 05:02:01 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,13 +11,60 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'lutaml-model.gemspec'
13
13
 
14
- # Offense count: 3012
14
+ # Offense count: 2
15
+ # This cop supports safe autocorrection (--autocorrect).
16
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
17
+ # SupportedStyles: with_first_argument, with_fixed_indentation
18
+ Layout/ArgumentAlignment:
19
+ Exclude:
20
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
21
+
22
+ # Offense count: 1
23
+ # This cop supports safe autocorrection (--autocorrect).
24
+ Layout/ClosingParenthesisIndentation:
25
+ Exclude:
26
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
27
+
28
+ # Offense count: 1
29
+ # This cop supports safe autocorrection (--autocorrect).
30
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
31
+ # SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
32
+ Layout/FirstArgumentIndentation:
33
+ Exclude:
34
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
35
+
36
+ # Offense count: 2
37
+ # This cop supports safe autocorrection (--autocorrect).
38
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
39
+ # SupportedHashRocketStyles: key, separator, table
40
+ # SupportedColonStyles: key, separator, table
41
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
42
+ Layout/HashAlignment:
43
+ Exclude:
44
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
45
+
46
+ # Offense count: 3022
15
47
  # This cop supports safe autocorrection (--autocorrect).
16
48
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
49
  # URISchemes: http, https
18
50
  Layout/LineLength:
19
51
  Enabled: false
20
52
 
53
+ # Offense count: 1
54
+ # This cop supports safe autocorrection (--autocorrect).
55
+ # Configuration parameters: EnforcedStyle.
56
+ # SupportedStyles: symmetrical, new_line, same_line
57
+ Layout/MultilineMethodCallBraceLayout:
58
+ Exclude:
59
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
60
+
61
+ # Offense count: 1
62
+ # This cop supports safe autocorrection (--autocorrect).
63
+ # Configuration parameters: AllowInHeredoc.
64
+ Layout/TrailingWhitespace:
65
+ Exclude:
66
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
67
+
21
68
  # Offense count: 21
22
69
  # Configuration parameters: AllowedMethods.
23
70
  # AllowedMethods: enums
@@ -109,6 +156,12 @@ Lint/UnusedMethodArgument:
109
156
  - 'lib/lutaml/xml/unqualified_inheritance_strategy.rb'
110
157
  - 'spec/support/xml/xsd/code_example_validator.rb'
111
158
 
159
+ # Offense count: 1
160
+ # This cop supports safe autocorrection (--autocorrect).
161
+ Lint/UselessAssignment:
162
+ Exclude:
163
+ - 'spec/lutaml/xml/opal_xml_spec.rb'
164
+
112
165
  # Offense count: 1
113
166
  Lint/UselessConstantScoping:
114
167
  Exclude:
@@ -130,12 +183,12 @@ Metrics/BlockLength:
130
183
  Metrics/BlockNesting:
131
184
  Max: 6
132
185
 
133
- # Offense count: 310
186
+ # Offense count: 308
134
187
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
135
188
  Metrics/CyclomaticComplexity:
136
189
  Enabled: false
137
190
 
138
- # Offense count: 554
191
+ # Offense count: 553
139
192
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
140
193
  Metrics/MethodLength:
141
194
  Max: 514
@@ -238,7 +291,7 @@ RSpec/BeforeAfterAll:
238
291
  RSpec/ContextWording:
239
292
  Enabled: false
240
293
 
241
- # Offense count: 96
294
+ # Offense count: 98
242
295
  # Configuration parameters: IgnoredMetadata.
243
296
  RSpec/DescribeClass:
244
297
  Enabled: false
@@ -249,7 +302,7 @@ RSpec/DescribeMethod:
249
302
  - 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
250
303
  - 'spec/lutaml/xml/serializable_namespace_spec.rb'
251
304
 
252
- # Offense count: 1257
305
+ # Offense count: 1301
253
306
  # Configuration parameters: CountAsOne.
254
307
  RSpec/ExampleLength:
255
308
  Max: 68
@@ -324,7 +377,7 @@ RSpec/MultipleDescribes:
324
377
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
325
378
  - 'spec/lutaml/xml/xml_space_type_spec.rb'
326
379
 
327
- # Offense count: 1490
380
+ # Offense count: 1515
328
381
  RSpec/MultipleExpectations:
329
382
  Max: 21
330
383
 
@@ -490,3 +543,11 @@ Style/StringConcatenation:
490
543
  - 'lib/lutaml/model/schema/xml_compiler/complex_type.rb'
491
544
  - 'lib/lutaml/model/schema/xml_compiler/simple_type.rb'
492
545
  - 'lib/lutaml/model/schema/xml_compiler/xml_namespace_class.rb'
546
+
547
+ # Offense count: 1
548
+ # This cop supports safe autocorrection (--autocorrect).
549
+ # Configuration parameters: EnforcedStyleForMultiline.
550
+ # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
551
+ Style/TrailingCommaInArguments:
552
+ Exclude:
553
+ - 'spec/lutaml/model/opal_smoke_spec.rb'
data/README.adoc CHANGED
@@ -5507,6 +5507,49 @@ vocab.to_turtle
5507
5507
  vocab.to_jsonld
5508
5508
  ----
5509
5509
 
5510
+ ==== Multiple RDF types
5511
+
5512
+ `type` accepts a single compact IRI or an array:
5513
+
5514
+ [source,ruby]
5515
+ ----
5516
+ type "skos:Concept"
5517
+ # or multiple types:
5518
+ type ["skos:Concept", "dcterms:Agent"]
5519
+ ----
5520
+
5521
+ In Turtle, each type produces a separate `a` triple. In JSON-LD, a single type
5522
+ produces `"@type": "skos:Concept"` while multiple types produce an array.
5523
+
5524
+ ==== URI reference predicates
5525
+
5526
+ Predicates declared with `uri_reference: true` serialize values as URI objects
5527
+ rather than string literals. In Turtle, values are emitted without quotes. In
5528
+ JSON-LD, values are wrapped in `{"@id": ...}` and the context term includes
5529
+ `"@type": "@id"`.
5530
+
5531
+ [source,ruby]
5532
+ ----
5533
+ predicate :related, namespace: SkosNamespace, to: :related, uri_reference: true
5534
+ ----
5535
+
5536
+ Round-trip fidelity is preserved: compact IRI forms (e.g. `"skos:other"`) are
5537
+ maintained through serialization and deserialization.
5538
+
5539
+ ==== Linked member predicates
5540
+
5541
+ `members` accepts `predicate_name:` and `namespace:` to generate linking triples
5542
+ from the container to each member:
5543
+
5544
+ [source,ruby]
5545
+ ----
5546
+ members :children, predicate_name: :member, namespace: SkosNamespace
5547
+ ----
5548
+
5549
+ In Turtle, this produces `skos:member <child-uri>` triples on the container
5550
+ subject. In JSON-LD, the context includes a `member` term with `"@type": "@id"`
5551
+ and the container resource contains `{"@id": "..."}` references.
5552
+
5510
5553
  See link:docs/_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
5511
5554
  complete guide including language-tagged values, graph serialization, and
5512
5555
  architecture details.
@@ -16152,11 +16195,20 @@ Lutaml::Model supports running under https://opalrb.com[Opal] (Ruby compiled to
16152
16195
  JavaScript). The runtime is detected automatically and adapter selection is
16153
16196
  adjusted accordingly.
16154
16197
 
16155
- * *XML*: Only the `:oga` adapter is available (auto-selected).
16198
+ * *XML*: Only the `:rexml` adapter is available (auto-selected). Opal
16199
+ reimplements `strscan` and `stringio` in its stdlib, enabling REXML (pure
16200
+ Ruby) to compile cleanly to JavaScript.
16156
16201
  * *JSON*: Only the `:standard` adapter is available.
16202
+ * *YAML*: Only the `:standard` adapter is available.
16157
16203
  * *TOML*: Not available on Opal.
16158
16204
  * *Schema generation*: `to_xsd`, `to_relaxng`, and `from_xml` (XML schema
16159
16205
  compilation) raise `NotImplementedError` on Opal.
16206
+ * *Liquid templating*: Not available on Opal (Phase 1 — skipped).
16207
+ * *XPath*: Not available on Opal (REXML XPath requires features not yet
16208
+ supported by Opal's stdlib).
16209
+
16210
+ See the link:docs/_guides/opal.adoc[Opal Usage Guide] for setup instructions
16211
+ and limitations.
16160
16212
 
16161
16213
 
16162
16214
  === Error handling
@@ -46,6 +46,10 @@ See link:../references/rdf-namespaces[RDF Namespaces] for namespace classes,
46
46
  * link:../consolidation-mapping[Consolidation Mapping] - Group sibling elements into structured models
47
47
  * link:../document-validation[Document Validation] - Document-level validation with rules, profiles, and remediation
48
48
 
49
+ == Runtime environments
50
+
51
+ * link:../opal[Opal Usage Guide] - Run Lutaml::Model in the browser via Opal (Ruby to JavaScript)
52
+
49
53
  == By task
50
54
 
51
55
  === I want to serialize to XML
@@ -160,7 +160,9 @@ end
160
160
  ----
161
161
 
162
162
  See link:../_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
163
- complete guide including graph-level serialization with `members`.
163
+ complete guide including graph-level serialization with `members`, URI reference
164
+ predicates (`uri_reference: true`), multiple RDF types, and linking predicates
165
+ on member collections.
164
166
 
165
167
  == Multi-format models
166
168
 
@@ -0,0 +1,221 @@
1
+ ---
2
+ title: Opal Usage Guide
3
+ parent: Guides
4
+ nav_order: 99
5
+ ---
6
+
7
+ = Opal Usage Guide
8
+
9
+ :toc:
10
+ :toclevels: 3
11
+
12
+ == Overview
13
+
14
+ https://opalrb.com[Opal] is a Ruby-to-JavaScript compiler that allows Ruby code to run in the browser. Lutaml::Model supports running under Opal, enabling XML/JSON/YAML serialization in client-side applications.
15
+
16
+ The XML parsing layer is provided by the https://github.com/lutaml/moxml[moxml] gem (v0.2+), which detects the Opal runtime and uses the REXML adapter automatically.
17
+
18
+ == How it works
19
+
20
+ Opal compiles Ruby source code to JavaScript. Under Opal:
21
+
22
+ . `RUBY_ENGINE` equals `"opal"`
23
+ . `Lutaml::Model::RuntimeCompatibility.opal?` returns `true`
24
+ . Adapter selection is adjusted automatically
25
+ . Gems with C extensions (Nokogiri, Ox, Oj, tomlib) are not available
26
+ . REXML is used for XML because Opal reimplements `strscan` and `stringio` in its stdlib, enabling REXML (pure Ruby) to compile cleanly to JavaScript
27
+
28
+ No configuration is needed -- the runtime is detected and adapters are selected automatically.
29
+
30
+ == Supported features
31
+
32
+ === Fully supported
33
+
34
+ * Model definition with `attribute`, types, collections, defaults
35
+ * XML serialization/deserialization (via REXML adapter)
36
+ ** Element and attribute mapping
37
+ ** Nested elements and collections
38
+ ** Mixed content
39
+ ** Namespaces (parsing and serialization)
40
+ ** CDATA sections
41
+ ** Processing instructions
42
+ ** Comments
43
+ ** Entity references
44
+ ** XML declarations and doctypes
45
+ * JSON serialization/deserialization (via standard adapter)
46
+ * YAML serialization/deserialization (via standard adapter)
47
+ * Hash transformation
48
+ * Custom types
49
+ * Model import (`import_model`)
50
+ * Value transformations
51
+
52
+ === Not available
53
+
54
+ [cols="1,3",options="header"]
55
+ |===
56
+ | Feature | Reason
57
+ | XSD schema generation | Requires Nokogiri
58
+ | RELAX NG generation | Requires Nokogiri
59
+ | XML schema compilation | Requires Nokogiri + native parsing
60
+ | TOML serialization | Both tomlib and toml-rb require native extensions
61
+ | Oj / MultiJson adapters | Require native extensions
62
+ | Ox / Nokogiri / Oga adapters | Require native extensions or C extensions
63
+ | XPath queries | REXML XPath requires features not yet in Opal's stdlib
64
+ | Liquid templating | Uses file-system-based template loading (Phase 1: skipped)
65
+ | Canon XML equivalence | Uses Nokogiri for XML parsing
66
+ |===
67
+
68
+ == Setup
69
+
70
+ === Gemfile
71
+
72
+ Add Opal gems to your Gemfile:
73
+
74
+ [source,ruby]
75
+ ----
76
+ gem "lutaml-model"
77
+
78
+ group :opal do
79
+ gem "opal", "~> 1.8"
80
+ gem "opal-rspec", "~> 1.0"
81
+ gem "opal-sprockets"
82
+ end
83
+ ----
84
+
85
+ === Rake task
86
+
87
+ Add an Opal RSpec task to your Rakefile:
88
+
89
+ [source,ruby]
90
+ ----
91
+ begin
92
+ require "opal/rspec/rake_task"
93
+ rescue LoadError
94
+ # Opal not available
95
+ end
96
+
97
+ namespace :spec do
98
+ if defined?(Opal::RSpec::RakeTask)
99
+ desc "Run Opal (JavaScript) tests"
100
+ Opal::RSpec::RakeTask.new(:opal) do |server, runner|
101
+ server.append_path "lib"
102
+ runner.default_path = "spec"
103
+ runner.pattern = "spec/**/*_spec.{rb,opal}"
104
+ end
105
+ end
106
+ end
107
+ ----
108
+
109
+ === Test configuration
110
+
111
+ Create `spec/support/opal.rb` for Opal-specific test patches:
112
+
113
+ [source,ruby]
114
+ ----
115
+ # frozen_string_literal: true
116
+
117
+ if RUBY_ENGINE == "opal"
118
+ Lutaml::Model::Config.xml_adapter_type = :rexml
119
+ end
120
+ ----
121
+
122
+ Create `.rspec-opal`:
123
+
124
+ ----
125
+ --default-path=spec
126
+ --pattern='spec/**/*_spec.{rb,opal}'
127
+ -I lib
128
+ --opal-opt=-g,lutaml-model
129
+ -I spec
130
+ --require=spec_helper
131
+ --require=support/opal
132
+ ----
133
+
134
+ === CI workflow
135
+
136
+ Add `.github/workflows/opal.yml`:
137
+
138
+ [source,yaml]
139
+ ----
140
+ name: opal
141
+ on:
142
+ push:
143
+ branches: [main]
144
+ pull_request:
145
+
146
+ jobs:
147
+ test:
148
+ runs-on: ubuntu-latest
149
+ steps:
150
+ - uses: actions/checkout@v4
151
+ with:
152
+ submodules: "recursive"
153
+ - uses: ruby/setup-ruby@v1
154
+ with:
155
+ ruby-version: "3.3"
156
+ bundler-cache: true
157
+ - uses: actions/setup-node@v4
158
+ with:
159
+ node-version: "18"
160
+ - name: Run Opal tests
161
+ run: bundle exec rake spec:opal
162
+ ----
163
+
164
+ == Example: XML round-trip in the browser
165
+
166
+ [source,ruby]
167
+ ----
168
+ class Person
169
+ include Lutaml::Model::Serialize
170
+
171
+ attribute :name, :string
172
+ attribute :age, :integer
173
+
174
+ xml do
175
+ root "person"
176
+ map_element "name", to: :name
177
+ map_element "age", to: :age
178
+ end
179
+ end
180
+
181
+ # Parse XML
182
+ person = Person.from_xml('<person><name>Alice</name><age>30</age></person>')
183
+ person.name # => "Alice"
184
+ person.age # => 30
185
+
186
+ # Serialize to XML
187
+ person.to_xml # => "<person><name>Alice</name><age>30</age></person>"
188
+
189
+ # JSON and YAML also work
190
+ person.to_json # => '{"name":"Alice","age":30}'
191
+ person.to_yaml # => "---\nname: Alice\nage: 30\n"
192
+ ----
193
+
194
+ == Architecture
195
+
196
+ Under Opal, the library uses a JRuby-like pattern for dependency management:
197
+
198
+ . *Dependencies stay in the gemspec.* Gems like Nokogiri, Ox, and Oga remain listed as dependencies -- they are simply not loadable under Opal.
199
+ . *Requires are guarded with `RUBY_ENGINE`.* Code that depends on native gems uses `RUBY_ENGINE == "opal"` checks instead of silent `rescue LoadError`.
200
+ . *No gem splitting.* The same gem works on both MRI and Opal.
201
+
202
+ Key components:
203
+ * `Lutaml::Model::RuntimeCompatibility` -- detects the runtime (opal, windows, native)
204
+ * `Lutaml::Model::AdapterResolver` -- selects adapters based on runtime capabilities
205
+ * `Moxml::Config::OPAL_DEFAULT_ADAPTER` -- set to `:rexml`
206
+ * `Moxml::Adapter::OPAL_AVAILABLE_ADAPTERS` -- set to `%i[rexml]`
207
+
208
+ == Dependencies
209
+
210
+ The following gems in the lutaml ecosystem support Opal:
211
+
212
+ * **lutaml-model** -- Core model library (this gem)
213
+ * **moxml** (v0.2+) -- XML parsing abstraction with REXML adapter for Opal
214
+ * **canon** -- XML comparison (uses moxml under Opal; comparison features work but Nokogiri-specific features do not)
215
+
216
+ == Limitations
217
+
218
+ * **No XPath** -- REXML's XPath module has dependencies not yet reimplemented in Opal's stdlib.
219
+ * **No schema generation** -- XSD, RELAX NG generation, and schema compilation require Nokogiri.
220
+ * **No TOML** -- Both TOML adapters (tomlib, toml-rb) require native extensions.
221
+ * **No Liquid** -- Template rendering via Liquid is skipped in Opal (file-system dependency). May be addressed in a future phase using liquidjs via Opal's JavaScript bridge.
@@ -94,15 +94,22 @@ subject { |m| "http://example.org/concept/#{m.code}" }
94
94
 
95
95
  ==== type
96
96
 
97
- Sets the RDF type (`rdf:type`). Compact IRIs are resolved via declared
98
- namespaces:
97
+ Sets the RDF type (`rdf:type`). Accepts a single compact IRI or an array of
98
+ compact IRIs. Compact IRIs are resolved via declared namespaces:
99
99
 
100
100
  [source,ruby]
101
101
  ----
102
102
  type "skos:Concept"
103
103
  # resolves to <http://www.w3.org/2004/02/skos/core#Concept>
104
+
105
+ # multiple types:
106
+ type ["skos:Concept", "dcterms:Agent"]
104
107
  ----
105
108
 
109
+ In Turtle, each type produces a separate `a` triple. In JSON-LD, a single type
110
+ produces `"@type": "skos:Concept"` while multiple types produce an array
111
+ `"@type": ["skos:Concept", "dcterms:Agent"]`.
112
+
106
113
  ==== predicate
107
114
 
108
115
  Each `predicate` creates a mapping between an RDF predicate and a model
@@ -119,13 +126,38 @@ Parameters:
119
126
  * `namespace:` — the `Lutaml::Rdf::Namespace` subclass (required)
120
127
  * `to:` — the model attribute to read (required)
121
128
  * `lang_tagged:` (default: `false`) — if true, values are serialized with
122
- language tags (see <<language-tagged-values>>)
129
+ language tags (see <<language-tagged-values>>). Mutually exclusive with
130
+ `uri_reference`.
131
+ * `uri_reference:` (default: `false`) — if true, values are serialized as URI
132
+ references rather than string literals (see <<uri-reference-predicates>>).
133
+ Mutually exclusive with `lang_tagged`.
123
134
 
124
135
  ==== members
125
136
 
126
137
  Declares that a container model contains member resources that should be
127
138
  serialized as separate subjects in the output graph (see <<graph-serialization>>).
128
139
 
140
+ Optional linking predicate parameters generate relationship triples from the
141
+ container to each member:
142
+
143
+ [source,ruby]
144
+ ----
145
+ members :concepts, predicate_name: :member, namespace: SkosNamespace
146
+ ----
147
+
148
+ When `predicate_name` and `namespace` are provided:
149
+
150
+ * **Turtle**: produces `skos:member <child-uri>` triples on the container subject
151
+ * **JSON-LD**: adds a `member` term to `@context` with `"@type": "@id"` and
152
+ `{"@id": "..."}` references in the container resource
153
+
154
+ Parameters:
155
+
156
+ * `attr_name` — the model attribute holding the member collection (required)
157
+ * `predicate_name:` — the local name for the linking predicate (optional)
158
+ * `namespace:` — the `Lutaml::Rdf::Namespace` subclass for the linking predicate
159
+ (required when `predicate_name` is given)
160
+
129
161
  == Serialization
130
162
 
131
163
  === Turtle
@@ -207,6 +239,48 @@ class LocalizedLiteral < Lutaml::Model::Serializable
207
239
  end
208
240
  ----
209
241
 
242
+ == [[uri-reference-predicates]]URI Reference Predicates
243
+
244
+ Predicates declared with `uri_reference: true` serialize attribute values as
245
+ URI references rather than string literals:
246
+
247
+ [source,ruby]
248
+ ----
249
+ predicate :related, namespace: SkosNamespace, to: :related, uri_reference: true
250
+ ----
251
+
252
+ **Turtle:**
253
+
254
+ Values are emitted as URI objects without quotes. Compact IRIs (e.g.
255
+ `"skos:other"`) are resolved to full URIs using the declared namespaces. Full
256
+ URIs (e.g. `"http://example.org/foo"`) are used as-is.
257
+
258
+ [source,turtle]
259
+ ----
260
+ <http://example.org/concept/1> skos:related skos:other .
261
+ ----
262
+
263
+ **JSON-LD:**
264
+
265
+ Values are wrapped in `{"@id": ...}` objects. The auto-generated `@context`
266
+ includes a term definition with `"@type": "@id"`:
267
+
268
+ [source,json]
269
+ ----
270
+ {
271
+ "@context": {
272
+ "related": { "@id": "http://www.w3.org/2004/02/skos/core#related", "@type": "@id" }
273
+ },
274
+ "related": [ { "@id": "skos:other" } ]
275
+ }
276
+ ----
277
+
278
+ Round-trip fidelity: compact IRI forms are preserved through serialization and
279
+ deserialization. Values round-trip as `Concept.from_turtle(concept.to_turtle)`
280
+ preserving the original `"skos:other"` compact form.
281
+
282
+ The `uri_reference` option is mutually exclusive with `lang_tagged`.
283
+
210
284
  == [[graph-serialization]]Graph Serialization
211
285
 
212
286
  When a container model holds a collection of member resources, use `members` to
@@ -227,11 +301,16 @@ class Vocabulary < Lutaml::Model::Serializable
227
301
  type "skos:ConceptScheme"
228
302
  predicate :prefLabel, namespace: SkosNamespace, to: :id
229
303
 
230
- members :concepts
304
+ members :concepts,
305
+ predicate_name: :member,
306
+ namespace: SkosNamespace
231
307
  end
232
308
  end
233
309
  ----
234
310
 
311
+ The `predicate_name` and `namespace` parameters on `members` generate linking
312
+ triples from the container to each member.
313
+
235
314
  === Turtle Output with Members
236
315
 
237
316
  [source,ruby]
@@ -247,7 +326,9 @@ Produces a single Turtle document with the container and all member triples:
247
326
  @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
248
327
 
249
328
  <http://example.org/vocab/iso1087> a skos:ConceptScheme;
250
- skos:prefLabel "iso1087" .
329
+ skos:prefLabel "iso1087";
330
+ skos:member <http://example.org/concept/2119>;
331
+ skos:member <http://example.org/concept/2120> .
251
332
 
252
333
  <http://example.org/concept/2119> a skos:Concept;
253
334
  skos:notation "2119";
@@ -272,14 +353,19 @@ Produces a JSON-LD document with `@graph` containing all resources:
272
353
  {
273
354
  "@context": {
274
355
  "skos": "http://www.w3.org/2004/02/skos/core#",
275
- "prefLabel": "skos:prefLabel",
276
- "notation": "skos:notation"
356
+ "prefLabel": { "@id": "skos:prefLabel" },
357
+ "notation": { "@id": "skos:notation" },
358
+ "member": { "@id": "http://www.w3.org/2004/02/skos/core#member", "@type": "@id" }
277
359
  },
278
360
  "@graph": [
279
361
  {
280
362
  "@id": "http://example.org/vocab/iso1087",
281
363
  "@type": "skos:ConceptScheme",
282
- "prefLabel": "iso1087"
364
+ "prefLabel": "iso1087",
365
+ "member": [
366
+ { "@id": "http://example.org/concept/2119" },
367
+ { "@id": "http://example.org/concept/2120" }
368
+ ]
283
369
  },
284
370
  {
285
371
  "@id": "http://example.org/concept/2119",