lutaml-model 0.8.4 → 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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +3 -1
  3. data/.rubocop.yml +18 -0
  4. data/.rubocop_todo.yml +12 -18
  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/_pages/serialization_adapters.adoc +31 -0
  12. data/docs/_references/index.adoc +1 -0
  13. data/docs/_references/rdf-namespaces.adoc +243 -0
  14. data/docs/index.adoc +3 -2
  15. data/lib/lutaml/jsonld/adapter.rb +23 -0
  16. data/lib/lutaml/jsonld/context.rb +69 -0
  17. data/lib/lutaml/jsonld/term_definition.rb +39 -0
  18. data/lib/lutaml/jsonld/transform.rb +174 -0
  19. data/lib/lutaml/jsonld.rb +23 -0
  20. data/lib/lutaml/model/format_registry.rb +10 -1
  21. data/lib/lutaml/model/serialize/format_conversion.rb +17 -1
  22. data/lib/lutaml/model/version.rb +1 -1
  23. data/lib/lutaml/model.rb +6 -0
  24. data/lib/lutaml/rdf/error.rb +7 -0
  25. data/lib/lutaml/rdf/iri.rb +44 -0
  26. data/lib/lutaml/rdf/language_tagged.rb +11 -0
  27. data/lib/lutaml/rdf/literal.rb +62 -0
  28. data/lib/lutaml/rdf/mapping.rb +71 -0
  29. data/lib/lutaml/rdf/mapping_rule.rb +35 -0
  30. data/lib/lutaml/rdf/member_rule.rb +13 -0
  31. data/lib/lutaml/rdf/namespace.rb +58 -0
  32. data/lib/lutaml/rdf/namespace_set.rb +69 -0
  33. data/lib/lutaml/rdf/namespaces/dcterms_namespace.rb +12 -0
  34. data/lib/lutaml/rdf/namespaces/owl_namespace.rb +12 -0
  35. data/lib/lutaml/rdf/namespaces/rdf_namespace.rb +14 -0
  36. data/lib/lutaml/rdf/namespaces/rdfs_namespace.rb +12 -0
  37. data/lib/lutaml/rdf/namespaces/skos_namespace.rb +12 -0
  38. data/lib/lutaml/rdf/namespaces/xsd_namespace.rb +12 -0
  39. data/lib/lutaml/rdf/namespaces.rb +14 -0
  40. data/lib/lutaml/rdf/transform.rb +36 -0
  41. data/lib/lutaml/rdf.rb +19 -0
  42. data/lib/lutaml/turtle/adapter.rb +35 -0
  43. data/lib/lutaml/turtle/mapping.rb +7 -0
  44. data/lib/lutaml/turtle/transform.rb +158 -0
  45. data/lib/lutaml/turtle.rb +22 -0
  46. data/spec/lutaml/integration/edge_cases_spec.rb +109 -0
  47. data/spec/lutaml/integration/multi_format_spec.rb +106 -0
  48. data/spec/lutaml/integration/round_trip_spec.rb +170 -0
  49. data/spec/lutaml/jsonld/adapter_spec.rb +46 -0
  50. data/spec/lutaml/jsonld/context_spec.rb +114 -0
  51. data/spec/lutaml/jsonld/term_definition_spec.rb +55 -0
  52. data/spec/lutaml/jsonld/transform_spec.rb +211 -0
  53. data/spec/lutaml/rdf/graph_serialization_spec.rb +137 -0
  54. data/spec/lutaml/rdf/iri_spec.rb +73 -0
  55. data/spec/lutaml/rdf/literal_spec.rb +98 -0
  56. data/spec/lutaml/rdf/mapping_spec.rb +164 -0
  57. data/spec/lutaml/rdf/member_rule_spec.rb +17 -0
  58. data/spec/lutaml/rdf/namespace_set_spec.rb +115 -0
  59. data/spec/lutaml/rdf/namespace_spec.rb +241 -0
  60. data/spec/lutaml/rdf/rdf_transform_spec.rb +82 -0
  61. data/spec/lutaml/turtle/adapter_spec.rb +47 -0
  62. data/spec/lutaml/turtle/mapping_spec.rb +123 -0
  63. data/spec/lutaml/turtle/transform_spec.rb +273 -0
  64. metadata +50 -1
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module LanguageTagged
6
+ def language_tag
7
+ language
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ class MemberRule
6
+ attr_reader :attr_name
7
+
8
+ def initialize(attr_name)
9
+ @attr_name = attr_name.to_sym
10
+ end
11
+ end
12
+ end
13
+ 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module Namespaces
6
+ class DctermsNamespace < Lutaml::Rdf::Namespace
7
+ uri "http://purl.org/dc/terms/"
8
+ prefix "dcterms"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module Namespaces
6
+ class OwlNamespace < Lutaml::Rdf::Namespace
7
+ uri "http://www.w3.org/2002/07/owl#"
8
+ prefix "owl"
9
+ end
10
+ end
11
+ end
12
+ 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module Namespaces
6
+ class RdfsNamespace < Lutaml::Rdf::Namespace
7
+ uri "http://www.w3.org/2000/01/rdf-schema#"
8
+ prefix "rdfs"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module Namespaces
6
+ class SkosNamespace < Lutaml::Rdf::Namespace
7
+ uri "http://www.w3.org/2004/02/skos/core#"
8
+ prefix "skos"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Rdf
5
+ module Namespaces
6
+ class XsdNamespace < Lutaml::Rdf::Namespace
7
+ uri "http://www.w3.org/2001/XMLSchema#"
8
+ prefix "xsd"
9
+ end
10
+ end
11
+ end
12
+ 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Turtle
5
+ class Mapping < Lutaml::Rdf::Mapping; end
6
+ end
7
+ end