lutaml-model 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-tests.yml +5 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +91 -22
- data/Gemfile +2 -0
- data/README.adoc +114 -2
- data/docs/_guides/index.adoc +18 -0
- data/docs/_guides/jsonld-serialization.adoc +217 -0
- data/docs/_guides/rdf-serialization.adoc +344 -0
- data/docs/_guides/turtle-serialization.adoc +224 -0
- data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
- data/docs/_pages/serialization_adapters.adoc +31 -0
- data/docs/_references/index.adoc +1 -0
- data/docs/_references/rdf-namespaces.adoc +243 -0
- data/docs/index.adoc +3 -2
- data/lib/lutaml/jsonld/adapter.rb +23 -0
- data/lib/lutaml/jsonld/context.rb +69 -0
- data/lib/lutaml/jsonld/term_definition.rb +39 -0
- data/lib/lutaml/jsonld/transform.rb +174 -0
- data/lib/lutaml/jsonld.rb +23 -0
- data/lib/lutaml/model/format_registry.rb +10 -1
- data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +6 -0
- data/lib/lutaml/rdf/error.rb +7 -0
- data/lib/lutaml/rdf/iri.rb +44 -0
- data/lib/lutaml/rdf/language_tagged.rb +11 -0
- data/lib/lutaml/rdf/literal.rb +62 -0
- data/lib/lutaml/rdf/mapping.rb +71 -0
- data/lib/lutaml/rdf/mapping_rule.rb +35 -0
- data/lib/lutaml/rdf/member_rule.rb +13 -0
- data/lib/lutaml/rdf/namespace.rb +58 -0
- data/lib/lutaml/rdf/namespace_set.rb +69 -0
- data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
- data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
- data/lib/lutaml/rdf/namespaces.rb +14 -0
- data/lib/lutaml/rdf/transform.rb +36 -0
- data/lib/lutaml/rdf.rb +19 -0
- data/lib/lutaml/turtle/adapter.rb +35 -0
- data/lib/lutaml/turtle/mapping.rb +7 -0
- data/lib/lutaml/turtle/transform.rb +158 -0
- data/lib/lutaml/turtle.rb +22 -0
- data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
- data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
- data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
- data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
- data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
- data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
- data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
- data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
- data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
- data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
- data/lib/lutaml/xml/adapter.rb +0 -1
- data/lib/lutaml/xml/adapter_element.rb +7 -1
- data/lib/lutaml/xml/builder/base.rb +0 -1
- data/lib/lutaml/xml/data_model.rb +9 -1
- data/lib/lutaml/xml/document.rb +3 -1
- data/lib/lutaml/xml/element.rb +13 -10
- data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
- data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
- data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
- data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
- data/lib/lutaml/xml/xml_element.rb +24 -20
- data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
- data/spec/lutaml/integration/multi_format_spec.rb +106 -0
- data/spec/lutaml/integration/round_trip_spec.rb +170 -0
- data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
- data/spec/lutaml/jsonld/context_spec.rb +114 -0
- data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
- data/spec/lutaml/jsonld/transform_spec.rb +211 -0
- data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
- data/spec/lutaml/rdf/iri_spec.rb +73 -0
- data/spec/lutaml/rdf/literal_spec.rb +98 -0
- data/spec/lutaml/rdf/mapping_spec.rb +164 -0
- data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
- data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
- data/spec/lutaml/rdf/namespace_spec.rb +241 -0
- data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
- data/spec/lutaml/turtle/adapter_spec.rb +47 -0
- data/spec/lutaml/turtle/mapping_spec.rb +123 -0
- data/spec/lutaml/turtle/transform_spec.rb +273 -0
- data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
- data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
- data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
- data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
- data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
- data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
- metadata +58 -3
- data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: JSON-LD Serialization
|
|
3
|
+
nav_order: 15
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
= JSON-LD Serialization
|
|
7
|
+
|
|
8
|
+
Lutaml::Model supports serialization to and from JSON-LD (W3C JSON-LD 1.1) using
|
|
9
|
+
SKOS, Dublin Core Terms, and other W3C vocabularies.
|
|
10
|
+
|
|
11
|
+
== Setup
|
|
12
|
+
|
|
13
|
+
JSON-LD is built on the key-value serialization pipeline and requires no
|
|
14
|
+
additional gems beyond what JSON already uses. The `json-ld` gem may be added
|
|
15
|
+
for advanced JSON-LD Processing (expansion, compaction, flattening) in the
|
|
16
|
+
future.
|
|
17
|
+
|
|
18
|
+
JSON-LD is a **key-value** format adapter that extends the built-in key-value
|
|
19
|
+
serialization with JSON-LD-specific constructs: `@context`, `@type`, and
|
|
20
|
+
`@id`.
|
|
21
|
+
|
|
22
|
+
== Mapping DSL
|
|
23
|
+
|
|
24
|
+
Use the `jsonld do` block in your model to define JSON-LD mappings:
|
|
25
|
+
|
|
26
|
+
[source,ruby]
|
|
27
|
+
----
|
|
28
|
+
class Concept < Lutaml::Model::Serializable
|
|
29
|
+
attribute :name, :string
|
|
30
|
+
attribute :description, :string
|
|
31
|
+
|
|
32
|
+
jsonld do
|
|
33
|
+
context do
|
|
34
|
+
prefix Lutaml::Rdf::Namespaces::SkosNamespace
|
|
35
|
+
vocab "http://example.org/ns/"
|
|
36
|
+
|
|
37
|
+
term "name", id: "http://example.org/name"
|
|
38
|
+
term "description", id: "http://example.org/description"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
type "skos:Concept"
|
|
42
|
+
id { |m| "http://example.org/concept/#{m.name}" }
|
|
43
|
+
|
|
44
|
+
map "name", to: :name
|
|
45
|
+
map "description", to: :description
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
----
|
|
49
|
+
|
|
50
|
+
=== Context
|
|
51
|
+
|
|
52
|
+
The `context` block builds the `@context` object in the JSON-LD output:
|
|
53
|
+
|
|
54
|
+
* `prefix(NamespaceClass)` — registers an RDF namespace as a prefix
|
|
55
|
+
* `vocab(uri)` — sets `@vocab` for unprefixed terms
|
|
56
|
+
* `language(code)` — sets default `@language`
|
|
57
|
+
* `base(uri)` — sets `@base` URI
|
|
58
|
+
* `term(name, ...)` — defines a term with optional `id:`, `type:`,
|
|
59
|
+
`container:`, `language:`, `reverse:`
|
|
60
|
+
|
|
61
|
+
Context resolution:
|
|
62
|
+
|
|
63
|
+
* Compact IRIs (e.g., `"skos:Concept"`) are expanded to full URIs via prefix
|
|
64
|
+
* Terms are resolved via the term definitions
|
|
65
|
+
* Unprefixed names are resolved via `@vocab`
|
|
66
|
+
|
|
67
|
+
=== Type and ID
|
|
68
|
+
|
|
69
|
+
* `type "skos:Concept"` — sets `@type` in the output (resolved to full IRI via
|
|
70
|
+
context)
|
|
71
|
+
* `id { |model| ... }` — generates `@id` from the model instance
|
|
72
|
+
|
|
73
|
+
Both are optional. Omit `type` or `id` if your JSON-LD document does not
|
|
74
|
+
require them.
|
|
75
|
+
|
|
76
|
+
=== map
|
|
77
|
+
|
|
78
|
+
`map` entries work identically to key-value serialization, defining the JSON
|
|
79
|
+
properties serialized from model attributes.
|
|
80
|
+
|
|
81
|
+
== Serialization
|
|
82
|
+
|
|
83
|
+
[source,ruby]
|
|
84
|
+
----
|
|
85
|
+
concept = Concept.new(name: "test", description: "A test concept")
|
|
86
|
+
jsonld = concept.to_jsonld
|
|
87
|
+
----
|
|
88
|
+
|
|
89
|
+
Produces:
|
|
90
|
+
|
|
91
|
+
[source,json]
|
|
92
|
+
----
|
|
93
|
+
{
|
|
94
|
+
"@context": {
|
|
95
|
+
"skos": "http://www.w3.org/2004/02/skos/core#",
|
|
96
|
+
"@vocab": "http://example.org/ns/",
|
|
97
|
+
"name": "http://example.org/name",
|
|
98
|
+
"description": "http://example.org/description"
|
|
99
|
+
},
|
|
100
|
+
"@type": "http://www.w3.org/2004/02/skos/core#Concept",
|
|
101
|
+
"@id": "http://example.org/concept/test",
|
|
102
|
+
"name": "test",
|
|
103
|
+
"description": "A test concept"
|
|
104
|
+
}
|
|
105
|
+
----
|
|
106
|
+
|
|
107
|
+
Nil attribute values are omitted from the output.
|
|
108
|
+
|
|
109
|
+
== Deserialization
|
|
110
|
+
|
|
111
|
+
[source,ruby]
|
|
112
|
+
----
|
|
113
|
+
concept = Concept.from_jsonld(jsonld_string)
|
|
114
|
+
puts concept.name # => "test"
|
|
115
|
+
----
|
|
116
|
+
|
|
117
|
+
The `from_jsonld` method:
|
|
118
|
+
|
|
119
|
+
. Parses the JSON-LD string into a hash via `JSON.parse`
|
|
120
|
+
. Strips all `@`-prefixed keywords (`@context`, `@type`, `@id`, `@graph`,
|
|
121
|
+
etc.) before attribute mapping
|
|
122
|
+
. Delegates attribute mapping to the key-value transform pipeline
|
|
123
|
+
|
|
124
|
+
This prevents JSON-LD keywords from colliding with model attributes named
|
|
125
|
+
`type`, `id`, `context`, etc.
|
|
126
|
+
|
|
127
|
+
== Round-trip
|
|
128
|
+
|
|
129
|
+
Model data round-trips through JSON-LD serialization:
|
|
130
|
+
|
|
131
|
+
[source,ruby]
|
|
132
|
+
----
|
|
133
|
+
restored = Concept.from_jsonld(concept.to_jsonld)
|
|
134
|
+
restored.name == concept.name # => true
|
|
135
|
+
----
|
|
136
|
+
|
|
137
|
+
The `@context` structure is preserved across round-trips because it is defined
|
|
138
|
+
in the mapping, not derived from the input.
|
|
139
|
+
|
|
140
|
+
== Unified `rdf` DSL
|
|
141
|
+
|
|
142
|
+
If you need both JSON-LD and Turtle output from the same model, use the
|
|
143
|
+
unified `rdf` DSL instead of separate `jsonld` and `turtle` blocks. This
|
|
144
|
+
defines the mapping once and auto-generates `@context` from predicates:
|
|
145
|
+
|
|
146
|
+
[source,ruby]
|
|
147
|
+
----
|
|
148
|
+
class Concept < Lutaml::Model::Serializable
|
|
149
|
+
attribute :name, :string
|
|
150
|
+
attribute :code, :string
|
|
151
|
+
|
|
152
|
+
rdf do
|
|
153
|
+
namespace Lutaml::Rdf::Namespaces::SkosNamespace
|
|
154
|
+
subject { |m| "http://example.org/#{m.code}" }
|
|
155
|
+
type "skos:Concept"
|
|
156
|
+
predicate :prefLabel, namespace: SkosNamespace, to: :name
|
|
157
|
+
predicate :notation, namespace: SkosNamespace, to: :code
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
----
|
|
161
|
+
|
|
162
|
+
See link:../_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
|
|
163
|
+
complete guide including graph-level serialization with `members`.
|
|
164
|
+
|
|
165
|
+
== Multi-format models
|
|
166
|
+
|
|
167
|
+
A single model can define `json`, `jsonld`, `turtle`, and `rdf` mappings
|
|
168
|
+
simultaneously. Each format operates independently:
|
|
169
|
+
|
|
170
|
+
[source,ruby]
|
|
171
|
+
----
|
|
172
|
+
class Concept < Lutaml::Model::Serializable
|
|
173
|
+
attribute :name, :string
|
|
174
|
+
attribute :code, :string
|
|
175
|
+
|
|
176
|
+
json do
|
|
177
|
+
map "name", to: :name
|
|
178
|
+
map "code", to: :code
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
rdf do
|
|
182
|
+
namespace Lutaml::Rdf::Namespaces::SkosNamespace
|
|
183
|
+
subject { |m| "http://example.org/#{m.code}" }
|
|
184
|
+
type "skos:Concept"
|
|
185
|
+
predicate :prefLabel, namespace: SkosNamespace, to: :name
|
|
186
|
+
predicate :notation, namespace: SkosNamespace, to: :code
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
----
|
|
190
|
+
|
|
191
|
+
* `to_json` produces plain JSON without `@context`
|
|
192
|
+
* `to_jsonld` produces JSON-LD with auto-generated `@context`, `@type`, `@id`
|
|
193
|
+
* `to_turtle` produces Turtle with `@prefix` declarations
|
|
194
|
+
|
|
195
|
+
The unified `rdf` block creates mappings for both `:turtle` and `:jsonld`
|
|
196
|
+
formats. If separate `jsonld do` or `turtle do` blocks are also defined, they
|
|
197
|
+
take precedence over the `rdf` block for their respective format.
|
|
198
|
+
|
|
199
|
+
== Error Handling
|
|
200
|
+
|
|
201
|
+
* `JSON::ParserError` — raised for malformed JSON input
|
|
202
|
+
* All parsing errors are wrapped in `Lutaml::Model::InvalidFormatError` by the
|
|
203
|
+
format pipeline
|
|
204
|
+
|
|
205
|
+
== Architecture
|
|
206
|
+
|
|
207
|
+
The JSON-LD format is composed of:
|
|
208
|
+
|
|
209
|
+
* `Lutaml::JsonLd::Adapter` — extends `KeyValue::Document`; parses JSON-LD
|
|
210
|
+
strings to hashes and serializes hashes back to JSON
|
|
211
|
+
* `Lutaml::JsonLd::Context` — DSL for building `@context` with prefixes,
|
|
212
|
+
vocab, terms, language, and base
|
|
213
|
+
* `Lutaml::JsonLd::TermDefinition` — value object for term definitions with
|
|
214
|
+
`@id`, `@type`, `@container`, `@language`, `@reverse`
|
|
215
|
+
* `Lutaml::JsonLd::Transform` — inherits from `Rdf::Transform`; auto-generates
|
|
216
|
+
`@context` from predicates, injects `@type`/`@id` on export, strips
|
|
217
|
+
`@`-keywords on import
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Unified RDF Serialization
|
|
3
|
+
nav_order: 17
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
= Unified RDF Serialization
|
|
7
|
+
|
|
8
|
+
Lutaml::Model provides a unified `rdf` DSL that defines RDF mappings once for
|
|
9
|
+
both JSON-LD and Turtle serialization. This follows the same principle as
|
|
10
|
+
`key_value do ... end` which serves JSON, YAML, and TOML from a single block.
|
|
11
|
+
|
|
12
|
+
Both JSON-LD and Turtle are RDF serialization formats representing the same
|
|
13
|
+
subject–predicate–object triples. The `rdf` DSL lets you define the mapping
|
|
14
|
+
once, and the format adapters handle syntax differences automatically.
|
|
15
|
+
|
|
16
|
+
== Setup
|
|
17
|
+
|
|
18
|
+
Add the `rdf-turtle` gem to your Gemfile (required for Turtle output):
|
|
19
|
+
|
|
20
|
+
[source,ruby]
|
|
21
|
+
----
|
|
22
|
+
gem "rdf-turtle", "~> 3.3"
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
No additional gem is needed for JSON-LD output.
|
|
26
|
+
|
|
27
|
+
== When to Use `rdf` vs Separate `jsonld`/`turtle` Blocks
|
|
28
|
+
|
|
29
|
+
Use `rdf do ... end` when:
|
|
30
|
+
* Your model maps to RDF resources using standard vocabularies (SKOS, DC, etc.)
|
|
31
|
+
* You need both JSON-LD and Turtle output from the same model
|
|
32
|
+
* You want predicate-based mapping with automatic `@context` generation
|
|
33
|
+
|
|
34
|
+
Use `jsonld do ... end` when:
|
|
35
|
+
* You need full control over the JSON-LD `@context` structure
|
|
36
|
+
* You are mapping to JSON properties that don't map to RDF predicates
|
|
37
|
+
|
|
38
|
+
Use `turtle do ... end` when:
|
|
39
|
+
* You only need Turtle output
|
|
40
|
+
* You need Turtle-specific features not available in the unified DSL
|
|
41
|
+
|
|
42
|
+
Both the unified `rdf` block and the format-specific blocks can coexist on the
|
|
43
|
+
same model. If both are defined, format-specific blocks take precedence.
|
|
44
|
+
|
|
45
|
+
== The `rdf` DSL
|
|
46
|
+
|
|
47
|
+
=== Basic Model
|
|
48
|
+
|
|
49
|
+
[source,ruby]
|
|
50
|
+
----
|
|
51
|
+
class Concept < Lutaml::Model::Serializable
|
|
52
|
+
attribute :code, :string
|
|
53
|
+
attribute :name, :string
|
|
54
|
+
|
|
55
|
+
rdf do
|
|
56
|
+
namespace SkosNamespace
|
|
57
|
+
|
|
58
|
+
subject { |m| "http://example.org/concept/#{m.code}" }
|
|
59
|
+
type "skos:Concept"
|
|
60
|
+
|
|
61
|
+
predicate :notation, namespace: SkosNamespace, to: :code
|
|
62
|
+
predicate :prefLabel, namespace: SkosNamespace, to: :name
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
----
|
|
66
|
+
|
|
67
|
+
This single `rdf` block creates mappings for both `:turtle` and `:jsonld`
|
|
68
|
+
formats automatically.
|
|
69
|
+
|
|
70
|
+
=== DSL Methods
|
|
71
|
+
|
|
72
|
+
==== namespace
|
|
73
|
+
|
|
74
|
+
Declares the RDF namespaces used by predicates. Accepts one or more
|
|
75
|
+
`Lutaml::Rdf::Namespace` subclass references:
|
|
76
|
+
|
|
77
|
+
[source,ruby]
|
|
78
|
+
----
|
|
79
|
+
namespace SkosNamespace, DctermsNamespace
|
|
80
|
+
----
|
|
81
|
+
|
|
82
|
+
In Turtle output, each namespace becomes an `@prefix` declaration. In JSON-LD
|
|
83
|
+
output, each becomes a prefix entry in `@context`.
|
|
84
|
+
|
|
85
|
+
==== subject
|
|
86
|
+
|
|
87
|
+
Required for top-level resources. A block that generates the subject URI from
|
|
88
|
+
the model instance:
|
|
89
|
+
|
|
90
|
+
[source,ruby]
|
|
91
|
+
----
|
|
92
|
+
subject { |m| "http://example.org/concept/#{m.code}" }
|
|
93
|
+
----
|
|
94
|
+
|
|
95
|
+
==== type
|
|
96
|
+
|
|
97
|
+
Sets the RDF type (`rdf:type`). Compact IRIs are resolved via declared
|
|
98
|
+
namespaces:
|
|
99
|
+
|
|
100
|
+
[source,ruby]
|
|
101
|
+
----
|
|
102
|
+
type "skos:Concept"
|
|
103
|
+
# resolves to <http://www.w3.org/2004/02/skos/core#Concept>
|
|
104
|
+
----
|
|
105
|
+
|
|
106
|
+
==== predicate
|
|
107
|
+
|
|
108
|
+
Each `predicate` creates a mapping between an RDF predicate and a model
|
|
109
|
+
attribute:
|
|
110
|
+
|
|
111
|
+
[source,ruby]
|
|
112
|
+
----
|
|
113
|
+
predicate :prefLabel, namespace: SkosNamespace, to: :name, lang_tagged: true
|
|
114
|
+
----
|
|
115
|
+
|
|
116
|
+
Parameters:
|
|
117
|
+
|
|
118
|
+
* `name` — the local name in the namespace (e.g., `:prefLabel`)
|
|
119
|
+
* `namespace:` — the `Lutaml::Rdf::Namespace` subclass (required)
|
|
120
|
+
* `to:` — the model attribute to read (required)
|
|
121
|
+
* `lang_tagged:` (default: `false`) — if true, values are serialized with
|
|
122
|
+
language tags (see <<language-tagged-values>>)
|
|
123
|
+
|
|
124
|
+
==== members
|
|
125
|
+
|
|
126
|
+
Declares that a container model contains member resources that should be
|
|
127
|
+
serialized as separate subjects in the output graph (see <<graph-serialization>>).
|
|
128
|
+
|
|
129
|
+
== Serialization
|
|
130
|
+
|
|
131
|
+
=== Turtle
|
|
132
|
+
|
|
133
|
+
[source,ruby]
|
|
134
|
+
----
|
|
135
|
+
concept = Concept.new(code: "2119", name: "component")
|
|
136
|
+
turtle = concept.to_turtle
|
|
137
|
+
----
|
|
138
|
+
|
|
139
|
+
Produces:
|
|
140
|
+
|
|
141
|
+
[source,turtle]
|
|
142
|
+
----
|
|
143
|
+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
144
|
+
|
|
145
|
+
<http://example.org/concept/2119> a skos:Concept;
|
|
146
|
+
skos:notation "2119";
|
|
147
|
+
skos:prefLabel "component" .
|
|
148
|
+
----
|
|
149
|
+
|
|
150
|
+
=== JSON-LD
|
|
151
|
+
|
|
152
|
+
[source,ruby]
|
|
153
|
+
----
|
|
154
|
+
jsonld = concept.to_jsonld
|
|
155
|
+
----
|
|
156
|
+
|
|
157
|
+
Produces:
|
|
158
|
+
|
|
159
|
+
[source,json]
|
|
160
|
+
----
|
|
161
|
+
{
|
|
162
|
+
"@context": {
|
|
163
|
+
"skos": "http://www.w3.org/2004/02/skos/core#",
|
|
164
|
+
"notation": "skos:notation",
|
|
165
|
+
"prefLabel": "skos:prefLabel"
|
|
166
|
+
},
|
|
167
|
+
"@type": "skos:Concept",
|
|
168
|
+
"@id": "http://example.org/concept/2119",
|
|
169
|
+
"notation": "2119",
|
|
170
|
+
"prefLabel": "component"
|
|
171
|
+
}
|
|
172
|
+
----
|
|
173
|
+
|
|
174
|
+
The `@context` is auto-generated from the declared namespaces and predicates.
|
|
175
|
+
No manual context definition is needed.
|
|
176
|
+
|
|
177
|
+
== [[language-tagged-values]]Language-Tagged Values
|
|
178
|
+
|
|
179
|
+
When a predicate is declared with `lang_tagged: true`, values are serialized
|
|
180
|
+
with language information:
|
|
181
|
+
|
|
182
|
+
**Turtle:**
|
|
183
|
+
```turtle
|
|
184
|
+
skos:prefLabel "component"@eng ;
|
|
185
|
+
skos:prefLabel "composant"@fra ;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**JSON-LD:**
|
|
189
|
+
```json
|
|
190
|
+
"prefLabel": { "eng": "component", "fra": "composant" }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
In JSON-LD, the `@context` auto-generates a language container:
|
|
194
|
+
```json
|
|
195
|
+
"prefLabel": { "@id": "skos:prefLabel", "@container": "@language" }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For this to work, the attribute's values must include the
|
|
199
|
+
`Lutaml::Rdf::LanguageTagged` module (or be a `Lutaml::Rdf::Literal`). A simple
|
|
200
|
+
value object is sufficient:
|
|
201
|
+
|
|
202
|
+
[source,ruby]
|
|
203
|
+
----
|
|
204
|
+
class LocalizedLiteral < Lutaml::Model::Serializable
|
|
205
|
+
attribute :value, :string
|
|
206
|
+
attribute :language_code, :string
|
|
207
|
+
end
|
|
208
|
+
----
|
|
209
|
+
|
|
210
|
+
== [[graph-serialization]]Graph Serialization
|
|
211
|
+
|
|
212
|
+
When a container model holds a collection of member resources, use `members` to
|
|
213
|
+
serialize them as separate subjects in the same RDF graph.
|
|
214
|
+
|
|
215
|
+
=== Container Model
|
|
216
|
+
|
|
217
|
+
[source,ruby]
|
|
218
|
+
----
|
|
219
|
+
class Vocabulary < Lutaml::Model::Serializable
|
|
220
|
+
attribute :id, :string
|
|
221
|
+
attribute :concepts, Concept, collection: true
|
|
222
|
+
|
|
223
|
+
rdf do
|
|
224
|
+
namespace SkosNamespace
|
|
225
|
+
|
|
226
|
+
subject { |v| "http://example.org/vocab/#{v.id}" }
|
|
227
|
+
type "skos:ConceptScheme"
|
|
228
|
+
predicate :prefLabel, namespace: SkosNamespace, to: :id
|
|
229
|
+
|
|
230
|
+
members :concepts
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
----
|
|
234
|
+
|
|
235
|
+
=== Turtle Output with Members
|
|
236
|
+
|
|
237
|
+
[source,ruby]
|
|
238
|
+
----
|
|
239
|
+
vocab = Vocabulary.new(id: "iso1087", concepts: [concept1, concept2])
|
|
240
|
+
puts vocab.to_turtle
|
|
241
|
+
----
|
|
242
|
+
|
|
243
|
+
Produces a single Turtle document with the container and all member triples:
|
|
244
|
+
|
|
245
|
+
[source,turtle]
|
|
246
|
+
----
|
|
247
|
+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
248
|
+
|
|
249
|
+
<http://example.org/vocab/iso1087> a skos:ConceptScheme;
|
|
250
|
+
skos:prefLabel "iso1087" .
|
|
251
|
+
|
|
252
|
+
<http://example.org/concept/2119> a skos:Concept;
|
|
253
|
+
skos:notation "2119";
|
|
254
|
+
skos:prefLabel "component" .
|
|
255
|
+
|
|
256
|
+
<http://example.org/concept/2120> a skos:Concept;
|
|
257
|
+
skos:notation "2120";
|
|
258
|
+
skos:prefLabel "intension" .
|
|
259
|
+
----
|
|
260
|
+
|
|
261
|
+
=== JSON-LD Output with Members
|
|
262
|
+
|
|
263
|
+
[source,ruby]
|
|
264
|
+
----
|
|
265
|
+
puts vocab.to_jsonld
|
|
266
|
+
----
|
|
267
|
+
|
|
268
|
+
Produces a JSON-LD document with `@graph` containing all resources:
|
|
269
|
+
|
|
270
|
+
[source,json]
|
|
271
|
+
----
|
|
272
|
+
{
|
|
273
|
+
"@context": {
|
|
274
|
+
"skos": "http://www.w3.org/2004/02/skos/core#",
|
|
275
|
+
"prefLabel": "skos:prefLabel",
|
|
276
|
+
"notation": "skos:notation"
|
|
277
|
+
},
|
|
278
|
+
"@graph": [
|
|
279
|
+
{
|
|
280
|
+
"@id": "http://example.org/vocab/iso1087",
|
|
281
|
+
"@type": "skos:ConceptScheme",
|
|
282
|
+
"prefLabel": "iso1087"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"@id": "http://example.org/concept/2119",
|
|
286
|
+
"@type": "skos:Concept",
|
|
287
|
+
"notation": "2119",
|
|
288
|
+
"prefLabel": "component"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"@id": "http://example.org/concept/2120",
|
|
292
|
+
"@type": "skos:Concept",
|
|
293
|
+
"notation": "2120",
|
|
294
|
+
"prefLabel": "intension"
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
----
|
|
299
|
+
|
|
300
|
+
=== Member-Only Models
|
|
301
|
+
|
|
302
|
+
A container model without a `subject` block serializes only the member triples.
|
|
303
|
+
This is useful when you don't need a container resource:
|
|
304
|
+
|
|
305
|
+
[source,ruby]
|
|
306
|
+
----
|
|
307
|
+
class MemberOnly < Lutaml::Model::Serializable
|
|
308
|
+
attribute :items, Concept, collection: true
|
|
309
|
+
|
|
310
|
+
rdf do
|
|
311
|
+
namespace SkosNamespace
|
|
312
|
+
members :items
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
----
|
|
316
|
+
|
|
317
|
+
=== Namespace Merging
|
|
318
|
+
|
|
319
|
+
When a container and its members declare different namespaces, all namespaces
|
|
320
|
+
are merged in the output. Prefix declarations in Turtle and `@context` entries
|
|
321
|
+
in JSON-LD include the union of all namespaces.
|
|
322
|
+
|
|
323
|
+
== Architecture
|
|
324
|
+
|
|
325
|
+
The unified RDF infrastructure consists of:
|
|
326
|
+
|
|
327
|
+
* `Lutaml::Rdf::Mapping` — Unified mapping base class with `namespace`,
|
|
328
|
+
`subject`, `type`, `predicate`, and `members` DSL methods
|
|
329
|
+
* `Lutaml::Rdf::MappingRule` — Value object for predicate-to-attribute mappings
|
|
330
|
+
* `Lutaml::Rdf::MemberRule` — Value object for `members` declarations
|
|
331
|
+
* `Lutaml::Rdf::Transform` — Base transform class with shared logic for subject
|
|
332
|
+
URI resolution, type resolution, and language extraction
|
|
333
|
+
* `Lutaml::Turtle::Transform` — Inherits from `Rdf::Transform`, produces Turtle
|
|
334
|
+
* `Lutaml::JsonLd::Transform` — Inherits from `Rdf::Transform`, produces JSON-LD
|
|
335
|
+
|
|
336
|
+
Both format-specific transforms detect `Rdf::Mapping` instances and dispatch to
|
|
337
|
+
unified serialization logic.
|
|
338
|
+
|
|
339
|
+
== Error Handling
|
|
340
|
+
|
|
341
|
+
* `Lutaml::Turtle::MissingSubjectError` — raised when a model with predicates
|
|
342
|
+
has no `subject` block and no `members` declaration
|
|
343
|
+
* `ArgumentError` — raised when a predicate's `namespace` is not a
|
|
344
|
+
`Rdf::Namespace` subclass
|