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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +5 -0
  3. data/.rubocop.yml +18 -0
  4. data/.rubocop_todo.yml +91 -22
  5. data/Gemfile +2 -0
  6. data/README.adoc +114 -2
  7. data/docs/_guides/index.adoc +18 -0
  8. data/docs/_guides/jsonld-serialization.adoc +217 -0
  9. data/docs/_guides/rdf-serialization.adoc +344 -0
  10. data/docs/_guides/turtle-serialization.adoc +224 -0
  11. data/docs/_migrations/0-8-0-namespace-restructuring.adoc +90 -0
  12. data/docs/_pages/serialization_adapters.adoc +31 -0
  13. data/docs/_references/index.adoc +1 -0
  14. data/docs/_references/rdf-namespaces.adoc +243 -0
  15. data/docs/index.adoc +3 -2
  16. data/lib/lutaml/jsonld/adapter.rb +23 -0
  17. data/lib/lutaml/jsonld/context.rb +69 -0
  18. data/lib/lutaml/jsonld/term_definition.rb +39 -0
  19. data/lib/lutaml/jsonld/transform.rb +174 -0
  20. data/lib/lutaml/jsonld.rb +23 -0
  21. data/lib/lutaml/model/format_registry.rb +10 -1
  22. data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
  23. data/lib/lutaml/model/version.rb +1 -1
  24. data/lib/lutaml/model.rb +6 -0
  25. data/lib/lutaml/rdf/error.rb +7 -0
  26. data/lib/lutaml/rdf/iri.rb +44 -0
  27. data/lib/lutaml/rdf/language_tagged.rb +11 -0
  28. data/lib/lutaml/rdf/literal.rb +62 -0
  29. data/lib/lutaml/rdf/mapping.rb +71 -0
  30. data/lib/lutaml/rdf/mapping_rule.rb +35 -0
  31. data/lib/lutaml/rdf/member_rule.rb +13 -0
  32. data/lib/lutaml/rdf/namespace.rb +58 -0
  33. data/lib/lutaml/rdf/namespace_set.rb +69 -0
  34. data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
  35. data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
  36. data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
  37. data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
  38. data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
  39. data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
  40. data/lib/lutaml/rdf/namespaces.rb +14 -0
  41. data/lib/lutaml/rdf/transform.rb +36 -0
  42. data/lib/lutaml/rdf.rb +19 -0
  43. data/lib/lutaml/turtle/adapter.rb +35 -0
  44. data/lib/lutaml/turtle/mapping.rb +7 -0
  45. data/lib/lutaml/turtle/transform.rb +158 -0
  46. data/lib/lutaml/turtle.rb +22 -0
  47. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -42
  48. data/lib/lutaml/xml/adapter/base_adapter.rb +48 -458
  49. data/lib/lutaml/xml/adapter/namespace_data.rb +0 -17
  50. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +71 -0
  51. data/lib/lutaml/xml/adapter/nokogiri_adapter.rb +5 -1110
  52. data/lib/lutaml/xml/adapter/oga_adapter.rb +6 -846
  53. data/lib/lutaml/xml/adapter/ox_adapter.rb +7 -884
  54. data/lib/lutaml/xml/adapter/plan_based_builder.rb +929 -0
  55. data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -864
  56. data/lib/lutaml/xml/adapter/xml_parser.rb +86 -0
  57. data/lib/lutaml/xml/adapter/xml_serializer.rb +291 -0
  58. data/lib/lutaml/xml/adapter.rb +0 -1
  59. data/lib/lutaml/xml/adapter_element.rb +7 -1
  60. data/lib/lutaml/xml/builder/base.rb +0 -1
  61. data/lib/lutaml/xml/data_model.rb +9 -1
  62. data/lib/lutaml/xml/document.rb +3 -1
  63. data/lib/lutaml/xml/element.rb +13 -10
  64. data/lib/lutaml/xml/serialization/format_conversion.rb +19 -42
  65. data/lib/lutaml/xml/serialization/instance_methods.rb +26 -35
  66. data/lib/lutaml/xml/transformation/custom_method_wrapper.rb +34 -55
  67. data/lib/lutaml/xml/transformation/rule_applier.rb +1 -1
  68. data/lib/lutaml/xml/xml_element.rb +24 -20
  69. data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
  70. data/spec/lutaml/integration/multi_format_spec.rb +106 -0
  71. data/spec/lutaml/integration/round_trip_spec.rb +170 -0
  72. data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
  73. data/spec/lutaml/jsonld/context_spec.rb +114 -0
  74. data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
  75. data/spec/lutaml/jsonld/transform_spec.rb +211 -0
  76. data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
  77. data/spec/lutaml/rdf/iri_spec.rb +73 -0
  78. data/spec/lutaml/rdf/literal_spec.rb +98 -0
  79. data/spec/lutaml/rdf/mapping_spec.rb +164 -0
  80. data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
  81. data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
  82. data/spec/lutaml/rdf/namespace_spec.rb +241 -0
  83. data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
  84. data/spec/lutaml/turtle/adapter_spec.rb +47 -0
  85. data/spec/lutaml/turtle/mapping_spec.rb +123 -0
  86. data/spec/lutaml/turtle/transform_spec.rb +273 -0
  87. data/spec/lutaml/xml/adapter/base_adapter_regression_spec.rb +151 -0
  88. data/spec/lutaml/xml/adapter/order_spec.rb +150 -0
  89. data/spec/lutaml/xml/clear_parse_state_spec.rb +139 -0
  90. data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +0 -2
  91. data/spec/lutaml/xml/schema/compiler_spec.rb +75 -69
  92. data/spec/lutaml/xml/transformation/custom_method_wrapper_spec.rb +213 -14
  93. metadata +58 -3
  94. data/lib/lutaml/xml/adapter/xml_serialization.rb +0 -145
@@ -0,0 +1,224 @@
1
+ ---
2
+ title: Turtle Serialization
3
+ nav_order: 16
4
+ ---
5
+
6
+ = Turtle Serialization
7
+
8
+ Lutaml::Model supports serialization to and from W3C RDF Turtle format using
9
+ SKOS, Dublin Core Terms, and other W3C vocabularies.
10
+
11
+ == Setup
12
+
13
+ Add the `rdf-turtle` gem to your Gemfile:
14
+
15
+ [source,ruby]
16
+ ----
17
+ gem "rdf-turtle", "~> 3.3"
18
+ ----
19
+
20
+ Turtle is a **non-key-value** format adapter with its own mapping DSL based on
21
+ RDF triples (subject–predicate–object). It is registered as a first-class
22
+ format in the `FormatRegistry` with dedicated `Mapping`, `Transform`, and
23
+ `Adapter` classes.
24
+
25
+ == Mapping DSL
26
+
27
+ Use the `turtle do` block in your model to define Turtle mappings:
28
+
29
+ [source,ruby]
30
+ ----
31
+ class Concept < Lutaml::Model::Serializable
32
+ attribute :name, :string
33
+ attribute :description, :string
34
+ attribute :code, :string
35
+
36
+ turtle do
37
+ namespace Lutaml::Rdf::Namespaces::SkosNamespace,
38
+ Lutaml::Rdf::Namespaces::DctermsNamespace
39
+
40
+ subject { |m| "http://example.org/concept/#{m.code}" }
41
+ type "skos:Concept"
42
+
43
+ predicate :prefLabel,
44
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
45
+ to: :name,
46
+ lang_tagged: true
47
+
48
+ predicate :definition,
49
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
50
+ to: :description
51
+
52
+ predicate :notation,
53
+ namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
54
+ to: :code
55
+ end
56
+ end
57
+ ----
58
+
59
+ === namespace
60
+
61
+ Declares the `@prefix` lines in the output. Accepts one or more
62
+ `Lutaml::Rdf::Namespace` subclass references. Internally builds a
63
+ `NamespaceSet` for O(1) prefix lookup and IRI resolution.
64
+
65
+ === subject
66
+
67
+ Required for top-level models with predicates and no `members`. A block that
68
+ generates the subject URI from the model instance. Raises
69
+ `Lutaml::Turtle::MissingSubjectError` if not defined when needed.
70
+
71
+ Member models (serialized via a container's `members` declaration) can omit the
72
+ `subject` block — blank nodes are used automatically.
73
+
74
+ === type
75
+
76
+ Sets the RDF type (`rdf:type`) triple. Compact IRIs (e.g., `"skos:Concept"`)
77
+ are resolved to full URIs via the declared namespaces. Full URIs (e.g.,
78
+ `"http://example.org/MyType"`) are used as-is.
79
+
80
+ === predicate
81
+
82
+ Each `predicate` creates a `Lutaml::Rdf::MappingRule` value object:
83
+
84
+ * `name` — the local name in the namespace (e.g., `:prefLabel`)
85
+ * `namespace:` — the `Lutaml::Rdf::Namespace` subclass (required, validated)
86
+ * `to:` — the model attribute to read/write (required)
87
+ * `lang_tagged:` (default: `false`) — if true, appends `@lang` suffix from the
88
+ value's `language_code` or `language` method
89
+
90
+ Validation errors:
91
+
92
+ * `namespace` must be a `Rdf::Namespace` subclass — raises `ArgumentError`
93
+ * `to:` is required — raises `ArgumentError`
94
+
95
+ == Serialization
96
+
97
+ [source,ruby]
98
+ ----
99
+ concept = Concept.new(name: "test", description: "A description", code: "2119")
100
+ turtle = concept.to_turtle
101
+ ----
102
+
103
+ Produces:
104
+
105
+ [source,turtle]
106
+ ----
107
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
108
+
109
+ <http://example.org/concept/2119> a skos:Concept;
110
+ skos:definition "A description";
111
+ skos:notation "2119";
112
+ skos:prefLabel "test" .
113
+ ----
114
+
115
+ The output uses the `RDF::Turtle::Writer` from the `rdf-turtle` gem, which
116
+ automatically compacts URIs using declared prefixes and uses native Turtle
117
+ syntax for typed literals.
118
+
119
+ === Typed values
120
+
121
+ Integer and boolean attributes are serialized using native Turtle literal
122
+ syntax:
123
+
124
+ * Integers → `42` (not `"42"^^xsd:integer`)
125
+ * Booleans → `true` / `false` (not `"true"^^xsd:boolean`)
126
+
127
+ === Collection attributes
128
+
129
+ When an attribute is defined with `collection: true`, each value produces a
130
+ separate triple with the same predicate. The writer may use comma-separated
131
+ object syntax:
132
+
133
+ [source,turtle]
134
+ ----
135
+ <http://example.org/1> skos:prefLabel "en", "fr" .
136
+ ----
137
+
138
+ == Special Characters
139
+
140
+ String values containing quotes, newlines, tabs, or backslashes are
141
+ automatically escaped. The `RDF::Turtle::Writer` uses triple-quoted strings
142
+ (`"""..."""`) for multi-line literals.
143
+
144
+ == Nil Values
145
+
146
+ Predicates for attributes with `nil` values are omitted from the output. If
147
+ all predicates produce no data, the result is an empty string.
148
+
149
+ == Deserialization
150
+
151
+ [source,ruby]
152
+ ----
153
+ concept = Concept.from_turtle(turtle_string)
154
+ puts concept.code # => "2119"
155
+ puts concept.description # => "A description"
156
+ ----
157
+
158
+ The `from_turtle` method:
159
+
160
+ . Parses the Turtle string into an `RDF::Graph` via `RDF::Turtle::Reader`
161
+ . Finds subjects by `rdf:type` matching the declared type
162
+ . Maps predicates back to model attributes using the declared predicate rules
163
+ . Converts RDF typed literals back to Ruby types (integers, booleans, etc.)
164
+
165
+ Language-tagged literals are extracted without the language tag.
166
+
167
+ == Round-trip
168
+
169
+ Model data round-trips through Turtle serialization:
170
+
171
+ [source,ruby]
172
+ ----
173
+ restored = Concept.from_turtle(concept.to_turtle)
174
+ restored.code == concept.code # => true
175
+ restored.description == concept.description # => true
176
+ ----
177
+
178
+ == Error Handling
179
+
180
+ * `Lutaml::Turtle::MissingSubjectError` — raised when serializing a model
181
+ without a `subject` block defined
182
+ * `RDF::ReaderError` — raised by the RDF parser for malformed Turtle input
183
+ * Both are wrapped in `Lutaml::Model::InvalidFormatError` by the format
184
+ pipeline
185
+
186
+ == Architecture
187
+
188
+ The Turtle format is composed of:
189
+
190
+ * `Lutaml::Turtle::Adapter` — parses Turtle strings to `RDF::Graph` and
191
+ serializes graphs back to Turtle
192
+ * `Lutaml::Turtle::Mapping` — empty subclass of `Rdf::Mapping`; inherits
193
+ `namespace`, `subject`, `type`, `predicate`, `members` DSL methods
194
+ * `Lutaml::Turtle::Transform` — inherits from `Rdf::Transform`; bidirectional
195
+ transform between model instances and RDF graphs via `model_to_data` /
196
+ `data_to_model`
197
+
198
+ == Unified `rdf` DSL
199
+
200
+ If you need both JSON-LD and Turtle output from the same model, use the
201
+ unified `rdf` DSL instead of separate `jsonld` and `turtle` blocks:
202
+
203
+ [source,ruby]
204
+ ----
205
+ class Concept < Lutaml::Model::Serializable
206
+ attribute :name, :string
207
+ attribute :code, :string
208
+
209
+ rdf do
210
+ namespace Lutaml::Rdf::Namespaces::SkosNamespace
211
+ subject { |m| "http://example.org/#{m.code}" }
212
+ type "skos:Concept"
213
+ predicate :prefLabel, namespace: SkosNamespace, to: :name
214
+ predicate :notation, namespace: SkosNamespace, to: :code
215
+ end
216
+ end
217
+ ----
218
+
219
+ The `rdf` block also supports `members` for graph-level serialization, where a
220
+ container model emits all member resources as separate subjects in the same
221
+ Turtle document.
222
+
223
+ See link:../_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
224
+ complete guide.
@@ -873,6 +873,96 @@ xml do
873
873
  end
874
874
  ----
875
875
 
876
+ == Custom XML Serialization Methods
877
+
878
+ Custom XML methods declared via `with: { from: :foo_from_xml, to: :foo_to_xml }` continue to work in v0.8.0, but the runtime objects passed to them have changed. This section covers the API differences and the migration steps needed.
879
+
880
+ === `doc` is now a `CustomMethodWrapper`
881
+
882
+ In v0.7.x the third argument of `def foo_to_xml(model, parent, doc)` was the underlying adapter document (Nokogiri-, Ox-, Oga-, REXML-backed). In v0.8.0 it is a `Lutaml::Xml::CustomMethodWrapper`, and `parent` is a `Lutaml::Xml::DataModel::XmlElement` rather than an adapter element. The wrapper exposes the same `create_element` / `add_text` / `add_attribute` / `add_element` surface, so straight-line code that uses only those methods keeps working.
883
+
884
+ === `add_element(parent, x)` raises `TypeError` for foreign element types
885
+
886
+ `doc.add_element(parent, x)` accepts two shapes for `x`:
887
+
888
+ * A `String`, parsed as an XML fragment via Moxml and grafted in as `DataModel::XmlElement` children. This is the path most existing custom methods rely on (e.g. passing the result of `instance.to_xml`).
889
+ * A `Lutaml::Xml::DataModel::XmlElement`, added directly as a child.
890
+
891
+ Anything else — most commonly a `Nokogiri::XML::Element` left over from a v0.7-style custom method that built fragments with `Nokogiri::XML::Builder` — now raises a `TypeError` with a message like:
892
+
893
+ add_element expects a String or XmlElement, got Nokogiri::XML::Element.
894
+ Call .to_xml on the element first.
895
+
896
+ ==== Fix
897
+
898
+ Serialize the foreign element to a string before handing it to the wrapper:
899
+
900
+ [source,ruby]
901
+ ----
902
+ # v0.7-era pattern (works against Nokogiri-backed adapter doc, raises in 0.8)
903
+ b.parent.elements.first.elements.each { |x| doc.add_element(parent, x) }
904
+
905
+ # v0.8 fix — pass the string fragment, takes the Moxml path
906
+ b.parent.elements.first.elements.each { |x| doc.add_element(parent, x.to_xml) }
907
+ ----
908
+
909
+ The same applies to single-element handoffs:
910
+
911
+ [source,ruby]
912
+ ----
913
+ # v0.7-era
914
+ elem = b.parent.elements.first
915
+ doc.add_element(parent, elem)
916
+
917
+ # v0.8 fix
918
+ elem = b.parent.elements.first.to_xml
919
+ doc.add_element(parent, elem)
920
+ ----
921
+
922
+ === Sibling walks: `parent.elements` → `current_context.children`
923
+
924
+ Custom methods that introspect the document under construction — for example, an idempotency guard that checks whether a sibling element has already been emitted — need to be translated. The v0.7 idiom
925
+
926
+ [source,ruby]
927
+ ----
928
+ def already_emitted?(doc)
929
+ doc.parent.elements.detect { |x| x.name == "foo" }
930
+ end
931
+ ----
932
+
933
+ becomes
934
+
935
+ [source,ruby]
936
+ ----
937
+ def already_emitted?(doc)
938
+ doc.current_context.children.detect { |x| x.name == "foo" }
939
+ end
940
+ ----
941
+
942
+ If you need to support both v0.7 and v0.8 from the same codebase, use a version constant rather than `respond_to?` checks:
943
+
944
+ [source,ruby]
945
+ ----
946
+ def already_emitted?(doc)
947
+ siblings = if defined?(Lutaml::VERSION) && Gem::Version.new(Lutaml::VERSION) >= Gem::Version.new("0.8")
948
+ doc.current_context.children
949
+ else
950
+ doc.parent.elements
951
+ end
952
+ siblings.detect { |x| x.name == "foo" }
953
+ end
954
+ ----
955
+
956
+ === Migration checklist for custom methods
957
+
958
+ Audit each `with: { to: :*_to_xml }` method for the following patterns:
959
+
960
+ . Does it call `Nokogiri::XML::Builder.new`, `Ox.dump`, or any other adapter-specific builder, then pass the resulting elements to `doc.add_element`? → Convert each element to a string via `.to_xml` (or equivalent) before passing.
961
+ . Does it walk `doc.parent.elements` / `doc.parent.children`? → Translate to `doc.current_context.children`.
962
+ . Does it cast `doc` to a specific adapter type, or call adapter-specific methods on it? → Replace with the wrapper API (`create_element`, `add_text`, `add_attribute`, `add_element`).
963
+
964
+ Test by round-tripping a representative fixture through `Model.from_xml(file).to_xml` and asserting equivalence. Foreign types will now raise `TypeError` rather than being silently dropped.
965
+
876
966
  == Testing Your Migration
877
967
 
878
968
  After updating, run your test suite:
@@ -25,6 +25,8 @@ LutaML, out of the box, supports the following serialization formats:
25
25
  * YAML (https://yaml.org/[YAML version 1.2])
26
26
  * JSON (https://www.ecma-international.org/publications-and-standards/standards/ecma-404/[ECMA-404 The JSON Data Interchange Standard], unofficial link: https://www.json.org[JSON])
27
27
  * TOML (https://toml.io/en[TOML version 1.0])
28
+ * JSON-LD (https://www.w3.org/TR/json-ld11/[W3C JSON-LD 1.1])
29
+ * Turtle (https://www.w3.org/TR/turtle/[W3C RDF 1.1 Turtle])
28
30
 
29
31
  The adapter interface is also used to support certain transformation of models
30
32
  into an "end format", which is not a serialization format. For example, the
@@ -309,6 +311,35 @@ end
309
311
  ----
310
312
 
311
313
 
314
+ === JSON-LD
315
+
316
+ Lutaml::Model supports serialization to and from JSON-LD (W3C JSON-LD 1.1).
317
+ JSON-LD is a key-value format that extends the built-in JSON serialization with
318
+ JSON-LD-specific constructs: `@context`, `@type`, and `@id`.
319
+
320
+ The adapter uses the standard Ruby `JSON` library internally — no additional gem
321
+ is required beyond `lutaml-model`.
322
+
323
+ See the link:../guides/jsonld-serialization[JSON-LD Serialization guide] for
324
+ mapping DSL details and usage examples.
325
+
326
+
327
+ === Turtle
328
+
329
+ Lutaml::Model supports serialization to and from W3C RDF Turtle format.
330
+ Turtle is a non-key-value format based on RDF triples with its own mapping DSL.
331
+
332
+ Requires the `rdf-turtle` gem:
333
+
334
+ [source,ruby]
335
+ ----
336
+ gem "rdf-turtle", "~> 3.3"
337
+ ----
338
+
339
+ See the link:../guides/turtle-serialization[Turtle Serialization guide] for
340
+ mapping DSL details and usage examples.
341
+
342
+
312
343
  === Error handling
313
344
 
314
345
  When parsing invalid serialization format data, an `InvalidFormatError` is
@@ -39,6 +39,7 @@ Deep dives into specific features:
39
39
 
40
40
  * link:../format-independent-features[Format-Independent Features] - Cross-format mechanisms
41
41
  * link:../parent-root-context[Parent and Root Context] - Model hierarchy navigation
42
+ * link:../rdf-namespaces[RDF Namespaces] - Namespace, NamespaceSet, Iri, Literal
42
43
 
43
44
  == See also
44
45
 
@@ -0,0 +1,243 @@
1
+ ---
2
+ title: RDF Namespaces
3
+ nav_order: 12
4
+ ---
5
+
6
+ = RDF Namespaces
7
+
8
+ Lutaml::Model provides a comprehensive RDF infrastructure with `Namespace`,
9
+ `NamespaceSet`, `Iri`, and `Literal` classes. These are shared by the JSON-LD
10
+ and Turtle format adapters.
11
+
12
+ == Namespace
13
+
14
+ `Lutaml::Rdf::Namespace` is the abstract base class for RDF namespace
15
+ vocabularies. Subclasses define `uri` and `prefix` at the class level:
16
+
17
+ [source,ruby]
18
+ ----
19
+ ns = Class.new(Lutaml::Rdf::Namespace)
20
+ ns.uri "http://example.org/ns/"
21
+ ns.prefix "ex"
22
+
23
+ ns["someName"] # => "http://example.org/ns/someName"
24
+ ns.prefixed("Name") # => "ex:Name"
25
+ ----
26
+
27
+ === Immutability
28
+
29
+ Once set, `uri` and `prefix` are frozen and cannot be changed:
30
+
31
+ [source,ruby]
32
+ ----
33
+ ns = Class.new(Lutaml::Rdf::Namespace)
34
+ ns.uri "http://example.org/ns/"
35
+ ns.uri "http://other.org/" # => raises FrozenError
36
+ ----
37
+
38
+ Each subclass has independent state — setting `uri` on one does not affect
39
+ another.
40
+
41
+ === Equality
42
+
43
+ Namespace classes implement equality based on `uri` and `prefix`:
44
+
45
+ [source,ruby]
46
+ ----
47
+ ns1 = Class.new(Lutaml::Rdf::Namespace)
48
+ ns1.uri "http://example.org/"
49
+ ns1.prefix "ex"
50
+
51
+ ns2 = Class.new(Lutaml::Rdf::Namespace)
52
+ ns2.uri "http://example.org/"
53
+ ns2.prefix "ex"
54
+
55
+ ns1 == ns2 # => true
56
+ ----
57
+
58
+ === Compact IRI resolution
59
+
60
+ [source,ruby]
61
+ ----
62
+ Lutaml::Rdf::Namespace.resolve_compact_iri("skos:Concept", [SkosNamespace])
63
+ # => "http://www.w3.org/2004/02/skos/core#Concept"
64
+
65
+ Lutaml::Rdf::Namespace.resolve_compact_iri("plain_name", [SkosNamespace])
66
+ # => "plain_name" (returned as-is when no colon present)
67
+ ----
68
+
69
+ == NamespaceSet
70
+
71
+ `Lutaml::Rdf::NamespaceSet` is an ordered, enumerable collection of namespace
72
+ classes with O(1) prefix lookup:
73
+
74
+ [source,ruby]
75
+ ----
76
+ set = Lutaml::Rdf::NamespaceSet.new(
77
+ Lutaml::Rdf::Namespaces::SkosNamespace,
78
+ Lutaml::Rdf::Namespaces::DctermsNamespace
79
+ )
80
+
81
+ set["skos"] # => SkosNamespace (O(1) lookup)
82
+ set.size # => 2
83
+ set.resolve_compact_iri("skos:Concept")
84
+ # => "http://www.w3.org/2004/02/skos/core#Concept"
85
+ set.compact("http://www.w3.org/2004/02/skos/core#Concept")
86
+ # => "skos:Concept"
87
+ set.to_hash
88
+ # => { "skos" => "http://www.w3.org/2004/02/skos/core#",
89
+ # "dcterms" => "http://purl.org/dc/terms/" }
90
+ ----
91
+
92
+ === Collision detection
93
+
94
+ Adding two different namespace classes with the same prefix raises an error:
95
+
96
+ [source,ruby]
97
+ ----
98
+ set = Lutaml::Rdf::NamespaceSet.new(SkosNamespace)
99
+ ns = Class.new(Lutaml::Rdf::Namespace)
100
+ ns.uri "http://other.org/"
101
+ ns.prefix "skos"
102
+ set.add(ns) # => raises ArgumentError: Prefix 'skos' conflicts
103
+ ----
104
+
105
+ Adding the same class twice is allowed (idempotent).
106
+
107
+ == Iri
108
+
109
+ `Lutaml::Rdf::Iri` is an immutable value object for IRIs:
110
+
111
+ [source,ruby]
112
+ ----
113
+ iri = Lutaml::Rdf::Iri.new("http://example.org/concept/1")
114
+ iri.to_s # => "http://example.org/concept/1"
115
+ iri.value # => "http://example.org/concept/1" (frozen)
116
+
117
+ # Expand compact → full
118
+ set = Lutaml::Rdf::NamespaceSet.new(SkosNamespace)
119
+ compact_iri = Lutaml::Rdf::Iri.new("skos:Concept")
120
+ compact_iri.expand(set) # => Iri("http://www.w3.org/2004/02/skos/core#Concept")
121
+
122
+ # Compact full → compact
123
+ full_iri = Lutaml::Rdf::Iri.new("http://www.w3.org/2004/02/skos/core#Concept")
124
+ full_iri.compact(set) # => "skos:Concept"
125
+ ----
126
+
127
+ Implements `Comparable` for sorting.
128
+
129
+ == Literal
130
+
131
+ `Lutaml::Rdf::Literal` is a value object for RDF literals with optional
132
+ datatype and language tag:
133
+
134
+ [source,ruby]
135
+ ----
136
+ # Plain literal
137
+ lit = Lutaml::Rdf::Literal.new("hello")
138
+ lit.to_turtle # => '"hello"'
139
+ lit.to_jsonld_term # => "hello"
140
+
141
+ # Language-tagged literal
142
+ lit = Lutaml::Rdf::Literal.new("hello", language: "en")
143
+ lit.to_turtle # => '"hello"@en'
144
+ lit.to_jsonld_term # => { "@value" => "hello", "@language" => "en" }
145
+
146
+ # Typed literal
147
+ lit = Lutaml::Rdf::Literal.new("42", datatype: "http://www.w3.org/2001/XMLSchema#integer")
148
+ lit.to_turtle # => '"42"^^<http://www.w3.org/2001/XMLSchema#integer>'
149
+ lit.to_jsonld_term # => { "@value" => "42", "@type" => "xsd:integer" }
150
+ ----
151
+
152
+ === Special character escaping
153
+
154
+ Quotes, newlines, tabs, and backslashes are automatically escaped in Turtle
155
+ and JSON-LD output.
156
+
157
+ == Standard W3C Namespaces
158
+
159
+ Pre-defined namespace classes are in `Lutaml::Rdf::Namespaces`:
160
+
161
+ [cols="1,1,1"]
162
+ |===
163
+ | Class | Prefix | URI
164
+
165
+ | `SkosNamespace` | `skos` | `http://www.w3.org/2004/02/skos/core#`
166
+ | `DctermsNamespace` | `dcterms` | `http://purl.org/dc/terms/`
167
+ | `RdfSyntaxNamespace` | `rdf` | `http://www.w3.org/1999/02/22-rdf-syntax-ns#`
168
+ | `RdfsNamespace` | `rdfs` | `http://www.w3.org/2000/01/rdf-schema#`
169
+ | `OwlNamespace` | `owl` | `http://www.w3.org/2002/07/owl#`
170
+ | `XsdNamespace` | `xsd` | `http://www.w3.org/2001/XMLSchema#`
171
+ |===
172
+
173
+ NOTE: `RdfNamespace` is a backward-compatible alias for `RdfSyntaxNamespace`.
174
+
175
+ == Custom Namespaces
176
+
177
+ Define your own by subclassing:
178
+
179
+ [source,ruby]
180
+ ----
181
+ class MyNamespace < Lutaml::Rdf::Namespace
182
+ uri "http://example.org/vocab/"
183
+ prefix "my"
184
+ end
185
+
186
+ MyNamespace["term"] # => "http://example.org/vocab/term"
187
+ ----
188
+
189
+ == Error hierarchy
190
+
191
+ [cols="1,1"]
192
+ |===
193
+ | Class | Description
194
+
195
+ | `Lutaml::Rdf::Error` | Base error for all RDF errors
196
+ | `Lutaml::Turtle::MissingSubjectError` | Raised when Turtle mapping has no `subject` block
197
+ |===
198
+
199
+ == Unified RDF Mapping
200
+
201
+ `Lutaml::Rdf::Mapping` is the unified mapping base class shared by both Turtle
202
+ and JSON-LD format adapters. It provides the `rdf` DSL for defining
203
+ predicate-based mappings once and using them for both output formats.
204
+
205
+ See link:../_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
206
+ complete guide.
207
+
208
+ === MappingRule
209
+
210
+ `Lutaml::Rdf::MappingRule` is a value object for predicate-to-attribute
211
+ mappings:
212
+
213
+ [source,ruby]
214
+ ----
215
+ # Created by the `predicate` DSL method
216
+ predicate :prefLabel, namespace: SkosNamespace, to: :name, lang_tagged: true
217
+ ----
218
+
219
+ Each rule stores: `predicate_name`, `namespace`, `to` (attribute symbol), and
220
+ `lang_tagged` flag.
221
+
222
+ === MemberRule
223
+
224
+ `Lutaml::Rdf::MemberRule` is a value object for the `members` declaration in
225
+ graph-level RDF mapping:
226
+
227
+ [source,ruby]
228
+ ----
229
+ # In a container model's rdf block
230
+ members :concepts
231
+ ----
232
+
233
+ Stores the attribute name referencing the collection of member models.
234
+
235
+ === Transform
236
+
237
+ `Lutaml::Rdf::Transform` is the base transform class for RDF serialization,
238
+ shared by both `Turtle::Transform` and `JsonLd::Transform`. It provides:
239
+
240
+ * `resolve_subject_uri(mapping, instance)` — evaluates the subject block
241
+ * `resolve_type_uri(mapping)` — resolves compact IRI to full URI
242
+ * `resolve_type_compact(mapping)` — returns the compact IRI form
243
+ * `extract_language(value)` — extracts language code from `LanguageTagged` values
data/docs/index.adoc CHANGED
@@ -11,13 +11,14 @@ image:https://img.shields.io/github/license/lutaml/lutaml-model.svg[License]
11
11
  image:https://github.com/lutaml/lutaml-model/actions/workflows/rake.yml/badge.svg["Build", link="https://github.com/lutaml/lutaml-model/actions/workflows/rake.yml"]
12
12
  image:https://github.com/lutaml/lutaml-model/actions/workflows/dependent-tests.yml/badge.svg["Dependent tests", link="https://github.com/lutaml/lutaml-model/actions/workflows/dependent-tests.yml"]
13
13
 
14
- Lutaml::Model is a Ruby library for creating information models with attributes and types, providing flexible and comprehensive mechanisms for serializing to and from multiple formats including XML, JSON, YAML, TOML, and Hash.
14
+ Lutaml::Model is a Ruby library for creating information models with attributes and types, providing flexible and comprehensive mechanisms for serializing to and from multiple formats including XML, JSON, YAML, TOML, JSON-LD, Turtle, and Hash.
15
15
 
16
16
  == Key features
17
17
 
18
18
  * **Model-driven design** - Define models with attributes and types
19
- * **Multi-format serialization** - XML, JSON, YAML, TOML, Hash, YAML Stream
19
+ * **Multi-format serialization** - XML, JSON, YAML, TOML, JSON-LD, Turtle, Hash, YAML Stream
20
20
  * **XML namespace support** - Full W3C namespace implementation
21
+ * **Linked Data support** - RDF namespaces, JSON-LD, and Turtle serialization
21
22
  * **Validation** - Built-in validation with custom rules
22
23
  * **Schema generation** - Generate XSD, JSON Schema, YAML Schema
23
24
  * **Polymorphism** - Handle multiple types elegantly
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Lutaml
6
+ module JsonLd
7
+ class Adapter < Lutaml::KeyValue::Document
8
+ def self.parse(jsonld_string, _options = {})
9
+ JSON.parse(jsonld_string, create_additions: false)
10
+ end
11
+
12
+ def to_jsonld(*args)
13
+ options = args.first || {}
14
+ data = @attributes
15
+ if options[:pretty]
16
+ JSON.pretty_generate(data, *args)
17
+ else
18
+ JSON.generate(data, *args)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end