lutaml-model 0.8.3 → 0.8.5
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 +3 -1
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +16 -22
- data/Gemfile +2 -0
- data/README.adoc +327 -3
- data/docs/_guides/document-validation.adoc +303 -0
- data/docs/_guides/index.adoc +19 -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/_guides/xml-mapping.adoc +9 -1
- data/docs/_guides/xml_mappings/07_best_practices.adoc +36 -0
- data/docs/_guides/xml_mappings/08_troubleshooting.adoc +89 -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/_tutorials/lutaml-xml-architecture.adoc +6 -1
- 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/attribute.rb +19 -1
- data/lib/lutaml/model/error/liquid_drop_already_registered_error.rb +11 -0
- data/lib/lutaml/model/error/ordered_content_mapping_error.rb +17 -0
- data/lib/lutaml/model/format_registry.rb +10 -1
- data/lib/lutaml/model/global_context.rb +1 -0
- data/lib/lutaml/model/liquefiable.rb +12 -15
- data/lib/lutaml/model/mapping/mapping_rule.rb +10 -2
- data/lib/lutaml/model/mapping_hash.rb +1 -1
- data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
- data/lib/lutaml/model/services/transformer.rb +67 -32
- data/lib/lutaml/model/transform.rb +41 -4
- data/lib/lutaml/model/uninitialized_class.rb +11 -5
- data/lib/lutaml/model/validation/concerns/has_issues.rb +27 -0
- data/lib/lutaml/model/validation/context.rb +36 -0
- data/lib/lutaml/model/validation/issue.rb +62 -0
- data/lib/lutaml/model/validation/layer_result.rb +34 -0
- data/lib/lutaml/model/validation/profile.rb +66 -0
- data/lib/lutaml/model/validation/registry.rb +60 -0
- data/lib/lutaml/model/validation/remediation.rb +33 -0
- data/lib/lutaml/model/validation/remediation_result.rb +20 -0
- data/lib/lutaml/model/validation/report.rb +39 -0
- data/lib/lutaml/model/validation/rule.rb +59 -0
- data/lib/lutaml/model/validation.rb +2 -1
- data/lib/lutaml/model/validation_framework.rb +77 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +10 -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/nokogiri_adapter.rb +9 -2
- data/lib/lutaml/xml/adapter/oga_adapter.rb +11 -3
- data/lib/lutaml/xml/adapter/ox_adapter.rb +5 -2
- data/lib/lutaml/xml/adapter/rexml_adapter.rb +10 -3
- data/lib/lutaml/xml/adapter_element.rb +26 -2
- data/lib/lutaml/xml/data_model.rb +14 -0
- data/lib/lutaml/xml/document.rb +3 -0
- data/lib/lutaml/xml/element.rb +8 -2
- data/lib/lutaml/xml/mapping.rb +9 -0
- data/lib/lutaml/xml/model_transform.rb +42 -0
- data/lib/lutaml/xml/schema/xsd/base.rb +4 -1
- data/lib/lutaml/xml/serialization/instance_methods.rb +3 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +46 -2
- data/lib/lutaml/xml/transformation.rb +40 -1
- data/lib/lutaml/xml/xml_element.rb +8 -7
- data/lutaml-model.gemspec +1 -1
- 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/model/attribute_default_cache_spec.rb +58 -0
- data/spec/lutaml/model/liquefiable_spec.rb +22 -6
- data/spec/lutaml/model/liquid_compatibility_spec.rb +442 -0
- data/spec/lutaml/model/ordered_content_spec.rb +5 -5
- data/spec/lutaml/model/services/transformer_spec.rb +43 -0
- data/spec/lutaml/model/transform_cache_spec.rb +62 -0
- data/spec/lutaml/model/transform_dynamic_attributes_spec.rb +41 -0
- data/spec/lutaml/model/uninitialized_class_deep_dup_spec.rb +39 -0
- data/spec/lutaml/model/uninitialized_class_spec.rb +14 -2
- data/spec/lutaml/model/validation/concerns/has_issues_spec.rb +76 -0
- data/spec/lutaml/model/validation/context_spec.rb +60 -0
- data/spec/lutaml/model/validation/issue_spec.rb +77 -0
- data/spec/lutaml/model/validation/layer_result_spec.rb +66 -0
- data/spec/lutaml/model/validation/profile_spec.rb +134 -0
- data/spec/lutaml/model/validation/registry_spec.rb +94 -0
- data/spec/lutaml/model/validation/remediation_result_spec.rb +23 -0
- data/spec/lutaml/model/validation/remediation_spec.rb +72 -0
- data/spec/lutaml/model/validation/report_spec.rb +58 -0
- data/spec/lutaml/model/validation/rule_spec.rb +134 -0
- data/spec/lutaml/model/validation/uninitialized_class_validate_spec.rb +29 -0
- data/spec/lutaml/model/validation/validation_error_spec.rb +29 -0
- data/spec/lutaml/model/validation/validation_framework_spec.rb +110 -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/content_model_validation_spec.rb +157 -0
- data/spec/lutaml/xml/mapping_spec.rb +12 -7
- metadata +95 -7
- data/spec/fixtures/liquid_templates/_ceramics.liquid +0 -3
|
@@ -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
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class Namespace
|
|
6
|
+
class << self
|
|
7
|
+
def uri(value = nil)
|
|
8
|
+
return @uri if value.nil?
|
|
9
|
+
if @uri
|
|
10
|
+
raise FrozenError,
|
|
11
|
+
"#{name}.uri is already set to #{@uri.inspect}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@uri = value.freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def prefix(value = nil)
|
|
18
|
+
return @prefix if value.nil?
|
|
19
|
+
if @prefix
|
|
20
|
+
raise FrozenError,
|
|
21
|
+
"#{name}.prefix is already set to #{@prefix.inspect}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@prefix = value.to_s.freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def [](local_name)
|
|
28
|
+
"#{uri}#{local_name}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def prefixed(local_name)
|
|
32
|
+
"#{prefix}:#{local_name}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_compact_iri(compact_iri, namespaces)
|
|
36
|
+
return compact_iri unless compact_iri.include?(":")
|
|
37
|
+
|
|
38
|
+
pfx, local = compact_iri.split(":", 2)
|
|
39
|
+
ns = namespaces.find { |n| n.prefix == pfx }
|
|
40
|
+
ns ? ns[local] : compact_iri
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ==(other)
|
|
44
|
+
other.is_a?(Class) && other < Namespace &&
|
|
45
|
+
other.uri == uri && other.prefix == prefix
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def hash
|
|
49
|
+
[uri, prefix].hash
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_s
|
|
53
|
+
"#{name}(prefix: #{prefix.inspect}, uri: #{uri.inspect})"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class NamespaceSet
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
def initialize(*namespace_classes)
|
|
9
|
+
@by_prefix = {}
|
|
10
|
+
namespace_classes.each { |ns| add(ns) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add(namespace_class)
|
|
14
|
+
pfx = namespace_class.prefix
|
|
15
|
+
if @by_prefix.key?(pfx) && @by_prefix[pfx] != namespace_class
|
|
16
|
+
raise ArgumentError,
|
|
17
|
+
"Prefix '#{pfx}' conflicts: #{@by_prefix[pfx].name} vs #{namespace_class.name}"
|
|
18
|
+
end
|
|
19
|
+
@by_prefix[pfx] = namespace_class
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def [](prefix)
|
|
24
|
+
@by_prefix[prefix]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def resolve_compact_iri(compact_iri)
|
|
28
|
+
Namespace.resolve_compact_iri(compact_iri, to_a)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def compact(full_uri)
|
|
32
|
+
each do |ns|
|
|
33
|
+
next unless full_uri.start_with?(ns.uri)
|
|
34
|
+
|
|
35
|
+
local = full_uri.delete_prefix(ns.uri)
|
|
36
|
+
return ns.prefixed(local)
|
|
37
|
+
end
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def each(&)
|
|
42
|
+
@by_prefix.each_value(&)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def size
|
|
46
|
+
@by_prefix.size
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def empty?
|
|
50
|
+
@by_prefix.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_a
|
|
54
|
+
@by_prefix.values
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_hash
|
|
58
|
+
@by_prefix.transform_values(&:uri)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def merge(other_set)
|
|
62
|
+
return self if equal?(other_set)
|
|
63
|
+
|
|
64
|
+
other_set.each { |ns| add(ns) }
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
module Namespaces
|
|
6
|
+
class RdfSyntaxNamespace < Lutaml::Rdf::Namespace
|
|
7
|
+
uri "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
8
|
+
prefix "rdf"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
RdfNamespace = RdfSyntaxNamespace
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
module Namespaces
|
|
6
|
+
autoload :SkosNamespace, "#{__dir__}/namespaces/skos_namespace"
|
|
7
|
+
autoload :DctermsNamespace, "#{__dir__}/namespaces/dcterms_namespace"
|
|
8
|
+
autoload :RdfNamespace, "#{__dir__}/namespaces/rdf_namespace"
|
|
9
|
+
autoload :RdfsNamespace, "#{__dir__}/namespaces/rdfs_namespace"
|
|
10
|
+
autoload :OwlNamespace, "#{__dir__}/namespaces/owl_namespace"
|
|
11
|
+
autoload :XsdNamespace, "#{__dir__}/namespaces/xsd_namespace"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Rdf
|
|
5
|
+
class Transform < Lutaml::Model::Transform
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def resolve_subject_uri(mapping, instance)
|
|
9
|
+
mapping.rdf_subject&.call(instance)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def resolve_type_uri(mapping)
|
|
13
|
+
return unless mapping.rdf_type
|
|
14
|
+
|
|
15
|
+
mapping.namespace_set.resolve_compact_iri(mapping.rdf_type)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def resolve_type_compact(mapping)
|
|
19
|
+
mapping.rdf_type
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_instance(attrs, options)
|
|
23
|
+
child_register = Lutaml::Model::Register.resolve_for_child(
|
|
24
|
+
model_class, lutaml_register
|
|
25
|
+
)
|
|
26
|
+
instance = model_class.new(attrs.merge(lutaml_register: child_register))
|
|
27
|
+
root_and_parent_assignment(instance, options)
|
|
28
|
+
instance
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def extract_language(value)
|
|
32
|
+
value.language_tag if value.is_a?(Lutaml::Rdf::LanguageTagged)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/lutaml/rdf.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "model"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Rdf
|
|
7
|
+
autoload :Error, "#{__dir__}/rdf/error"
|
|
8
|
+
autoload :Iri, "#{__dir__}/rdf/iri"
|
|
9
|
+
autoload :LanguageTagged, "#{__dir__}/rdf/language_tagged"
|
|
10
|
+
autoload :Literal, "#{__dir__}/rdf/literal"
|
|
11
|
+
autoload :Namespace, "#{__dir__}/rdf/namespace"
|
|
12
|
+
autoload :NamespaceSet, "#{__dir__}/rdf/namespace_set"
|
|
13
|
+
autoload :Mapping, "#{__dir__}/rdf/mapping"
|
|
14
|
+
autoload :MappingRule, "#{__dir__}/rdf/mapping_rule"
|
|
15
|
+
autoload :MemberRule, "#{__dir__}/rdf/member_rule"
|
|
16
|
+
autoload :Namespaces, "#{__dir__}/rdf/namespaces"
|
|
17
|
+
autoload :Transform, "#{__dir__}/rdf/transform"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Turtle
|
|
5
|
+
class Adapter
|
|
6
|
+
attr_reader :data, :register
|
|
7
|
+
|
|
8
|
+
def initialize(data, register: nil)
|
|
9
|
+
@data = data
|
|
10
|
+
@register = register || Lutaml::Model::Config.default_register
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.parse(turtle_string, _options = {})
|
|
14
|
+
require "rdf/turtle"
|
|
15
|
+
graph = RDF::Graph.new
|
|
16
|
+
RDF::Turtle::Reader.new(turtle_string).each_statement do |stmt|
|
|
17
|
+
graph << stmt
|
|
18
|
+
end
|
|
19
|
+
graph
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_turtle(_options = {})
|
|
23
|
+
require "rdf/turtle"
|
|
24
|
+
case data
|
|
25
|
+
when String
|
|
26
|
+
data
|
|
27
|
+
when RDF::Enumerable
|
|
28
|
+
RDF::Turtle::Writer.buffer { |w| data.each_statement { |s| w << s } }
|
|
29
|
+
else
|
|
30
|
+
data.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Turtle
|
|
5
|
+
class MissingSubjectError < Lutaml::Rdf::Error; end
|
|
6
|
+
|
|
7
|
+
class Transform < Lutaml::Rdf::Transform
|
|
8
|
+
def model_to_data(instance, _format, options = {})
|
|
9
|
+
require "rdf/turtle"
|
|
10
|
+
mapping = extract_turtle_mapping(options)
|
|
11
|
+
return "" unless mapping
|
|
12
|
+
|
|
13
|
+
if !mapping.rdf_subject && mapping.rdf_predicates.any? && mapping.rdf_members.empty?
|
|
14
|
+
raise MissingSubjectError,
|
|
15
|
+
"Turtle mapping requires a subject block"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
graph = build_graph(mapping, instance)
|
|
19
|
+
return "" if graph.empty?
|
|
20
|
+
|
|
21
|
+
prefixes = build_prefixes(mapping, instance)
|
|
22
|
+
RDF::Turtle::Writer.buffer(prefixes: prefixes) do |writer|
|
|
23
|
+
graph.each_statement { |stmt| writer << stmt }
|
|
24
|
+
end.strip
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def data_to_model(data, _format, options = {})
|
|
28
|
+
require "rdf/turtle"
|
|
29
|
+
mapping = extract_turtle_mapping(options)
|
|
30
|
+
unless mapping&.rdf_subject
|
|
31
|
+
raise MissingSubjectError,
|
|
32
|
+
"Turtle mapping requires a subject block"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
graph = data.is_a?(RDF::Graph) ? data : Lutaml::Turtle::Adapter.parse(data)
|
|
36
|
+
attrs = extract_attributes(graph, mapping)
|
|
37
|
+
build_instance(attrs, options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def extract_turtle_mapping(options)
|
|
43
|
+
options[:mappings] || mappings_for(:turtle, lutaml_register)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build_graph(mapping, instance)
|
|
47
|
+
graph = RDF::Graph.new
|
|
48
|
+
|
|
49
|
+
has_predicates_or_type = mapping.rdf_type || mapping.rdf_predicates.any?
|
|
50
|
+
|
|
51
|
+
if has_predicates_or_type
|
|
52
|
+
subject_uri = if mapping.rdf_subject
|
|
53
|
+
RDF::URI(resolve_subject_uri(mapping, instance))
|
|
54
|
+
else
|
|
55
|
+
RDF::Node.new
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if mapping.rdf_type
|
|
59
|
+
type_uri = RDF::URI(resolve_type_uri(mapping))
|
|
60
|
+
graph << RDF::Statement.new(subject_uri, RDF.type, type_uri)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
mapping.rdf_predicates.each do |rule|
|
|
64
|
+
value = instance.public_send(rule.to)
|
|
65
|
+
next if value.nil?
|
|
66
|
+
|
|
67
|
+
Array(value).each do |v|
|
|
68
|
+
object = build_rdf_object(v, rule)
|
|
69
|
+
graph << RDF::Statement.new(subject_uri, RDF::URI(rule.uri),
|
|
70
|
+
object)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
mapping.rdf_members.each do |member_rule|
|
|
76
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
77
|
+
collection.each do |member|
|
|
78
|
+
member_mapping = member.class.mappings[:turtle]
|
|
79
|
+
next unless member_mapping
|
|
80
|
+
|
|
81
|
+
graph << build_graph(member_mapping, member)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
graph
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def build_rdf_object(value, rule)
|
|
89
|
+
if rule.lang_tagged
|
|
90
|
+
lang = extract_language(value)
|
|
91
|
+
RDF::Literal.new(value.to_s, language: lang)
|
|
92
|
+
else
|
|
93
|
+
case value
|
|
94
|
+
when Integer then RDF::Literal.new(value, datatype: RDF::XSD.integer)
|
|
95
|
+
when Float then RDF::Literal.new(value, datatype: RDF::XSD.double)
|
|
96
|
+
when TrueClass, FalseClass then RDF::Literal.new(value, datatype: RDF::XSD.boolean)
|
|
97
|
+
else RDF::Literal.new(value.to_s)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_prefixes(mapping, instance)
|
|
103
|
+
ns_set = mapping.namespace_set
|
|
104
|
+
|
|
105
|
+
mapping.rdf_members.each do |member_rule|
|
|
106
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
107
|
+
next if collection.empty?
|
|
108
|
+
|
|
109
|
+
member_mapping = collection.first.class.mappings[:turtle]
|
|
110
|
+
next unless member_mapping
|
|
111
|
+
|
|
112
|
+
ns_set = ns_set.merge(member_mapping.namespace_set)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
ns_set.each.with_object({}) do |ns, h|
|
|
116
|
+
h[ns.prefix.to_sym] = ns.uri if ns.prefix && ns.uri
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def extract_attributes(graph, mapping)
|
|
121
|
+
attrs = {}
|
|
122
|
+
type_uri = resolve_type_uri(mapping)
|
|
123
|
+
|
|
124
|
+
matching_subjects = find_subjects_by_type(graph, type_uri)
|
|
125
|
+
|
|
126
|
+
matching_subjects.each do |subject|
|
|
127
|
+
mapping.rdf_predicates.each do |rule|
|
|
128
|
+
stmts = graph.query([subject, RDF::URI(rule.uri), nil])
|
|
129
|
+
next if stmts.empty?
|
|
130
|
+
|
|
131
|
+
values = stmts.map { |s| literal_to_ruby(s.object) }
|
|
132
|
+
attrs[rule.to] = values.length == 1 ? values.first : values
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
attrs
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def find_subjects_by_type(graph, type_uri)
|
|
140
|
+
graph.query([nil, RDF.type, RDF::URI(type_uri)]).map(&:subject).uniq
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def literal_to_ruby(rdf_object)
|
|
144
|
+
case rdf_object
|
|
145
|
+
when RDF::Literal
|
|
146
|
+
case rdf_object.datatype
|
|
147
|
+
when RDF::XSD.integer then rdf_object.value.to_i
|
|
148
|
+
when RDF::XSD.double, RDF::XSD.decimal, RDF::XSD.float then rdf_object.value.to_f
|
|
149
|
+
when RDF::XSD.boolean then rdf_object.value == "true"
|
|
150
|
+
else rdf_object.value
|
|
151
|
+
end
|
|
152
|
+
else
|
|
153
|
+
rdf_object.to_s
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "model"
|
|
4
|
+
require_relative "rdf"
|
|
5
|
+
|
|
6
|
+
module Lutaml
|
|
7
|
+
module Turtle
|
|
8
|
+
autoload :Mapping, "#{__dir__}/turtle/mapping"
|
|
9
|
+
autoload :Transform, "#{__dir__}/turtle/transform"
|
|
10
|
+
autoload :Adapter, "#{__dir__}/turtle/adapter"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Lutaml::Model::FormatRegistry.register(
|
|
15
|
+
:turtle,
|
|
16
|
+
mapping_class: Lutaml::Turtle::Mapping,
|
|
17
|
+
adapter_class: Lutaml::Turtle::Adapter,
|
|
18
|
+
transformer: Lutaml::Turtle::Transform,
|
|
19
|
+
key_value: false,
|
|
20
|
+
rdf: true,
|
|
21
|
+
error_types: ["RDF::ReaderError"],
|
|
22
|
+
)
|
|
@@ -191,9 +191,13 @@ module Lutaml
|
|
|
191
191
|
def order
|
|
192
192
|
children.filter_map do |child|
|
|
193
193
|
if child.text?
|
|
194
|
-
next if child.text.nil?
|
|
194
|
+
next if child.text.nil?
|
|
195
195
|
|
|
196
196
|
Element.new("Text", "text", text_content: child.text)
|
|
197
|
+
elsif child.comment?
|
|
198
|
+
Element.new("Comment", "comment",
|
|
199
|
+
text_content: child.content,
|
|
200
|
+
node_type: :comment)
|
|
197
201
|
else
|
|
198
202
|
Element.new("Element", child.unprefixed_name)
|
|
199
203
|
end
|
|
@@ -967,7 +971,7 @@ module Lutaml
|
|
|
967
971
|
# Pass THIS element as parent so children can inherit namespaces
|
|
968
972
|
child_element_index = 0
|
|
969
973
|
previous_sibling_had_xmlns_blank = false
|
|
970
|
-
xml_element.children.each do |xml_child|
|
|
974
|
+
xml_element.children.each do |xml_child| # rubocop:disable Metrics/BlockLength
|
|
971
975
|
# Entity reference nodes are preserved via the marker
|
|
972
976
|
# preprocessing approach in NokogiriAdapter.parse.
|
|
973
977
|
if xml_child.is_a?(Lutaml::Xml::NokogiriElement) &&
|
|
@@ -993,6 +997,9 @@ module Lutaml
|
|
|
993
997
|
elsif xml_child.is_a?(String)
|
|
994
998
|
add_content_node(element, xml_child, doc,
|
|
995
999
|
cdata: xml_element.cdata && !xml_child.strip.empty?)
|
|
1000
|
+
elsif xml_child.is_a?(::Lutaml::Xml::DataModel::XmlComment)
|
|
1001
|
+
comment_node = doc.create_comment(xml_child.content)
|
|
1002
|
+
element.add_child(comment_node)
|
|
996
1003
|
end
|
|
997
1004
|
end
|
|
998
1005
|
|