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,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module JsonLd
|
|
5
|
+
class Context
|
|
6
|
+
attr_reader :prefixes, :terms
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@prefixes = {}
|
|
10
|
+
@terms = {}
|
|
11
|
+
@vocab = nil
|
|
12
|
+
@language = nil
|
|
13
|
+
@base = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def prefix(namespace_class)
|
|
17
|
+
@prefixes[namespace_class.prefix] = namespace_class.uri
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def vocab(uri = nil)
|
|
21
|
+
@vocab = uri if uri
|
|
22
|
+
@vocab
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def language(lang = nil)
|
|
26
|
+
@language = lang if lang
|
|
27
|
+
@language
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def base(uri = nil)
|
|
31
|
+
@base = uri if uri
|
|
32
|
+
@base
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def term(name, id: nil, type: nil, container: nil, language: nil,
|
|
36
|
+
reverse: false)
|
|
37
|
+
@terms[name] = TermDefinition.new(
|
|
38
|
+
name: name,
|
|
39
|
+
id: id,
|
|
40
|
+
type: type,
|
|
41
|
+
container: container,
|
|
42
|
+
language: language,
|
|
43
|
+
reverse: reverse,
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_hash
|
|
48
|
+
ctx = {}
|
|
49
|
+
ctx["@vocab"] = @vocab if @vocab
|
|
50
|
+
ctx["@language"] = @language if @language
|
|
51
|
+
ctx["@base"] = @base if @base
|
|
52
|
+
@prefixes.each { |pfx, uri| ctx[pfx] = uri }
|
|
53
|
+
@terms.each_value { |td| ctx.merge!(td.to_context_hash) }
|
|
54
|
+
ctx
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolve(term_name)
|
|
58
|
+
if term_name.include?(":")
|
|
59
|
+
pfx, local = term_name.split(":", 2)
|
|
60
|
+
"#{@prefixes[pfx]}#{local}" if @prefixes.key?(pfx)
|
|
61
|
+
elsif @terms.key?(term_name)
|
|
62
|
+
@terms[term_name].id
|
|
63
|
+
elsif @vocab
|
|
64
|
+
"#{@vocab}#{term_name}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module JsonLd
|
|
5
|
+
class TermDefinition
|
|
6
|
+
attr_reader :name, :id, :type, :container, :language, :reverse
|
|
7
|
+
|
|
8
|
+
def initialize(name:, id: nil, type: nil, container: nil, language: nil,
|
|
9
|
+
reverse: false)
|
|
10
|
+
@name = name
|
|
11
|
+
@id = id
|
|
12
|
+
@type = type
|
|
13
|
+
@container = container
|
|
14
|
+
@language = language
|
|
15
|
+
@reverse = reverse
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_context_hash
|
|
19
|
+
if simple_mapping?
|
|
20
|
+
{ @name => @id }
|
|
21
|
+
else
|
|
22
|
+
defn = {}
|
|
23
|
+
defn["@id"] = @id if @id
|
|
24
|
+
defn["@type"] = @type if @type
|
|
25
|
+
defn["@container"] = "@#{@container}" if @container
|
|
26
|
+
defn["@language"] = @language if @language
|
|
27
|
+
defn["@reverse"] = @reverse if @reverse
|
|
28
|
+
{ @name => defn }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def simple_mapping?
|
|
35
|
+
@id && @type.nil? && @container.nil? && @language.nil? && !@reverse
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module JsonLd
|
|
7
|
+
class Transform < Lutaml::Rdf::Transform
|
|
8
|
+
def model_to_data(instance, _format, options = {})
|
|
9
|
+
mapping = extract_mapping(options)
|
|
10
|
+
return {} unless mapping
|
|
11
|
+
|
|
12
|
+
if mapping.rdf_members.any?
|
|
13
|
+
build_graph_document(mapping, instance)
|
|
14
|
+
else
|
|
15
|
+
build_resource_object(mapping, instance)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def data_to_model(data, _format, options = {})
|
|
20
|
+
mapping = extract_mapping(options)
|
|
21
|
+
return model_class.new unless mapping
|
|
22
|
+
|
|
23
|
+
hash = data.is_a?(String) ? JSON.parse(data) : data
|
|
24
|
+
|
|
25
|
+
if hash.key?("@graph") && hash["@graph"].is_a?(Array) && !hash["@graph"].empty?
|
|
26
|
+
graph_data = hash["@graph"]
|
|
27
|
+
first = graph_data.first
|
|
28
|
+
hash = first.is_a?(Hash) ? first : {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
hash = strip_jsonld_keywords(hash)
|
|
32
|
+
|
|
33
|
+
attrs = {}
|
|
34
|
+
mapping.rdf_predicates.each do |rule|
|
|
35
|
+
value = hash[rule.predicate_name]
|
|
36
|
+
next if value.nil?
|
|
37
|
+
|
|
38
|
+
attrs[rule.to] = if rule.lang_tagged && value.is_a?(Hash)
|
|
39
|
+
flatten_language_map(value)
|
|
40
|
+
else
|
|
41
|
+
value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
build_instance(attrs, options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def extract_mapping(options)
|
|
51
|
+
options[:mappings] || mappings_for(:jsonld, lutaml_register)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_graph_document(mapping, instance)
|
|
55
|
+
context = build_merged_context(mapping, instance)
|
|
56
|
+
graph = []
|
|
57
|
+
|
|
58
|
+
if mapping.rdf_subject
|
|
59
|
+
resource = build_resource_data(mapping, instance)
|
|
60
|
+
graph << resource unless resource.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
mapping.rdf_members.each do |member_rule|
|
|
64
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
65
|
+
collection.each do |member|
|
|
66
|
+
member_mapping = member.class.mappings[:jsonld]
|
|
67
|
+
next unless member_mapping
|
|
68
|
+
|
|
69
|
+
resource = build_resource_data(member_mapping, member)
|
|
70
|
+
graph << resource unless resource.empty?
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
{ "@context" => context, "@graph" => graph }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_merged_context(mapping, instance)
|
|
78
|
+
context_hash = build_context_from_mapping(mapping).to_hash
|
|
79
|
+
|
|
80
|
+
mapping.rdf_members.each do |member_rule|
|
|
81
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
82
|
+
next if collection.empty?
|
|
83
|
+
|
|
84
|
+
member_mapping = collection.first.class.mappings[:jsonld]
|
|
85
|
+
next unless member_mapping
|
|
86
|
+
|
|
87
|
+
context_hash.merge!(build_context_from_mapping(member_mapping).to_hash)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context_hash
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_context_from_mapping(mapping)
|
|
94
|
+
context = Context.new
|
|
95
|
+
mapping.namespace_set.each { |ns| context.prefix(ns) }
|
|
96
|
+
mapping.rdf_predicates.each do |pred|
|
|
97
|
+
if pred.lang_tagged
|
|
98
|
+
context.term(pred.predicate_name,
|
|
99
|
+
id: pred.uri,
|
|
100
|
+
container: :language)
|
|
101
|
+
else
|
|
102
|
+
context.term(pred.predicate_name, id: pred.uri)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
context
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def build_resource_object(mapping, instance)
|
|
109
|
+
context = build_context_from_mapping(mapping).to_hash
|
|
110
|
+
data = build_resource_data(mapping, instance)
|
|
111
|
+
{ "@context" => context }.merge(data)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_resource_data(mapping, instance)
|
|
115
|
+
result = {}
|
|
116
|
+
|
|
117
|
+
if mapping.rdf_type
|
|
118
|
+
result["@type"] = resolve_type_compact(mapping)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if mapping.rdf_subject
|
|
122
|
+
result["@id"] = resolve_subject_uri(mapping, instance)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
mapping.rdf_predicates.each do |rule|
|
|
126
|
+
value = instance.public_send(rule.to)
|
|
127
|
+
next if value.nil?
|
|
128
|
+
next if value.is_a?(String) && value.empty?
|
|
129
|
+
|
|
130
|
+
result[rule.predicate_name] = if rule.lang_tagged
|
|
131
|
+
build_language_map(value)
|
|
132
|
+
else
|
|
133
|
+
serialize_rdf_value(value)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
result
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def build_language_map(values)
|
|
141
|
+
case values
|
|
142
|
+
when Array
|
|
143
|
+
map = {}
|
|
144
|
+
values.each do |v|
|
|
145
|
+
lang = extract_language(v)
|
|
146
|
+
map[lang] = v.to_s if lang
|
|
147
|
+
end
|
|
148
|
+
map.empty? ? nil : map
|
|
149
|
+
else
|
|
150
|
+
lang = extract_language(values)
|
|
151
|
+
lang ? { lang => values.to_s } : values.to_s
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def flatten_language_map(lang_map)
|
|
156
|
+
lang_map.values
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def serialize_rdf_value(value)
|
|
160
|
+
case value
|
|
161
|
+
when Array then value.map { |v| serialize_rdf_value(v) }
|
|
162
|
+
when Integer, Float, TrueClass, FalseClass then value
|
|
163
|
+
else value.to_s
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def strip_jsonld_keywords(data)
|
|
168
|
+
return data unless data.is_a?(Hash)
|
|
169
|
+
|
|
170
|
+
data.reject { |key, _| key.start_with?("@") }
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "model"
|
|
4
|
+
require_relative "rdf"
|
|
5
|
+
|
|
6
|
+
module Lutaml
|
|
7
|
+
module JsonLd
|
|
8
|
+
autoload :Context, "#{__dir__}/jsonld/context"
|
|
9
|
+
autoload :TermDefinition, "#{__dir__}/jsonld/term_definition"
|
|
10
|
+
autoload :Transform, "#{__dir__}/jsonld/transform"
|
|
11
|
+
autoload :Adapter, "#{__dir__}/jsonld/adapter"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Lutaml::Model::FormatRegistry.register(
|
|
16
|
+
:jsonld,
|
|
17
|
+
mapping_class: Lutaml::Rdf::Mapping,
|
|
18
|
+
adapter_class: Lutaml::JsonLd::Adapter,
|
|
19
|
+
transformer: Lutaml::JsonLd::Transform,
|
|
20
|
+
key_value: false,
|
|
21
|
+
rdf: true,
|
|
22
|
+
error_types: ["JSON::ParserError"],
|
|
23
|
+
)
|
|
@@ -23,7 +23,7 @@ module Lutaml
|
|
|
23
23
|
# @param adapter_options [Hash, nil] { available: [...], default: :name }
|
|
24
24
|
def register(format, mapping_class:, adapter_class:, transformer:,
|
|
25
25
|
adapter_loader: nil, castable_type: nil, key_value: nil,
|
|
26
|
-
error_types: nil, adapter_options: nil)
|
|
26
|
+
rdf: nil, error_types: nil, adapter_options: nil)
|
|
27
27
|
validate_registration!(format, mapping_class, transformer)
|
|
28
28
|
|
|
29
29
|
registered_formats[format] = {
|
|
@@ -33,6 +33,7 @@ module Lutaml
|
|
|
33
33
|
adapter_loader: adapter_loader,
|
|
34
34
|
castable_type: castable_type,
|
|
35
35
|
key_value: key_value,
|
|
36
|
+
rdf: rdf,
|
|
36
37
|
error_types: error_types,
|
|
37
38
|
adapter_options: adapter_options,
|
|
38
39
|
registered_at: Time.now,
|
|
@@ -109,6 +110,14 @@ module Lutaml
|
|
|
109
110
|
registered_formats.dig(format, :key_value) == true
|
|
110
111
|
end
|
|
111
112
|
|
|
113
|
+
def rdf_formats
|
|
114
|
+
registered_formats.select { |_, info| info[:rdf] }.keys
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def rdf?(format)
|
|
118
|
+
registered_formats.dig(format, :rdf) == true
|
|
119
|
+
end
|
|
120
|
+
|
|
112
121
|
def error_types_for(format)
|
|
113
122
|
registered_formats.dig(format, :error_types)
|
|
114
123
|
end
|
|
@@ -15,7 +15,12 @@ module Lutaml
|
|
|
15
15
|
# @param block [Proc] The DSL block to evaluate
|
|
16
16
|
def process_mapping(format, *_args, &)
|
|
17
17
|
klass = ::Lutaml::Model::Config.mappings_class_for(format)
|
|
18
|
-
mappings[format]
|
|
18
|
+
existing = mappings[format]
|
|
19
|
+
mappings[format] = if existing.nil? || !existing.is_a?(klass)
|
|
20
|
+
klass.new
|
|
21
|
+
else
|
|
22
|
+
existing
|
|
23
|
+
end
|
|
19
24
|
mappings[format].instance_eval(&)
|
|
20
25
|
|
|
21
26
|
if mappings[format].respond_to?(:finalize)
|
|
@@ -273,6 +278,17 @@ module Lutaml
|
|
|
273
278
|
end
|
|
274
279
|
end
|
|
275
280
|
|
|
281
|
+
# Define RDF mappings for multiple formats (Turtle, JSON-LD, etc.).
|
|
282
|
+
# Discovers RDF formats dynamically from FormatRegistry.
|
|
283
|
+
#
|
|
284
|
+
# @param block [Proc] The DSL block
|
|
285
|
+
def rdf(&block)
|
|
286
|
+
Lutaml::Model::FormatRegistry.rdf_formats.each do |format|
|
|
287
|
+
mappings[format] = Lutaml::Rdf::Mapping.new
|
|
288
|
+
mappings[format].instance_eval(&block)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
276
292
|
# Get resolved mapping for a format
|
|
277
293
|
#
|
|
278
294
|
# Delegates to TransformationRegistry for centralized caching
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/model.rb
CHANGED
|
@@ -10,6 +10,8 @@ module Lutaml
|
|
|
10
10
|
autoload :Jsonl, "#{__dir__}/jsonl"
|
|
11
11
|
autoload :Yamls, "#{__dir__}/yamls"
|
|
12
12
|
autoload :Xml, "#{__dir__}/xml"
|
|
13
|
+
autoload :JsonLd, "#{__dir__}/jsonld"
|
|
14
|
+
autoload :Turtle, "#{__dir__}/turtle"
|
|
13
15
|
|
|
14
16
|
module Model
|
|
15
17
|
# Autoloads for lazy loading - set up BEFORE any requires
|
|
@@ -241,6 +243,10 @@ require "#{__dir__}/jsonl"
|
|
|
241
243
|
require "#{__dir__}/yamls"
|
|
242
244
|
require "#{__dir__}/xml"
|
|
243
245
|
|
|
246
|
+
# Optional formats: require "lutaml/jsonld" or "lutaml/turtle" to enable.
|
|
247
|
+
# These are not eagerly loaded because they depend on optional gems
|
|
248
|
+
# (e.g., rdf-turtle) that most users don't need.
|
|
249
|
+
|
|
244
250
|
# Prepend builder interface into Serialize
|
|
245
251
|
# Builder must be prepended AFTER XML so its initialize runs first
|
|
246
252
|
# (Builder -> XML InstanceMethods -> Serialize)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class Iri
|
|
6
|
+
include Comparable
|
|
7
|
+
|
|
8
|
+
attr_reader :value
|
|
9
|
+
|
|
10
|
+
def initialize(uri_string)
|
|
11
|
+
@value = uri_string.to_s.freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def expand(namespace_set)
|
|
15
|
+
namespace_set.resolve_compact_iri(value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def compact(namespace_set)
|
|
19
|
+
namespace_set.compact(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def <=>(other)
|
|
23
|
+
other.is_a?(self.class) ? value <=> other.value : nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.is_a?(self.class) && value == other.value
|
|
28
|
+
end
|
|
29
|
+
alias_method :eql?, :==
|
|
30
|
+
|
|
31
|
+
def hash
|
|
32
|
+
value.hash
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def inspect
|
|
40
|
+
"#<#{self.class.name} #{value}>"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class Literal
|
|
6
|
+
include LanguageTagged
|
|
7
|
+
|
|
8
|
+
attr_reader :value, :datatype, :language
|
|
9
|
+
|
|
10
|
+
def initialize(value, datatype: nil, language: nil)
|
|
11
|
+
@value = value
|
|
12
|
+
@datatype = datatype
|
|
13
|
+
@language = language
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_turtle
|
|
17
|
+
escaped = escape_turtle(value.to_s)
|
|
18
|
+
if language
|
|
19
|
+
"#{escaped}@#{language}"
|
|
20
|
+
elsif datatype
|
|
21
|
+
"#{escaped}^^<#{datatype}>"
|
|
22
|
+
else
|
|
23
|
+
escaped
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_jsonld_term
|
|
28
|
+
if language
|
|
29
|
+
{ "@value" => value, "@language" => language }
|
|
30
|
+
elsif datatype
|
|
31
|
+
{ "@value" => value, "@type" => datatype.to_s }
|
|
32
|
+
else
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def ==(other)
|
|
38
|
+
other.is_a?(self.class) &&
|
|
39
|
+
value == other.value &&
|
|
40
|
+
datatype == other.datatype &&
|
|
41
|
+
language == other.language
|
|
42
|
+
end
|
|
43
|
+
alias_method :eql?, :==
|
|
44
|
+
|
|
45
|
+
def hash
|
|
46
|
+
[value, datatype, language].hash
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def escape_turtle(str)
|
|
52
|
+
escaped = str.gsub(/[\n\r\t"\\]/,
|
|
53
|
+
"\\" => "\\\\",
|
|
54
|
+
'"' => "\\\"",
|
|
55
|
+
"\n" => "\\n",
|
|
56
|
+
"\r" => "\\r",
|
|
57
|
+
"\t" => "\\t")
|
|
58
|
+
"\"#{escaped}\""
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class Mapping < Lutaml::Model::Mapping
|
|
6
|
+
attr_reader :namespace_set, :rdf_subject, :rdf_type, :rdf_predicates,
|
|
7
|
+
:rdf_members
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
super
|
|
11
|
+
@namespace_set = Lutaml::Rdf::NamespaceSet.new
|
|
12
|
+
@rdf_subject = nil
|
|
13
|
+
@rdf_type = nil
|
|
14
|
+
@rdf_predicates = []
|
|
15
|
+
@rdf_members = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def namespace(*namespace_classes)
|
|
19
|
+
@namespace_set = Lutaml::Rdf::NamespaceSet.new(*namespace_classes)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def subject(&)
|
|
23
|
+
@rdf_subject = Proc.new(&) if block_given?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def type(value)
|
|
27
|
+
@rdf_type = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def predicate(name, namespace:, to:, lang_tagged: false)
|
|
31
|
+
@rdf_predicates << Lutaml::Rdf::MappingRule.new(
|
|
32
|
+
name,
|
|
33
|
+
namespace: namespace,
|
|
34
|
+
to: to,
|
|
35
|
+
lang_tagged: lang_tagged,
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def members(attr_name)
|
|
40
|
+
@rdf_members << Lutaml::Rdf::MemberRule.new(attr_name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mappings(_register_id = nil)
|
|
44
|
+
@rdf_predicates
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def finalize(_mapper_class); end
|
|
48
|
+
|
|
49
|
+
def finalized?
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def map_element(name, to:)
|
|
54
|
+
raise Lutaml::Model::IncorrectMappingArgumentsError,
|
|
55
|
+
"RDF mappings use `predicate` instead of `map_element`. " \
|
|
56
|
+
"Use `predicate :#{name}, namespace: MyNs, to: :#{to}` inside `rdf do`."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def deep_dup
|
|
60
|
+
self.class.new.tap do |new_mapping|
|
|
61
|
+
new_mapping.instance_variable_set(:@namespace_set, @namespace_set)
|
|
62
|
+
new_mapping.instance_variable_set(:@rdf_subject, @rdf_subject)
|
|
63
|
+
new_mapping.instance_variable_set(:@rdf_type, @rdf_type)
|
|
64
|
+
new_mapping.instance_variable_set(:@rdf_predicates,
|
|
65
|
+
@rdf_predicates.dup)
|
|
66
|
+
new_mapping.instance_variable_set(:@rdf_members, @rdf_members.dup)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class MappingRule
|
|
6
|
+
attr_reader :predicate_name, :namespace, :to, :lang_tagged
|
|
7
|
+
|
|
8
|
+
def initialize(predicate_name, namespace:, to:, lang_tagged: false)
|
|
9
|
+
validate!(predicate_name, namespace, to)
|
|
10
|
+
@predicate_name = predicate_name.to_s.freeze
|
|
11
|
+
@namespace = namespace
|
|
12
|
+
@to = to
|
|
13
|
+
@lang_tagged = lang_tagged
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def uri
|
|
17
|
+
@namespace[@predicate_name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prefixed_name
|
|
21
|
+
@namespace.prefixed(@predicate_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def validate!(name, namespace_class, target)
|
|
27
|
+
raise ArgumentError, "predicate_name is required" unless name
|
|
28
|
+
unless namespace_class.is_a?(Class) && namespace_class < Lutaml::Rdf::Namespace
|
|
29
|
+
raise ArgumentError, "namespace must be a Rdf::Namespace subclass"
|
|
30
|
+
end
|
|
31
|
+
raise ArgumentError, ":to is required" unless target
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|