json-ld 3.2.4 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/json/ld/api.rb +807 -771
  4. data/lib/json/ld/compact.rb +304 -304
  5. data/lib/json/ld/conneg.rb +179 -161
  6. data/lib/json/ld/context.rb +2080 -1913
  7. data/lib/json/ld/expand.rb +745 -666
  8. data/lib/json/ld/extensions.rb +14 -13
  9. data/lib/json/ld/flatten.rb +257 -247
  10. data/lib/json/ld/format.rb +202 -194
  11. data/lib/json/ld/frame.rb +525 -502
  12. data/lib/json/ld/from_rdf.rb +223 -204
  13. data/lib/json/ld/html/nokogiri.rb +123 -121
  14. data/lib/json/ld/html/rexml.rb +151 -147
  15. data/lib/json/ld/reader.rb +107 -100
  16. data/lib/json/ld/resource.rb +224 -205
  17. data/lib/json/ld/streaming_reader.rb +574 -507
  18. data/lib/json/ld/streaming_writer.rb +93 -92
  19. data/lib/json/ld/to_rdf.rb +171 -169
  20. data/lib/json/ld/utils.rb +270 -264
  21. data/lib/json/ld/version.rb +24 -14
  22. data/lib/json/ld/writer.rb +334 -311
  23. data/lib/json/ld.rb +103 -96
  24. metadata +55 -198
  25. data/spec/api_spec.rb +0 -132
  26. data/spec/compact_spec.rb +0 -3482
  27. data/spec/conneg_spec.rb +0 -373
  28. data/spec/context_spec.rb +0 -2056
  29. data/spec/expand_spec.rb +0 -4496
  30. data/spec/flatten_spec.rb +0 -1203
  31. data/spec/format_spec.rb +0 -115
  32. data/spec/frame_spec.rb +0 -2541
  33. data/spec/from_rdf_spec.rb +0 -1072
  34. data/spec/matchers.rb +0 -20
  35. data/spec/rdfstar_spec.rb +0 -25
  36. data/spec/reader_spec.rb +0 -883
  37. data/spec/resource_spec.rb +0 -76
  38. data/spec/spec_helper.rb +0 -281
  39. data/spec/streaming_reader_spec.rb +0 -237
  40. data/spec/streaming_writer_spec.rb +0 -145
  41. data/spec/suite_compact_spec.rb +0 -22
  42. data/spec/suite_expand_spec.rb +0 -36
  43. data/spec/suite_flatten_spec.rb +0 -34
  44. data/spec/suite_frame_spec.rb +0 -29
  45. data/spec/suite_from_rdf_spec.rb +0 -22
  46. data/spec/suite_helper.rb +0 -411
  47. data/spec/suite_html_spec.rb +0 -22
  48. data/spec/suite_http_spec.rb +0 -35
  49. data/spec/suite_remote_doc_spec.rb +0 -22
  50. data/spec/suite_to_rdf_spec.rb +0 -30
  51. data/spec/support/extensions.rb +0 -44
  52. data/spec/test-files/test-1-compacted.jsonld +0 -10
  53. data/spec/test-files/test-1-context.jsonld +0 -7
  54. data/spec/test-files/test-1-expanded.jsonld +0 -5
  55. data/spec/test-files/test-1-input.jsonld +0 -10
  56. data/spec/test-files/test-1-rdf.ttl +0 -8
  57. data/spec/test-files/test-2-compacted.jsonld +0 -20
  58. data/spec/test-files/test-2-context.jsonld +0 -7
  59. data/spec/test-files/test-2-expanded.jsonld +0 -16
  60. data/spec/test-files/test-2-input.jsonld +0 -20
  61. data/spec/test-files/test-2-rdf.ttl +0 -14
  62. data/spec/test-files/test-3-compacted.jsonld +0 -11
  63. data/spec/test-files/test-3-context.jsonld +0 -8
  64. data/spec/test-files/test-3-expanded.jsonld +0 -10
  65. data/spec/test-files/test-3-input.jsonld +0 -11
  66. data/spec/test-files/test-3-rdf.ttl +0 -8
  67. data/spec/test-files/test-4-compacted.jsonld +0 -10
  68. data/spec/test-files/test-4-context.jsonld +0 -7
  69. data/spec/test-files/test-4-expanded.jsonld +0 -6
  70. data/spec/test-files/test-4-input.jsonld +0 -10
  71. data/spec/test-files/test-4-rdf.ttl +0 -5
  72. data/spec/test-files/test-5-compacted.jsonld +0 -13
  73. data/spec/test-files/test-5-context.jsonld +0 -7
  74. data/spec/test-files/test-5-expanded.jsonld +0 -9
  75. data/spec/test-files/test-5-input.jsonld +0 -13
  76. data/spec/test-files/test-5-rdf.ttl +0 -7
  77. data/spec/test-files/test-6-compacted.jsonld +0 -10
  78. data/spec/test-files/test-6-context.jsonld +0 -7
  79. data/spec/test-files/test-6-expanded.jsonld +0 -10
  80. data/spec/test-files/test-6-input.jsonld +0 -10
  81. data/spec/test-files/test-6-rdf.ttl +0 -6
  82. data/spec/test-files/test-7-compacted.jsonld +0 -23
  83. data/spec/test-files/test-7-context.jsonld +0 -4
  84. data/spec/test-files/test-7-expanded.jsonld +0 -20
  85. data/spec/test-files/test-7-input.jsonld +0 -23
  86. data/spec/test-files/test-7-rdf.ttl +0 -14
  87. data/spec/test-files/test-8-compacted.jsonld +0 -34
  88. data/spec/test-files/test-8-context.jsonld +0 -11
  89. data/spec/test-files/test-8-expanded.jsonld +0 -24
  90. data/spec/test-files/test-8-frame.jsonld +0 -18
  91. data/spec/test-files/test-8-framed.jsonld +0 -25
  92. data/spec/test-files/test-8-input.jsonld +0 -30
  93. data/spec/test-files/test-8-rdf.ttl +0 -15
  94. data/spec/test-files/test-9-compacted.jsonld +0 -20
  95. data/spec/test-files/test-9-context.jsonld +0 -13
  96. data/spec/test-files/test-9-expanded.jsonld +0 -14
  97. data/spec/test-files/test-9-input.jsonld +0 -12
  98. data/spec/to_rdf_spec.rb +0 -1684
  99. data/spec/writer_spec.rb +0 -427
data/lib/json/ld/api.rb CHANGED
@@ -1,5 +1,7 @@
1
- # -*- encoding: utf-8 -*-
2
1
  # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
3
5
  require 'openssl'
4
6
  require 'cgi'
5
7
  require 'json/ld/expand'
@@ -14,886 +16,920 @@ begin
14
16
  rescue LoadError
15
17
  end
16
18
 
17
- module JSON::LD
18
- ##
19
- # A JSON-LD processor based on the JsonLdProcessor interface.
20
- #
21
- # This API provides a clean mechanism that enables developers to convert JSON-LD data into a a variety of output formats that are easier to work with in various programming languages. If a JSON-LD API is provided in a programming environment, the entirety of the following API must be implemented.
22
- #
23
- # Note that the API method signatures are somewhat different than what is specified, as the use of Futures and explicit callback parameters is not as relevant for Ruby-based interfaces.
24
- #
25
- # @see https://www.w3.org/TR/json-ld11-api/#the-application-programming-interface
26
- # @author [Gregg Kellogg](http://greggkellogg.net/)
27
- class API
28
- include Expand
29
- include Compact
30
- include ToRDF
31
- include Flatten
32
- include FromRDF
33
- include Frame
34
- include RDF::Util::Logger
35
-
36
- # Options used for open_file
37
- OPEN_OPTS = {
38
- headers: {"Accept" => "application/ld+json, text/html;q=0.8, application/xhtml+xml;q=0.8, application/json;q=0.5"}
39
- }
40
-
41
- # The following constants are used to reduce object allocations
42
- LINK_REL_CONTEXT = %w(rel http://www.w3.org/ns/json-ld#context).freeze
43
- LINK_REL_ALTERNATE = %w(rel alternate).freeze
44
- LINK_TYPE_JSONLD = %w(type application/ld+json).freeze
45
- JSON_LD_PROCESSING_MODES = %w(json-ld-1.0 json-ld-1.1).freeze
46
-
47
- # Current input
48
- # @!attribute [rw] input
49
- # @return [String, #read, Hash, Array]
50
- attr_accessor :value
51
-
52
- # Input evaluation context
53
- # @!attribute [rw] context
54
- # @return [JSON::LD::Context]
55
- attr_accessor :context
56
-
57
- # Current Blank Node Namer
58
- # @!attribute [r] namer
59
- # @return [JSON::LD::BlankNodeNamer]
60
- attr_reader :namer
61
-
19
+ module JSON
20
+ module LD
62
21
  ##
63
- # Initialize the API, reading in any document and setting global options
22
+ # A JSON-LD processor based on the JsonLdProcessor interface.
64
23
  #
65
- # @param [String, #read, Hash, Array] input
66
- # @param [String, #read, Hash, Array, JSON::LD::Context] context
67
- # An external context to use additionally to the context embedded in input when expanding the input.
68
- # @param [Hash{Symbol => Object}] options
69
- # @option options [Symbol] :adapter used with MultiJson
70
- # @option options [RDF::URI, String, #to_s] :base
71
- # The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context. If not specified, and a base IRI is found from `input`, options[:base] will be modified with this value.
72
- # @option options [Boolean] :compactArrays (true)
73
- # If set to `true`, the JSON-LD processor replaces arrays with just one element with that element during compaction. If set to `false`, all arrays will remain arrays even if they have just one element.
74
- # @option options [Boolean] :compactToRelative (true)
75
- # Creates document relative IRIs when compacting, if `true`, otherwise leaves expanded.
76
- # @option options [Proc] :documentLoader
77
- # The callback of the loader to be used to retrieve remote documents and contexts. If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. See {documentLoader} for the method signature.
78
- # @option options [String, #read, Hash, Array, JSON::LD::Context] :expandContext
79
- # A context that is used to initialize the active context when expanding a document.
80
- # @option options [Boolean] :extendedRepresentation (false)
81
- # Use the extended internal representation.
82
- # @option options [Boolean] :extractAllScripts
83
- # If set, when given an HTML input without a fragment identifier, extracts all `script` elements with type `application/ld+json` into an array during expansion.
84
- # @option options [Boolean, String, RDF::URI] :flatten
85
- # If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened.
86
- # @option options [String] :language
87
- # When set, this has the effect of inserting a context definition with `@language` set to the associated value, creating a default language for interpreting string values.
88
- # @option options [Symbol] :library
89
- # One of :nokogiri or :rexml. If nil/unspecified uses :nokogiri if available, :rexml otherwise.
90
- # @option options [Boolean] :lowercaseLanguage
91
- # By default, language tags are left as is. To normalize to lowercase, set this option to `true`.
92
- # @option options [Boolean] :ordered (true)
93
- # Order traversal of dictionary members by key when performing algorithms.
94
- # @option options [String] :processingMode
95
- # Processing mode, json-ld-1.0 or json-ld-1.1.
96
- # @option options [Boolean] :rdfstar (false)
97
- # support parsing JSON-LD-star statement resources.
98
- # @option options [Boolean] :rename_bnodes (true)
99
- # Rename bnodes as part of expansion, or keep them the same.
100
- # @option options [Boolean] :unique_bnodes (false)
101
- # Use unique bnode identifiers, defaults to using the identifier which the node was originally initialized with (if any).
102
- # @option options [Boolean] :validate Validate input, if a string or readable object.
103
- # @yield [api]
104
- # @yieldparam [API]
105
- # @raise [JsonLdError]
106
- def initialize(input, context, **options, &block)
107
- @options = {
108
- compactArrays: true,
109
- ordered: false,
110
- extractAllScripts: false,
111
- rename_bnodes: true,
112
- unique_bnodes: false,
113
- }.merge(options)
114
- @namer = @options[:unique_bnodes] ? BlankNodeUniqer.new : (@options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
115
-
116
- @options[:base] = RDF::URI(@options[:base]) if @options[:base] && !@options[:base].is_a?(RDF::URI)
117
- # For context via Link header
118
- _, context_ref = nil, nil
119
-
120
- @value = case input
121
- when Array, Hash then input.dup
122
- when IO, StringIO, String
123
- remote_doc = self.class.loadRemoteDocument(input, **@options)
124
-
125
- context_ref = remote_doc.contextUrl
126
- @options[:base] = RDF::URI(remote_doc.documentUrl) if remote_doc.documentUrl && !@options[:no_default_base]
127
-
128
- case remote_doc.document
129
- when String
130
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
131
- MultiJson.load(remote_doc.document, **mj_opts)
24
+ # This API provides a clean mechanism that enables developers to convert JSON-LD data into a a variety of output formats that are easier to work with in various programming languages. If a JSON-LD API is provided in a programming environment, the entirety of the following API must be implemented.
25
+ #
26
+ # Note that the API method signatures are somewhat different than what is specified, as the use of Futures and explicit callback parameters is not as relevant for Ruby-based interfaces.
27
+ #
28
+ # @see https://www.w3.org/TR/json-ld11-api/#the-application-programming-interface
29
+ # @author [Gregg Kellogg](http://greggkellogg.net/)
30
+ class API
31
+ include Expand
32
+ include Compact
33
+ include ToRDF
34
+ include Flatten
35
+ include FromRDF
36
+ include Frame
37
+ include RDF::Util::Logger
38
+
39
+ # Options used for open_file
40
+ OPEN_OPTS = {
41
+ headers: { "Accept" => "application/ld+json, text/html;q=0.8, application/xhtml+xml;q=0.8, application/json;q=0.5" }
42
+ }
43
+
44
+ # The following constants are used to reduce object allocations
45
+ LINK_REL_CONTEXT = %w[rel http://www.w3.org/ns/json-ld#context].freeze
46
+ LINK_REL_ALTERNATE = %w[rel alternate].freeze
47
+ LINK_TYPE_JSONLD = %w[type application/ld+json].freeze
48
+ JSON_LD_PROCESSING_MODES = %w[json-ld-1.0 json-ld-1.1].freeze
49
+
50
+ # Current input
51
+ # @!attribute [rw] input
52
+ # @return [String, #read, Hash, Array]
53
+ attr_accessor :value
54
+
55
+ # Input evaluation context
56
+ # @!attribute [rw] context
57
+ # @return [JSON::LD::Context]
58
+ attr_accessor :context
59
+
60
+ # Current Blank Node Namer
61
+ # @!attribute [r] namer
62
+ # @return [JSON::LD::BlankNodeNamer]
63
+ attr_reader :namer
64
+
65
+ ##
66
+ # Initialize the API, reading in any document and setting global options
67
+ #
68
+ # @param [String, #read, Hash, Array] input
69
+ # @param [String, #read, Hash, Array, JSON::LD::Context] context
70
+ # An external context to use additionally to the context embedded in input when expanding the input.
71
+ # @param [Hash{Symbol => Object}] options
72
+ # @option options [Symbol] :adapter used with MultiJson
73
+ # @option options [RDF::URI, String, #to_s] :base
74
+ # The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context. If not specified, and a base IRI is found from `input`, options[:base] will be modified with this value.
75
+ # @option options [Boolean] :compactArrays (true)
76
+ # If set to `true`, the JSON-LD processor replaces arrays with just one element with that element during compaction. If set to `false`, all arrays will remain arrays even if they have just one element.
77
+ # @option options [Boolean] :compactToRelative (true)
78
+ # Creates document relative IRIs when compacting, if `true`, otherwise leaves expanded.
79
+ # @option options [Proc] :documentLoader
80
+ # The callback of the loader to be used to retrieve remote documents and contexts. If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. See {documentLoader} for the method signature.
81
+ # @option options [String, #read, Hash, Array, JSON::LD::Context] :expandContext
82
+ # A context that is used to initialize the active context when expanding a document.
83
+ # @option options [Boolean] :extendedRepresentation (false)
84
+ # Use the extended internal representation.
85
+ # @option options [Boolean] :extractAllScripts
86
+ # If set, when given an HTML input without a fragment identifier, extracts all `script` elements with type `application/ld+json` into an array during expansion.
87
+ # @option options [Boolean, String, RDF::URI] :flatten
88
+ # If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened.
89
+ # @option options [String] :language
90
+ # When set, this has the effect of inserting a context definition with `@language` set to the associated value, creating a default language for interpreting string values.
91
+ # @option options [Symbol] :library
92
+ # One of :nokogiri or :rexml. If nil/unspecified uses :nokogiri if available, :rexml otherwise.
93
+ # @option options [Boolean] :lowercaseLanguage
94
+ # By default, language tags are left as is. To normalize to lowercase, set this option to `true`.
95
+ # @option options [Boolean] :ordered (true)
96
+ # Order traversal of dictionary members by key when performing algorithms.
97
+ # @option options [String] :processingMode
98
+ # Processing mode, json-ld-1.0 or json-ld-1.1.
99
+ # @option options [Boolean] :rdfstar (false)
100
+ # support parsing JSON-LD-star statement resources.
101
+ # @option options [Boolean] :rename_bnodes (true)
102
+ # Rename bnodes as part of expansion, or keep them the same.
103
+ # @option options [Boolean] :unique_bnodes (false)
104
+ # Use unique bnode identifiers, defaults to using the identifier which the node was originally initialized with (if any).
105
+ # @option options [Boolean] :validate Validate input, if a string or readable object.
106
+ # @yield [api]
107
+ # @yieldparam [API]
108
+ # @raise [JsonLdError]
109
+ def initialize(input, context, **options, &block)
110
+ @options = {
111
+ compactArrays: true,
112
+ ordered: false,
113
+ extractAllScripts: false,
114
+ rename_bnodes: true,
115
+ unique_bnodes: false
116
+ }.merge(options)
117
+ @namer = if @options[:unique_bnodes]
118
+ BlankNodeUniqer.new
132
119
  else
133
- # Already parsed
134
- remote_doc.document
120
+ (@options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
135
121
  end
136
- end
137
122
 
138
- # If not provided, first use context from document, or from a Link header
139
- context ||= context_ref || {}
140
- @context = Context.parse(context, **@options)
123
+ @options[:base] = RDF::URI(@options[:base]) if @options[:base] && !@options[:base].is_a?(RDF::URI)
124
+ # For context via Link header
125
+ _ = nil
126
+ context_ref = nil
127
+
128
+ @value = case input
129
+ when Array, Hash then input.dup
130
+ when IO, StringIO, String
131
+ remote_doc = self.class.loadRemoteDocument(input, **@options)
132
+
133
+ context_ref = remote_doc.contextUrl
134
+ @options[:base] = RDF::URI(remote_doc.documentUrl) if remote_doc.documentUrl && !@options[:no_default_base]
135
+
136
+ case remote_doc.document
137
+ when String
138
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
139
+ MultiJson.load(remote_doc.document, **mj_opts)
140
+ else
141
+ # Already parsed
142
+ remote_doc.document
143
+ end
144
+ end
145
+
146
+ # If not provided, first use context from document, or from a Link header
147
+ context ||= context_ref || {}
148
+ @context = Context.parse(context, **@options)
149
+
150
+ return unless block
141
151
 
142
- if block_given?
143
152
  case block.arity
144
- when 0, -1 then instance_eval(&block)
145
- else block.call(self)
153
+ when 0, -1 then instance_eval(&block)
154
+ else yield(self)
146
155
  end
147
156
  end
148
- end
149
157
 
150
- # This is used internally only
151
- private :initialize
152
-
153
- ##
154
- # Expands the given input according to the steps in the Expansion Algorithm. The input must be copied, expanded and returned if there are no errors. If the expansion fails, an appropriate exception must be thrown.
155
- #
156
- # The resulting `Array` either returned or yielded
157
- #
158
- # @param [String, #read, Hash, Array] input
159
- # The JSON-LD object to copy and perform the expansion upon.
160
- # @param [Proc] serializer (nil)
161
- # A Serializer method used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
162
- # See {JSON::LD::API.serializer}.
163
- # @param [Hash{Symbol => Object}] options
164
- # @option options (see #initialize)
165
- # @raise [JsonLdError]
166
- # @yield jsonld, base_iri
167
- # @yieldparam [Array<Hash>] jsonld
168
- # The expanded JSON-LD document
169
- # @yieldparam [RDF::URI] base_iri
170
- # The document base as determined during expansion
171
- # @yieldreturn [Object] returned object
172
- # @return [Object, Array<Hash>]
173
- # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
174
- # @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
175
- def self.expand(input, framing: false, serializer: nil, **options, &block)
176
- result = doc_base = nil
177
- API.new(input, options[:expandContext], **options) do
178
- result = self.expand(self.value, nil, self.context,
179
- framing: framing)
180
- doc_base = @options[:base]
181
- end
158
+ # This is used internally only
159
+ private :initialize
160
+
161
+ ##
162
+ # Expands the given input according to the steps in the Expansion Algorithm. The input must be copied, expanded and returned if there are no errors. If the expansion fails, an appropriate exception must be thrown.
163
+ #
164
+ # The resulting `Array` either returned or yielded
165
+ #
166
+ # @param [String, #read, Hash, Array] input
167
+ # The JSON-LD object to copy and perform the expansion upon.
168
+ # @param [Proc] serializer (nil)
169
+ # A Serializer method used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
170
+ # See {JSON::LD::API.serializer}.
171
+ # @param [Hash{Symbol => Object}] options
172
+ # @option options (see #initialize)
173
+ # @raise [JsonLdError]
174
+ # @yield jsonld, base_iri
175
+ # @yieldparam [Array<Hash>] jsonld
176
+ # The expanded JSON-LD document
177
+ # @yieldparam [RDF::URI] base_iri
178
+ # The document base as determined during expansion
179
+ # @yieldreturn [Object] returned object
180
+ # @return [Object, Array<Hash>]
181
+ # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
182
+ # @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
183
+ def self.expand(input, framing: false, serializer: nil, **options, &block)
184
+ result = doc_base = nil
185
+ API.new(input, options[:expandContext], **options) do
186
+ result = expand(value, nil, context,
187
+ framing: framing)
188
+ doc_base = @options[:base]
189
+ end
182
190
 
183
- # If, after the algorithm outlined above is run, the resulting element is an JSON object with just a @graph property, element is set to the value of @graph's value.
184
- result = result['@graph'] if result.is_a?(Hash) && result.length == 1 && result.key?('@graph')
191
+ # If, after the algorithm outlined above is run, the resulting element is an JSON object with just a @graph property, element is set to the value of @graph's value.
192
+ result = result['@graph'] if result.is_a?(Hash) && result.length == 1 && result.key?('@graph')
185
193
 
186
- # Finally, if element is a JSON object, it is wrapped into an array.
187
- result = [result].compact unless result.is_a?(Array)
188
- result = serializer.call(result, **options) if serializer
194
+ # Finally, if element is a JSON object, it is wrapped into an array.
195
+ result = [result].compact unless result.is_a?(Array)
196
+ result = serializer.call(result, **options) if serializer
189
197
 
190
- if block_given?
191
- case block.arity
192
- when 1 then yield(result)
193
- when 2 then yield(result, doc_base)
198
+ if block
199
+ case block.arity
200
+ when 1 then yield(result)
201
+ when 2 then yield(result, doc_base)
202
+ else
203
+ raise "Unexpected number of yield parameters to expand"
204
+ end
194
205
  else
195
- raise "Unexpected number of yield parameters to expand"
206
+ result
196
207
  end
197
- else
198
- result
199
208
  end
200
- end
201
209
 
202
- ##
203
- # Compacts the given input according to the steps in the Compaction Algorithm. The input must be copied, compacted and returned if there are no errors. If the compaction fails, an appropirate exception must be thrown.
204
- #
205
- # If no context is provided, the input document is compacted using the top-level context of the document
206
- #
207
- # The resulting `Hash` is either returned or yielded, if a block is given.
208
- #
209
- # @param [String, #read, Hash, Array] input
210
- # The JSON-LD object to copy and perform the compaction upon.
211
- # @param [String, #read, Hash, Array, JSON::LD::Context] context
212
- # The base context to use when compacting the input.
213
- # @param [Proc] serializer (nil)
214
- # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
215
- # See {JSON::LD::API.serializer}.
216
- # @param [Boolean] expanded (false) Input is already expanded
217
- # @param [Hash{Symbol => Object}] options
218
- # @option options (see #initialize)
219
- # @yield jsonld
220
- # @yieldparam [Hash] jsonld
221
- # The compacted JSON-LD document
222
- # @yieldreturn [Object] returned object
223
- # @return [Object, Hash]
224
- # If a block is given, the result of evaluating the block is returned, otherwise, the compacted JSON-LD document
225
- # @raise [JsonLdError]
226
- # @see https://www.w3.org/TR/json-ld11-api/#compaction-algorithm
227
- def self.compact(input, context, expanded: false, serializer: nil, **options)
228
- result = nil
229
- options = {compactToRelative: true}.merge(options)
230
-
231
- # 1) Perform the Expansion Algorithm on the JSON-LD input.
232
- # This removes any existing context to allow the given context to be cleanly applied.
233
- expanded_input = expanded ? input : API.expand(input, ordered: false, **options) do |res, base_iri|
234
- options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
235
- res
236
- end
210
+ ##
211
+ # Compacts the given input according to the steps in the Compaction Algorithm. The input must be copied, compacted and returned if there are no errors. If the compaction fails, an appropirate exception must be thrown.
212
+ #
213
+ # If no context is provided, the input document is compacted using the top-level context of the document
214
+ #
215
+ # The resulting `Hash` is either returned or yielded, if a block is given.
216
+ #
217
+ # @param [String, #read, Hash, Array] input
218
+ # The JSON-LD object to copy and perform the compaction upon.
219
+ # @param [String, #read, Hash, Array, JSON::LD::Context] context
220
+ # The base context to use when compacting the input.
221
+ # @param [Proc] serializer (nil)
222
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
223
+ # See {JSON::LD::API.serializer}.
224
+ # @param [Boolean] expanded (false) Input is already expanded
225
+ # @param [Hash{Symbol => Object}] options
226
+ # @option options (see #initialize)
227
+ # @yield jsonld
228
+ # @yieldparam [Hash] jsonld
229
+ # The compacted JSON-LD document
230
+ # @yieldreturn [Object] returned object
231
+ # @return [Object, Hash]
232
+ # If a block is given, the result of evaluating the block is returned, otherwise, the compacted JSON-LD document
233
+ # @raise [JsonLdError]
234
+ # @see https://www.w3.org/TR/json-ld11-api/#compaction-algorithm
235
+ def self.compact(input, context, expanded: false, serializer: nil, **options)
236
+ result = nil
237
+ options = { compactToRelative: true }.merge(options)
237
238
 
238
- API.new(expanded_input, context, no_default_base: true, **options) do
239
- # log_debug(".compact") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
240
- result = compact(value)
239
+ # 1) Perform the Expansion Algorithm on the JSON-LD input.
240
+ # This removes any existing context to allow the given context to be cleanly applied.
241
+ expanded_input = if expanded
242
+ input
243
+ else
244
+ API.expand(input, ordered: false, **options) do |res, base_iri|
245
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
246
+ res
247
+ end
248
+ end
241
249
 
242
- # xxx) Add the given context to the output
243
- ctx = self.context.serialize(provided_context: context)
244
- if result.is_a?(Array)
245
- kwgraph = self.context.compact_iri('@graph', vocab: true)
246
- result = result.empty? ? {} : {kwgraph => result}
250
+ API.new(expanded_input, context, no_default_base: true, **options) do
251
+ # log_debug(".compact") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
252
+ result = compact(value)
253
+
254
+ # xxx) Add the given context to the output
255
+ ctx = self.context.serialize(provided_context: context)
256
+ if result.is_a?(Array)
257
+ kwgraph = self.context.compact_iri('@graph', vocab: true)
258
+ result = result.empty? ? {} : { kwgraph => result }
259
+ end
260
+ result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
247
261
  end
248
- result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
262
+ result = serializer.call(result, **options) if serializer
263
+ block_given? ? yield(result) : result
249
264
  end
250
- result = serializer.call(result, **options) if serializer
251
- block_given? ? yield(result) : result
252
- end
253
265
 
254
- ##
255
- # This algorithm flattens an expanded JSON-LD document by collecting all properties of a node in a single JSON object and labeling all blank nodes with blank node identifiers. This resulting uniform shape of the document, may drastically simplify the code required to process JSON-LD data in certain applications.
256
- #
257
- # The resulting `Array` is either returned, or yielded if a block is given.
258
- #
259
- # @param [String, #read, Hash, Array] input
260
- # The JSON-LD object or array of JSON-LD objects to flatten or an IRI referencing the JSON-LD document to flatten.
261
- # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
262
- # An optional external context to use additionally to the context embedded in input when expanding the input.
263
- # @param [Boolean] expanded (false) Input is already expanded
264
- # @param [Proc] serializer (nil)
265
- # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
266
- # See {JSON::LD::API.serializer}.
267
- # @param [Hash{Symbol => Object}] options
268
- # @option options (see #initialize)
269
- # @option options [Boolean] :createAnnotations
270
- # Unfold embedded nodes which can be represented using `@annotation`.
271
- # @yield jsonld
272
- # @yieldparam [Hash] jsonld
273
- # The flattened JSON-LD document
274
- # @yieldreturn [Object] returned object
275
- # @return [Object, Hash]
276
- # If a block is given, the result of evaluating the block is returned, otherwise, the flattened JSON-LD document
277
- # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
278
- def self.flatten(input, context, expanded: false, serializer: nil, **options)
279
- flattened = []
280
- options = {
281
- compactToRelative: true,
282
- extractAllScripts: true,
283
- }.merge(options)
284
-
285
- # Expand input to simplify processing
286
- expanded_input = expanded ? input : API.expand(input, **options) do |result, base_iri|
287
- options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
288
- result
289
- end
266
+ ##
267
+ # This algorithm flattens an expanded JSON-LD document by collecting all properties of a node in a single JSON object and labeling all blank nodes with blank node identifiers. This resulting uniform shape of the document, may drastically simplify the code required to process JSON-LD data in certain applications.
268
+ #
269
+ # The resulting `Array` is either returned, or yielded if a block is given.
270
+ #
271
+ # @param [String, #read, Hash, Array] input
272
+ # The JSON-LD object or array of JSON-LD objects to flatten or an IRI referencing the JSON-LD document to flatten.
273
+ # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
274
+ # An optional external context to use additionally to the context embedded in input when expanding the input.
275
+ # @param [Boolean] expanded (false) Input is already expanded
276
+ # @param [Proc] serializer (nil)
277
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
278
+ # See {JSON::LD::API.serializer}.
279
+ # @param [Hash{Symbol => Object}] options
280
+ # @option options (see #initialize)
281
+ # @option options [Boolean] :createAnnotations
282
+ # Unfold embedded nodes which can be represented using `@annotation`.
283
+ # @yield jsonld
284
+ # @yieldparam [Hash] jsonld
285
+ # The flattened JSON-LD document
286
+ # @yieldreturn [Object] returned object
287
+ # @return [Object, Hash]
288
+ # If a block is given, the result of evaluating the block is returned, otherwise, the flattened JSON-LD document
289
+ # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
290
+ def self.flatten(input, context, expanded: false, serializer: nil, **options)
291
+ flattened = []
292
+ options = {
293
+ compactToRelative: true,
294
+ extractAllScripts: true
295
+ }.merge(options)
296
+
297
+ # Expand input to simplify processing
298
+ expanded_input = if expanded
299
+ input
300
+ else
301
+ API.expand(input, **options) do |result, base_iri|
302
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
303
+ result
304
+ end
305
+ end
290
306
 
291
- # Initialize input using
292
- API.new(expanded_input, context, no_default_base: true, **options) do
293
- # log_debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE) rescue 'malformed json'}"}
307
+ # Initialize input using
308
+ API.new(expanded_input, context, no_default_base: true, **options) do
309
+ # log_debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE) rescue 'malformed json'}"}
294
310
 
295
- # Rename blank nodes recusively. Note that this does not create new blank node identifiers where none exist, which is performed in the node map generation algorithm.
296
- @value = rename_bnodes(@value) if @options[:rename_bnodes]
311
+ # Rename blank nodes recusively. Note that this does not create new blank node identifiers where none exist, which is performed in the node map generation algorithm.
312
+ @value = rename_bnodes(@value) if @options[:rename_bnodes]
297
313
 
298
- # Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
299
- graph_maps = {'@default' => {}}
300
- create_node_map(value, graph_maps)
314
+ # Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
315
+ graph_maps = { '@default' => {} }
316
+ create_node_map(value, graph_maps)
301
317
 
302
- # If create annotations flag is set, then update each node map in graph maps with the result of calling the create annotations algorithm.
303
- if options[:createAnnotations]
304
- graph_maps.values.each do |node_map|
305
- create_annotations(node_map)
318
+ # If create annotations flag is set, then update each node map in graph maps with the result of calling the create annotations algorithm.
319
+ if options[:createAnnotations]
320
+ graph_maps.each_value do |node_map|
321
+ create_annotations(node_map)
322
+ end
306
323
  end
307
- end
308
324
 
309
- default_graph = graph_maps['@default']
310
- graph_maps.keys.opt_sort(ordered: @options[:ordered]).each do |graph_name|
311
- next if graph_name == '@default'
325
+ default_graph = graph_maps['@default']
326
+ graph_maps.keys.opt_sort(ordered: @options[:ordered]).each do |graph_name|
327
+ next if graph_name == '@default'
312
328
 
313
- graph = graph_maps[graph_name]
314
- entry = default_graph[graph_name] ||= {'@id' => graph_name}
315
- nodes = entry['@graph'] ||= []
316
- graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
317
- nodes << graph[id] unless node_reference?(graph[id])
329
+ graph = graph_maps[graph_name]
330
+ entry = default_graph[graph_name] ||= { '@id' => graph_name }
331
+ nodes = entry['@graph'] ||= []
332
+ graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
333
+ nodes << graph[id] unless node_reference?(graph[id])
334
+ end
335
+ end
336
+ default_graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
337
+ flattened << default_graph[id] unless node_reference?(default_graph[id])
318
338
  end
319
- end
320
- default_graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
321
- flattened << default_graph[id] unless node_reference?(default_graph[id])
322
- end
323
339
 
324
- if context && !flattened.empty?
325
- # Otherwise, return the result of compacting flattened according the Compaction algorithm passing context ensuring that the compaction result uses the @graph keyword (or its alias) at the top-level, even if the context is empty or if there is only one element to put in the @graph array. This ensures that the returned document has a deterministic structure.
326
- compacted = as_array(compact(flattened))
327
- kwgraph = self.context.compact_iri('@graph', vocab: true)
328
- flattened = self.context.
329
- serialize(provided_context: context).
330
- merge(kwgraph => compacted)
340
+ if context && !flattened.empty?
341
+ # Otherwise, return the result of compacting flattened according the Compaction algorithm passing context ensuring that the compaction result uses the @graph keyword (or its alias) at the top-level, even if the context is empty or if there is only one element to put in the @graph array. This ensures that the returned document has a deterministic structure.
342
+ compacted = as_array(compact(flattened))
343
+ kwgraph = self.context.compact_iri('@graph', vocab: true)
344
+ flattened = self.context
345
+ .serialize(provided_context: context)
346
+ .merge(kwgraph => compacted)
347
+ end
331
348
  end
332
- end
333
349
 
334
- flattened = serializer.call(flattened, **options) if serializer
335
- block_given? ? yield(flattened) : flattened
336
- end
350
+ flattened = serializer.call(flattened, **options) if serializer
351
+ block_given? ? yield(flattened) : flattened
352
+ end
337
353
 
338
- ##
339
- # Frames the given input using the frame according to the steps in the Framing Algorithm. The input is used to build the framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned. Exceptions must be thrown if there are errors.
340
- #
341
- # The resulting `Array` is either returned, or yielded if a block is given.
342
- #
343
- # @param [String, #read, Hash, Array] input
344
- # The JSON-LD object to copy and perform the framing on.
345
- # @param [String, #read, Hash, Array] frame
346
- # The frame to use when re-arranging the data.
347
- # @param [Boolean] expanded (false) Input is already expanded
348
- # @option options (see #initialize)
349
- # @option options ['@always', '@link', '@once', '@never'] :embed ('@once')
350
- # a flag specifying that objects should be directly embedded in the output, instead of being referred to by their IRI.
351
- # @option options [Boolean] :explicit (false)
352
- # a flag specifying that for properties to be included in the output, they must be explicitly declared in the framing context.
353
- # @option options [Boolean] :requireAll (false)
354
- # A flag specifying that all properties present in the input frame must either have a default value or be present in the JSON-LD input for the frame to match.
355
- # @option options [Boolean] :omitDefault (false)
356
- # a flag specifying that properties that are missing from the JSON-LD input should be omitted from the output.
357
- # @option options [Boolean] :pruneBlankNodeIdentifiers (true) removes blank node identifiers that are only used once.
358
- # @option options [Boolean] :omitGraph does not use `@graph` at top level unless necessary to describe multiple objects, defaults to `true` if processingMode is 1.1, otherwise `false`.
359
- # @yield jsonld
360
- # @yieldparam [Hash] jsonld
361
- # The framed JSON-LD document
362
- # @yieldreturn [Object] returned object
363
- # @return [Object, Hash]
364
- # If a block is given, the result of evaluating the block is returned, otherwise, the framed JSON-LD document
365
- # @raise [InvalidFrame]
366
- # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
367
- def self.frame(input, frame, expanded: false, serializer: nil, **options)
368
- result = nil
369
- options = {
370
- base: (RDF::URI(input) if input.is_a?(String)),
371
- compactArrays: true,
372
- compactToRelative: true,
373
- embed: '@once',
374
- explicit: false,
375
- requireAll: false,
376
- omitDefault: false,
377
- }.merge(options)
378
-
379
- framing_state = {
380
- graphMap: {},
381
- graphStack: [],
382
- subjectStack: [],
383
- link: {},
384
- embedded: false # False at the top-level
385
- }
354
+ ##
355
+ # Frames the given input using the frame according to the steps in the Framing Algorithm. The input is used to build the framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned. Exceptions must be thrown if there are errors.
356
+ #
357
+ # The resulting `Array` is either returned, or yielded if a block is given.
358
+ #
359
+ # @param [String, #read, Hash, Array] input
360
+ # The JSON-LD object to copy and perform the framing on.
361
+ # @param [String, #read, Hash, Array] frame
362
+ # The frame to use when re-arranging the data.
363
+ # @param [Boolean] expanded (false) Input is already expanded
364
+ # @option options (see #initialize)
365
+ # @option options ['@always', '@link', '@once', '@never'] :embed ('@once')
366
+ # a flag specifying that objects should be directly embedded in the output, instead of being referred to by their IRI.
367
+ # @option options [Boolean] :explicit (false)
368
+ # a flag specifying that for properties to be included in the output, they must be explicitly declared in the framing context.
369
+ # @option options [Boolean] :requireAll (false)
370
+ # A flag specifying that all properties present in the input frame must either have a default value or be present in the JSON-LD input for the frame to match.
371
+ # @option options [Boolean] :omitDefault (false)
372
+ # a flag specifying that properties that are missing from the JSON-LD input should be omitted from the output.
373
+ # @option options [Boolean] :pruneBlankNodeIdentifiers (true) removes blank node identifiers that are only used once.
374
+ # @option options [Boolean] :omitGraph does not use `@graph` at top level unless necessary to describe multiple objects, defaults to `true` if processingMode is 1.1, otherwise `false`.
375
+ # @yield jsonld
376
+ # @yieldparam [Hash] jsonld
377
+ # The framed JSON-LD document
378
+ # @yieldreturn [Object] returned object
379
+ # @return [Object, Hash]
380
+ # If a block is given, the result of evaluating the block is returned, otherwise, the framed JSON-LD document
381
+ # @raise [InvalidFrame]
382
+ # @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
383
+ def self.frame(input, frame, expanded: false, serializer: nil, **options)
384
+ result = nil
385
+ options = {
386
+ base: (RDF::URI(input) if input.is_a?(String)),
387
+ compactArrays: true,
388
+ compactToRelative: true,
389
+ embed: '@once',
390
+ explicit: false,
391
+ requireAll: false,
392
+ omitDefault: false
393
+ }.merge(options)
394
+
395
+ framing_state = {
396
+ graphMap: {},
397
+ graphStack: [],
398
+ subjectStack: [],
399
+ link: {},
400
+ embedded: false # False at the top-level
401
+ }
402
+
403
+ # de-reference frame to create the framing object
404
+ frame = case frame
405
+ when Hash then frame.dup
406
+ when IO, StringIO, String
407
+ remote_doc = loadRemoteDocument(frame,
408
+ profile: 'http://www.w3.org/ns/json-ld#frame',
409
+ requestProfile: 'http://www.w3.org/ns/json-ld#frame',
410
+ **options)
411
+ if remote_doc.document.is_a?(String)
412
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
413
+ MultiJson.load(remote_doc.document, **mj_opts)
414
+ else
415
+ remote_doc.document
416
+ end
417
+ end
386
418
 
387
- # de-reference frame to create the framing object
388
- frame = case frame
389
- when Hash then frame.dup
390
- when IO, StringIO, String
391
- remote_doc = loadRemoteDocument(frame,
392
- profile: 'http://www.w3.org/ns/json-ld#frame',
393
- requestProfile: 'http://www.w3.org/ns/json-ld#frame',
394
- **options)
395
- if remote_doc.document.is_a?(String)
396
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
397
- MultiJson.load(remote_doc.document, **mj_opts)
419
+ # Expand input to simplify processing
420
+ expanded_input = if expanded
421
+ input
398
422
  else
399
- remote_doc.document
423
+ API.expand(input, ordered: false, **options) do |res, base_iri|
424
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
425
+ res
426
+ end
400
427
  end
401
- end
402
428
 
403
- # Expand input to simplify processing
404
- expanded_input = expanded ? input : API.expand(input, ordered: false, **options) do |res, base_iri|
405
- options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
406
- res
407
- end
429
+ # Expand frame to simplify processing
430
+ expanded_frame = API.expand(frame, framing: true, ordered: false, **options)
408
431
 
409
- # Expand frame to simplify processing
410
- expanded_frame = API.expand(frame, framing: true, ordered: false, **options)
432
+ # Initialize input using frame as context
433
+ API.new(expanded_input, frame['@context'], no_default_base: true, **options) do
434
+ # log_debug(".frame") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
435
+ # log_debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE) rescue 'malformed json'}"}
411
436
 
412
- # Initialize input using frame as context
413
- API.new(expanded_input, frame['@context'], no_default_base: true, **options) do
414
- # log_debug(".frame") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
415
- # log_debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE) rescue 'malformed json'}"}
437
+ if %w[@first @last].include?(options[:embed]) && context.processingMode('json-ld-1.1')
438
+ if @options[:validate]
439
+ raise JSON::LD::JsonLdError::InvalidEmbedValue,
440
+ "#{options[:embed]} is not a valid value of @embed in 1.1 mode"
441
+ end
416
442
 
417
- if %w(@first @last).include?(options[:embed]) && context.processingMode('json-ld-1.1')
418
- raise JSON::LD::JsonLdError::InvalidEmbedValue, "#{options[:embed]} is not a valid value of @embed in 1.1 mode" if @options[:validate]
419
- warn "[DEPRECATION] #{options[:embed]} is not a valid value of @embed in 1.1 mode.\n"
420
- end
443
+ warn "[DEPRECATION] #{options[:embed]} is not a valid value of @embed in 1.1 mode.\n"
444
+ end
421
445
 
422
- # Set omitGraph option, if not present, based on processingMode
423
- unless options.key?(:omitGraph)
424
- options[:omitGraph] = context.processingMode('json-ld-1.1')
425
- end
446
+ # Set omitGraph option, if not present, based on processingMode
447
+ options[:omitGraph] = context.processingMode('json-ld-1.1') unless options.key?(:omitGraph)
426
448
 
427
- # Rename blank nodes recusively. Note that this does not create new blank node identifiers where none exist, which is performed in the node map generation algorithm.
428
- @value = rename_bnodes(@value)
449
+ # Rename blank nodes recusively. Note that this does not create new blank node identifiers where none exist, which is performed in the node map generation algorithm.
450
+ @value = rename_bnodes(@value)
429
451
 
430
- # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
431
- create_node_map(value, framing_state[:graphMap], active_graph: '@default')
452
+ # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
453
+ create_node_map(value, framing_state[:graphMap], active_graph: '@default')
432
454
 
433
- frame_keys = frame.keys.map {|k| context.expand_iri(k, vocab: true)}
434
- if frame_keys.include?('@graph')
435
- # If frame contains @graph, it matches the default graph.
436
- framing_state[:graph] = '@default'
437
- else
438
- # If frame does not contain @graph used the merged graph.
439
- framing_state[:graph] = '@merged'
440
- framing_state[:link]['@merged'] = {}
441
- framing_state[:graphMap]['@merged'] = merge_node_map_graphs(framing_state[:graphMap])
442
- end
455
+ frame_keys = frame.keys.map { |k| context.expand_iri(k, vocab: true) }
456
+ if frame_keys.include?('@graph')
457
+ # If frame contains @graph, it matches the default graph.
458
+ framing_state[:graph] = '@default'
459
+ else
460
+ # If frame does not contain @graph used the merged graph.
461
+ framing_state[:graph] = '@merged'
462
+ framing_state[:link]['@merged'] = {}
463
+ framing_state[:graphMap]['@merged'] = merge_node_map_graphs(framing_state[:graphMap])
464
+ end
443
465
 
444
- framing_state[:subjects] = framing_state[:graphMap][framing_state[:graph]]
466
+ framing_state[:subjects] = framing_state[:graphMap][framing_state[:graph]]
445
467
 
446
- result = []
447
- frame(framing_state, framing_state[:subjects].keys.opt_sort(ordered: @options[:ordered]), (expanded_frame.first || {}), parent: result, **options)
468
+ result = []
469
+ frame(framing_state, framing_state[:subjects].keys.opt_sort(ordered: @options[:ordered]),
470
+ (expanded_frame.first || {}), parent: result, **options)
448
471
 
449
- # Default to based on processinMode
450
- if !options.key?(:pruneBlankNodeIdentifiers)
451
- options[:pruneBlankNodeIdentifiers] = context.processingMode('json-ld-1.1')
452
- end
472
+ # Default to based on processinMode
473
+ unless options.key?(:pruneBlankNodeIdentifiers)
474
+ options[:pruneBlankNodeIdentifiers] = context.processingMode('json-ld-1.1')
475
+ end
453
476
 
454
- # Count blank node identifiers used in the document, if pruning
455
- if options[:pruneBlankNodeIdentifiers]
456
- bnodes_to_clear = count_blank_node_identifiers(result).collect {|k, v| k if v == 1}.compact
457
- result = prune_bnodes(result, bnodes_to_clear)
458
- end
477
+ # Count blank node identifiers used in the document, if pruning
478
+ if options[:pruneBlankNodeIdentifiers]
479
+ bnodes_to_clear = count_blank_node_identifiers(result).collect { |k, v| k if v == 1 }.compact
480
+ result = prune_bnodes(result, bnodes_to_clear)
481
+ end
459
482
 
460
- # Replace values with `@preserve` with the content of its entry.
461
- result = cleanup_preserve(result)
462
- # log_debug(".frame") {"expanded result: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
483
+ # Replace values with `@preserve` with the content of its entry.
484
+ result = cleanup_preserve(result)
485
+ # log_debug(".frame") {"expanded result: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
463
486
 
464
- # Compact result
465
- compacted = compact(result)
487
+ # Compact result
488
+ compacted = compact(result)
466
489
 
467
- # @replace `@null` with nil, compacting arrays
468
- compacted = cleanup_null(compacted)
469
- compacted = [compacted] unless options[:omitGraph] || compacted.is_a?(Array)
490
+ # @replace `@null` with nil, compacting arrays
491
+ compacted = cleanup_null(compacted)
492
+ compacted = [compacted] unless options[:omitGraph] || compacted.is_a?(Array)
470
493
 
471
- # Add the given context to the output
472
- result = if !compacted.is_a?(Array)
473
- compacted
474
- else
475
- kwgraph = context.compact_iri('@graph', vocab: true)
476
- {kwgraph => compacted}
494
+ # Add the given context to the output
495
+ result = if compacted.is_a?(Array)
496
+ kwgraph = context.compact_iri('@graph', vocab: true)
497
+ { kwgraph => compacted }
498
+ else
499
+ compacted
500
+ end
501
+ # Only add context if one was provided
502
+ result = context.serialize(provided_context: frame).merge(result) if frame['@context']
503
+
504
+ # log_debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
505
+ result
477
506
  end
478
- # Only add context if one was provided
479
- result = context.serialize(provided_context: frame).merge(result) if frame['@context']
480
-
481
- # log_debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
482
- result
483
- end
484
507
 
485
- result = serializer.call(result, **options) if serializer
486
- block_given? ? yield(result) : result
487
- end
508
+ result = serializer.call(result, **options) if serializer
509
+ block_given? ? yield(result) : result
510
+ end
488
511
 
489
- ##
490
- # Processes the input according to the RDF Conversion Algorithm, calling the provided callback for each triple generated.
491
- #
492
- # @param [String, #read, Hash, Array] input
493
- # The JSON-LD object to process when outputting statements.
494
- # @param [Boolean] expanded (false) Input is already expanded
495
- # @option options (see #initialize)
496
- # @option options [Boolean] :produceGeneralizedRdf (false)
497
- # If true, output will include statements having blank node predicates, otherwise they are dropped.
498
- # @raise [JsonLdError]
499
- # @yield statement
500
- # @yieldparam [RDF::Statement] statement
501
- # @return [RDF::Enumerable] set of statements, unless a block is given.
502
- def self.toRdf(input, expanded: false, **options, &block)
503
- unless block_given?
504
- results = []
505
- results.extend(RDF::Enumerable)
506
- self.toRdf(input, expanded: expanded, **options) do |stmt|
507
- results << stmt
512
+ ##
513
+ # Processes the input according to the RDF Conversion Algorithm, calling the provided callback for each triple generated.
514
+ #
515
+ # @param [String, #read, Hash, Array] input
516
+ # The JSON-LD object to process when outputting statements.
517
+ # @param [Boolean] expanded (false) Input is already expanded
518
+ # @option options (see #initialize)
519
+ # @option options [Boolean] :produceGeneralizedRdf (false)
520
+ # If true, output will include statements having blank node predicates, otherwise they are dropped.
521
+ # @raise [JsonLdError]
522
+ # @yield statement
523
+ # @yieldparam [RDF::Statement] statement
524
+ # @return [RDF::Enumerable] set of statements, unless a block is given.
525
+ def self.toRdf(input, expanded: false, **options)
526
+ unless block_given?
527
+ results = []
528
+ results.extend(RDF::Enumerable)
529
+ toRdf(input, expanded: expanded, **options) do |stmt|
530
+ results << stmt
531
+ end
532
+ return results
508
533
  end
509
- return results
510
- end
511
534
 
512
- options = {
513
- extractAllScripts: true,
514
- }.merge(options)
535
+ options = {
536
+ extractAllScripts: true
537
+ }.merge(options)
515
538
 
516
- # Flatten input to simplify processing
517
- flattened_input = API.flatten(input, nil, expanded: expanded, ordered: false, **options)
539
+ # Flatten input to simplify processing
540
+ flattened_input = API.flatten(input, nil, expanded: expanded, ordered: false, **options)
518
541
 
519
- API.new(flattened_input, nil, **options) do
520
- # 1) Perform the Expansion Algorithm on the JSON-LD input.
521
- # This removes any existing context to allow the given context to be cleanly applied.
522
- # log_debug(".toRdf") {"flattened input: #{flattened_input.to_json(JSON_STATE) rescue 'malformed json'}"}
542
+ API.new(flattened_input, nil, **options) do
543
+ # 1) Perform the Expansion Algorithm on the JSON-LD input.
544
+ # This removes any existing context to allow the given context to be cleanly applied.
545
+ # log_debug(".toRdf") {"flattened input: #{flattened_input.to_json(JSON_STATE) rescue 'malformed json'}"}
523
546
 
524
- # Recurse through input
525
- flattened_input.each do |node|
526
- item_to_rdf(node) do |statement|
527
- next if statement.predicate.node? && !options[:produceGeneralizedRdf]
547
+ # Recurse through input
548
+ flattened_input.each do |node|
549
+ item_to_rdf(node) do |statement|
550
+ next if statement.predicate.node? && !options[:produceGeneralizedRdf]
528
551
 
529
- # Drop invalid statements (other than IRIs)
530
- unless statement.valid_extended?
531
- # log_debug(".toRdf") {"drop invalid statement: #{statement.to_nquads}"}
532
- next
533
- end
552
+ # Drop invalid statements (other than IRIs)
553
+ unless statement.valid_extended?
554
+ # log_debug(".toRdf") {"drop invalid statement: #{statement.to_nquads}"}
555
+ next
556
+ end
534
557
 
535
- yield statement
558
+ yield statement
559
+ end
536
560
  end
537
561
  end
538
562
  end
539
- end
540
-
541
- ##
542
- # Take an ordered list of RDF::Statements and turn them into a JSON-LD document.
543
- #
544
- # The resulting `Array` is either returned or yielded, if a block is given.
545
- #
546
- # @param [RDF::Enumerable] input
547
- # @param [Boolean] useRdfType (false)
548
- # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
549
- # @param [Boolean] useNativeTypes (false) use native representations
550
- # @param [Proc] serializer (nil)
551
- # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
552
- # See {JSON::LD::API.serializer}.
553
- # @param [Hash{Symbol => Object}] options
554
- # @option options (see #initialize)
555
- # @yield jsonld
556
- # @yieldparam [Hash] jsonld
557
- # The JSON-LD document in expanded form
558
- # @yieldreturn [Object] returned object
559
- # @return [Object, Hash]
560
- # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
561
- def self.fromRdf(input, useRdfType: false, useNativeTypes: false, serializer: nil, **options, &block)
562
- result = nil
563
-
564
- API.new(nil, nil, **options) do
565
- result = from_statements(input,
566
- extendedRepresentation: options[:extendedRepresentation],
567
- useRdfType: useRdfType,
568
- useNativeTypes: useNativeTypes)
569
- end
570
563
 
571
- result = serializer.call(result, **options) if serializer
572
- block_given? ? yield(result) : result
573
- end
564
+ ##
565
+ # Take an ordered list of RDF::Statements and turn them into a JSON-LD document.
566
+ #
567
+ # The resulting `Array` is either returned or yielded, if a block is given.
568
+ #
569
+ # @param [RDF::Enumerable] input
570
+ # @param [Boolean] useRdfType (false)
571
+ # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
572
+ # @param [Boolean] useNativeTypes (false) use native representations
573
+ # @param [Proc] serializer (nil)
574
+ # A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
575
+ # See {JSON::LD::API.serializer}.
576
+ # @param [Hash{Symbol => Object}] options
577
+ # @option options (see #initialize)
578
+ # @yield jsonld
579
+ # @yieldparam [Hash] jsonld
580
+ # The JSON-LD document in expanded form
581
+ # @yieldreturn [Object] returned object
582
+ # @return [Object, Hash]
583
+ # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
584
+ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, serializer: nil, **options)
585
+ result = nil
586
+
587
+ API.new(nil, nil, **options) do
588
+ result = from_statements(input,
589
+ extendedRepresentation: options[:extendedRepresentation],
590
+ useRdfType: useRdfType,
591
+ useNativeTypes: useNativeTypes)
592
+ end
574
593
 
575
- ##
576
- # Uses built-in or provided documentLoader to retrieve a parsed document.
577
- #
578
- # @param [RDF::URI, String] url
579
- # @param [Regexp] allowed_content_types
580
- # A regular expression matching other content types allowed
581
- # beyond types for JSON and HTML.
582
- # @param [String, RDF::URI] base
583
- # Location to use as documentUrl instead of `url`.
584
- # @option options [Proc] :documentLoader
585
- # The callback of the loader to be used to retrieve remote documents and contexts.
586
- # @param [Boolean] extractAllScripts
587
- # If set to `true`, when extracting JSON-LD script elements from HTML, unless a specific fragment identifier is targeted, extracts all encountered JSON-LD script elements using an array form, if necessary.
588
- # @param [String] profile
589
- # When the resulting `contentType` is `text/html` or `application/xhtml+xml`, this option determines the profile to use for selecting a JSON-LD script elements.
590
- # @param [String] requestProfile
591
- # One or more IRIs to use in the request as a profile parameter.
592
- # @param [Boolean] validate (false)
593
- # Allow only appropriate content types
594
- # @param [Hash<Symbol => Object>] options
595
- # @yield remote_document
596
- # @yieldparam [RemoteDocumentRemoteDocument, RDF::Util::File::RemoteDocument] remote_document
597
- # @yieldreturn [Object] returned object
598
- # @return [Object, RemoteDocument]
599
- # If a block is given, the result of evaluating the block is returned, otherwise, the retrieved remote document and context information unless block given
600
- # @raise [JsonLdError]
601
- def self.loadRemoteDocument(url,
602
- allowed_content_types: nil,
603
- base: nil,
604
- documentLoader: nil,
605
- extractAllScripts: false,
606
- profile: nil,
607
- requestProfile: nil,
608
- validate: false,
609
- **options)
610
- documentLoader ||= self.method(:documentLoader)
611
- options = OPEN_OPTS.merge(options)
612
- if requestProfile
613
- # Add any request profile
614
- options[:headers]['Accept'] = options[:headers]['Accept'].sub('application/ld+json,', "application/ld+json;profile=#{requestProfile}, application/ld+json;q=0.9,")
594
+ result = serializer.call(result, **options) if serializer
595
+ block_given? ? yield(result) : result
615
596
  end
616
- documentLoader.call(url, **options) do |remote_doc|
617
- case remote_doc
618
- when RDF::Util::File::RemoteDocument
619
- # Convert to RemoteDocument
620
- context_url = if remote_doc.content_type != 'application/ld+json' &&
621
- (remote_doc.content_type == 'application/json' ||
622
- remote_doc.content_type.to_s.match?(%r(application/\w+\+json)))
623
- # Get context link(s)
624
- # Note, we can't simply use #find_link, as we need to detect multiple
625
- links = remote_doc.links.links.select do |link|
626
- link.attr_pairs.include?(LINK_REL_CONTEXT)
597
+
598
+ ##
599
+ # Uses built-in or provided documentLoader to retrieve a parsed document.
600
+ #
601
+ # @param [RDF::URI, String] url
602
+ # @param [Regexp] allowed_content_types
603
+ # A regular expression matching other content types allowed
604
+ # beyond types for JSON and HTML.
605
+ # @param [String, RDF::URI] base
606
+ # Location to use as documentUrl instead of `url`.
607
+ # @option options [Proc] :documentLoader
608
+ # The callback of the loader to be used to retrieve remote documents and contexts.
609
+ # @param [Boolean] extractAllScripts
610
+ # If set to `true`, when extracting JSON-LD script elements from HTML, unless a specific fragment identifier is targeted, extracts all encountered JSON-LD script elements using an array form, if necessary.
611
+ # @param [String] profile
612
+ # When the resulting `contentType` is `text/html` or `application/xhtml+xml`, this option determines the profile to use for selecting a JSON-LD script elements.
613
+ # @param [String] requestProfile
614
+ # One or more IRIs to use in the request as a profile parameter.
615
+ # @param [Boolean] validate (false)
616
+ # Allow only appropriate content types
617
+ # @param [Hash<Symbol => Object>] options
618
+ # @yield remote_document
619
+ # @yieldparam [RemoteDocumentRemoteDocument, RDF::Util::File::RemoteDocument] remote_document
620
+ # @yieldreturn [Object] returned object
621
+ # @return [Object, RemoteDocument]
622
+ # If a block is given, the result of evaluating the block is returned, otherwise, the retrieved remote document and context information unless block given
623
+ # @raise [JsonLdError]
624
+ def self.loadRemoteDocument(url,
625
+ allowed_content_types: nil,
626
+ base: nil,
627
+ documentLoader: nil,
628
+ extractAllScripts: false,
629
+ profile: nil,
630
+ requestProfile: nil,
631
+ validate: false,
632
+ **options)
633
+ documentLoader ||= method(:documentLoader)
634
+ options = OPEN_OPTS.merge(options)
635
+ if requestProfile
636
+ # Add any request profile
637
+ options[:headers]['Accept'] =
638
+ options[:headers]['Accept'].sub('application/ld+json,',
639
+ "application/ld+json;profile=#{requestProfile}, application/ld+json;q=0.9,")
640
+ end
641
+ documentLoader.call(url, **options) do |remote_doc|
642
+ case remote_doc
643
+ when RDF::Util::File::RemoteDocument
644
+ # Convert to RemoteDocument
645
+ context_url = if remote_doc.content_type != 'application/ld+json' &&
646
+ (remote_doc.content_type == 'application/json' ||
647
+ remote_doc.content_type.to_s.match?(%r{application/\w+\+json}))
648
+ # Get context link(s)
649
+ # Note, we can't simply use #find_link, as we need to detect multiple
650
+ links = remote_doc.links.links.select do |link|
651
+ link.attr_pairs.include?(LINK_REL_CONTEXT)
652
+ end
653
+ if links.length > 1
654
+ raise JSON::LD::JsonLdError::MultipleContextLinkHeaders,
655
+ "expected at most 1 Link header with rel=jsonld:context, got #{links.length}"
656
+ end
657
+ Array(links.first).first
627
658
  end
628
- raise JSON::LD::JsonLdError::MultipleContextLinkHeaders,
629
- "expected at most 1 Link header with rel=jsonld:context, got #{links.length}" if links.length > 1
630
- Array(links.first).first
631
- end
632
659
 
633
- # If content-type is not application/ld+json, nor any other +json and a link with rel=alternate and type='application/ld+json' is found, use that instead
634
- alternate = !remote_doc.content_type.match?(%r(application/(\w*\+)?json)) && remote_doc.links.links.detect do |link|
635
- link.attr_pairs.include?(LINK_REL_ALTERNATE) &&
636
- link.attr_pairs.include?(LINK_TYPE_JSONLD)
637
- end
660
+ # If content-type is not application/ld+json, nor any other +json and a link with rel=alternate and type='application/ld+json' is found, use that instead
661
+ alternate = !remote_doc.content_type.match?(%r{application/(\w*\+)?json}) && remote_doc.links.links.detect do |link|
662
+ link.attr_pairs.include?(LINK_REL_ALTERNATE) &&
663
+ link.attr_pairs.include?(LINK_TYPE_JSONLD)
664
+ end
638
665
 
639
- remote_doc = if alternate
640
- # Load alternate relative to URL
641
- loadRemoteDocument(RDF::URI(url).join(alternate.href),
666
+ remote_doc = if alternate
667
+ # Load alternate relative to URL
668
+ loadRemoteDocument(RDF::URI(url).join(alternate.href),
642
669
  extractAllScripts: extractAllScripts,
643
670
  profile: profile,
644
671
  requestProfile: requestProfile,
645
672
  validate: validate,
646
673
  base: base,
647
- **options)
674
+ **options)
675
+ else
676
+ RemoteDocument.new(remote_doc.read,
677
+ documentUrl: remote_doc.base_uri,
678
+ contentType: remote_doc.content_type,
679
+ contextUrl: context_url)
680
+ end
681
+ when RemoteDocument
682
+ # Pass through
648
683
  else
649
- RemoteDocument.new(remote_doc.read,
650
- documentUrl: remote_doc.base_uri,
651
- contentType: remote_doc.content_type,
652
- contextUrl: context_url)
684
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed,
685
+ "unknown result from documentLoader: #{remote_doc.class}"
653
686
  end
654
- when RemoteDocument
655
- # Pass through
656
- else
657
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "unknown result from documentLoader: #{remote_doc.class}"
658
- end
659
687
 
660
- # Use specified document location
661
- remote_doc.documentUrl = base if base
662
-
663
- # Parse any HTML
664
- if remote_doc.document.is_a?(String)
665
- remote_doc.document = case remote_doc.contentType
666
- when 'text/html', 'application/xhtml+xml'
667
- load_html(remote_doc.document,
668
- url: remote_doc.documentUrl,
669
- extractAllScripts: extractAllScripts,
670
- profile: profile,
671
- **options) do |base|
672
- remote_doc.documentUrl = base
688
+ # Use specified document location
689
+ remote_doc.documentUrl = base if base
690
+
691
+ # Parse any HTML
692
+ if remote_doc.document.is_a?(String)
693
+ remote_doc.document = case remote_doc.contentType
694
+ when 'text/html', 'application/xhtml+xml'
695
+ load_html(remote_doc.document,
696
+ url: remote_doc.documentUrl,
697
+ extractAllScripts: extractAllScripts,
698
+ profile: profile,
699
+ **options) do |base|
700
+ remote_doc.documentUrl = base
701
+ end
702
+ else
703
+ validate_input(remote_doc.document, url: remote_doc.documentUrl) if validate
704
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
705
+ MultiJson.load(remote_doc.document, **mj_opts)
673
706
  end
674
- else
675
- validate_input(remote_doc.document, url: remote_doc.documentUrl) if validate
676
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
677
- MultiJson.load(remote_doc.document, **mj_opts)
678
707
  end
679
- end
680
708
 
681
- if remote_doc.contentType && validate
682
- raise IOError, "url: #{url}, contentType: #{remote_doc.contentType}" unless
683
- remote_doc.contentType.match?(/application\/(.+\+)?json|text\/html|application\/xhtml\+xml/) ||
684
- (allowed_content_types && remote_doc.contentType.match?(allowed_content_types))
709
+ if remote_doc.contentType && validate && !(remote_doc.contentType.match?(%r{application/(.+\+)?json|text/html|application/xhtml\+xml}) ||
710
+ (allowed_content_types && remote_doc.contentType.match?(allowed_content_types)))
711
+ raise IOError, "url: #{url}, contentType: #{remote_doc.contentType}"
712
+ end
713
+
714
+ block_given? ? yield(remote_doc) : remote_doc
685
715
  end
686
- block_given? ? yield(remote_doc) : remote_doc
716
+ rescue IOError, MultiJson::ParseError => e
717
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, e.message
687
718
  end
688
- rescue IOError, MultiJson::ParseError => e
689
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, e.message
690
- end
691
719
 
692
- ##
693
- # Default document loader.
694
- # @param [RDF::URI, String] url
695
- # @param [Boolean] extractAllScripts
696
- # If set to `true`, when extracting JSON-LD script elements from HTML, unless a specific fragment identifier is targeted, extracts all encountered JSON-LD script elements using an array form, if necessary.
697
- # @param [String] profile
698
- # When the resulting `contentType` is `text/html` or `application/xhtml+xml`, this option determines the profile to use for selecting a JSON-LD script elements.
699
- # @param [String] requestProfile
700
- # One or more IRIs to use in the request as a profile parameter.
701
- # @param [Hash<Symbol => Object>] options
702
- # @yield remote_document
703
- # @yieldparam [RemoteDocument, RDF::Util::File::RemoteDocument] remote_document
704
- # @raise [IOError]
705
- def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProfile: nil, **options, &block)
706
- case url
707
- when IO, StringIO
708
- base_uri = options[:base]
709
- base_uri ||= url.base_uri if url.respond_to?(:base_uri)
710
- content_type = options[:content_type]
711
- content_type ||= url.content_type if url.respond_to?(:content_type)
712
- context_url = if url.respond_to?(:links) && url.links &&
713
- (content_type == 'application/json' || content_type.match?(%r(application/(^ld)+json)))
714
- link = url.links.find_link(LINK_REL_CONTEXT)
715
- link.href if link
716
- end
720
+ ##
721
+ # Default document loader.
722
+ # @param [RDF::URI, String] url
723
+ # @param [Boolean] extractAllScripts
724
+ # If set to `true`, when extracting JSON-LD script elements from HTML, unless a specific fragment identifier is targeted, extracts all encountered JSON-LD script elements using an array form, if necessary.
725
+ # @param [String] profile
726
+ # When the resulting `contentType` is `text/html` or `application/xhtml+xml`, this option determines the profile to use for selecting a JSON-LD script elements.
727
+ # @param [String] requestProfile
728
+ # One or more IRIs to use in the request as a profile parameter.
729
+ # @param [Hash<Symbol => Object>] options
730
+ # @yield remote_document
731
+ # @yieldparam [RemoteDocument, RDF::Util::File::RemoteDocument] remote_document
732
+ # @raise [IOError]
733
+ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProfile: nil, **options, &block)
734
+ case url
735
+ when IO, StringIO
736
+ base_uri = options[:base]
737
+ base_uri ||= url.base_uri if url.respond_to?(:base_uri)
738
+ content_type = options[:content_type]
739
+ content_type ||= url.content_type if url.respond_to?(:content_type)
740
+ context_url = if url.respond_to?(:links) && url.links &&
741
+ (content_type == 'application/json' || content_type.match?(%r{application/(^ld)+json}))
742
+ link = url.links.find_link(LINK_REL_CONTEXT)
743
+ link&.href
744
+ end
717
745
 
718
- block.call(RemoteDocument.new(url.read,
719
- documentUrl: base_uri,
720
- contentType: content_type,
721
- contextUrl: context_url))
722
- else
723
- RDF::Util::File.open_file(url, **options, &block)
746
+ yield(RemoteDocument.new(url.read,
747
+ documentUrl: base_uri,
748
+ contentType: content_type,
749
+ contextUrl: context_url))
750
+ else
751
+ RDF::Util::File.open_file(url, **options, &block)
752
+ end
724
753
  end
725
- end
726
754
 
727
- # Add class method aliases for backwards compatibility
728
- class << self
729
- alias :toRDF :toRdf
730
- alias :fromRDF :fromRdf
731
- end
755
+ # Add class method aliases for backwards compatibility
756
+ class << self
757
+ alias toRDF toRdf
758
+ alias fromRDF fromRdf
759
+ end
732
760
 
733
- ##
734
- # Load one or more script tags from an HTML source.
735
- # Unescapes and uncomments input, returns the internal representation
736
- # Yields document base
737
- # @param [String] input
738
- # @param [String] url Original URL
739
- # @param [:nokogiri, :rexml] library (nil)
740
- # @param [Boolean] extractAllScripts (false)
741
- # @param [Boolean] profile (nil) Optional priortized profile when loading a single script by type.
742
- # @param [Hash{Symbol => Object}] options
743
- def self.load_html(input, url:,
761
+ ##
762
+ # Load one or more script tags from an HTML source.
763
+ # Unescapes and uncomments input, returns the internal representation
764
+ # Yields document base
765
+ # @param [String] input
766
+ # @param [String] url Original URL
767
+ # @param [:nokogiri, :rexml] library (nil)
768
+ # @param [Boolean] extractAllScripts (false)
769
+ # @param [Boolean] profile (nil) Optional priortized profile when loading a single script by type.
770
+ # @param [Hash{Symbol => Object}] options
771
+ def self.load_html(input, url:,
744
772
  library: nil,
745
773
  extractAllScripts: false,
746
774
  profile: nil,
747
775
  **options)
748
776
 
749
- if input.is_a?(String)
750
- library ||= begin
751
- require 'nokogiri'
752
- :nokogiri
753
- rescue LoadError
754
- :rexml
755
- end
756
- require "json/ld/html/#{library}"
777
+ if input.is_a?(String)
778
+ library ||= begin
779
+ require 'nokogiri'
780
+ :nokogiri
781
+ rescue LoadError
782
+ :rexml
783
+ end
784
+ require "json/ld/html/#{library}"
757
785
 
758
- # Parse HTML using the appropriate library
759
- implementation = case library
760
- when :nokogiri then Nokogiri
761
- when :rexml then REXML
762
- end
763
- self.extend(implementation)
786
+ # Parse HTML using the appropriate library
787
+ implementation = case library
788
+ when :nokogiri then Nokogiri
789
+ when :rexml then REXML
790
+ end
791
+ extend(implementation)
764
792
 
765
- input = begin
766
- self.send("initialize_html_#{library}".to_sym, input, **options)
767
- rescue
768
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Malformed HTML document: #{$!.message}"
769
- end
793
+ input = begin
794
+ send("initialize_html_#{library}".to_sym, input, **options)
795
+ rescue StandardError
796
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Malformed HTML document: #{$ERROR_INFO.message}"
797
+ end
770
798
 
771
- # Potentially update options[:base]
772
- if html_base = input.at_xpath("/html/head/base/@href")
773
- base = RDF::URI(url) if url
774
- html_base = RDF::URI(html_base)
775
- html_base = base.join(html_base) if base
776
- yield html_base
799
+ # Potentially update options[:base]
800
+ if (html_base = input.at_xpath("/html/head/base/@href"))
801
+ base = RDF::URI(url) if url
802
+ html_base = RDF::URI(html_base)
803
+ html_base = base.join(html_base) if base
804
+ yield html_base
805
+ end
777
806
  end
778
- end
779
807
 
780
- url = RDF::URI.parse(url)
781
- if url.fragment
782
- id = CGI.unescape(url.fragment)
783
- # Find script with an ID based on that fragment.
784
- element = input.at_xpath("//script[@id='#{id}']")
785
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found with id=#{id}" unless element
786
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Script tag has type=#{element.attributes['type']}" unless element.attributes['type'].to_s.start_with?('application/ld+json')
787
- content = element.inner_html
788
- validate_input(content, url: url) if options[:validate]
789
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
790
- MultiJson.load(content, **mj_opts)
791
- elsif extractAllScripts
792
- res = []
793
- elements = if profile
794
- es = input.xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]")
795
- # If no profile script, just take a single script without profile
796
- es = [input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")].compact if es.empty?
797
- es
798
- else
799
- input.xpath("//script[starts-with(@type, 'application/ld+json')]")
800
- end
801
- elements.each do |element|
808
+ url = RDF::URI.parse(url)
809
+ if url.fragment
810
+ id = CGI.unescape(url.fragment)
811
+ # Find script with an ID based on that fragment.
812
+ element = input.at_xpath("//script[@id='#{id}']")
813
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found with id=#{id}" unless element
814
+
815
+ unless element.attributes['type'].to_s.start_with?('application/ld+json')
816
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed,
817
+ "Script tag has type=#{element.attributes['type']}"
818
+ end
819
+
802
820
  content = element.inner_html
803
821
  validate_input(content, url: url) if options[:validate]
804
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
805
- r = MultiJson.load(content, **mj_opts)
806
- if r.is_a?(Hash)
807
- res << r
808
- elsif r.is_a?(Array)
809
- res = res.concat(r)
822
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
823
+ MultiJson.load(content, **mj_opts)
824
+ elsif extractAllScripts
825
+ res = []
826
+ elements = if profile
827
+ es = input.xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]")
828
+ # If no profile script, just take a single script without profile
829
+ es = [input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")].compact if es.empty?
830
+ es
831
+ else
832
+ input.xpath("//script[starts-with(@type, 'application/ld+json')]")
833
+ end
834
+ elements.each do |element|
835
+ content = element.inner_html
836
+ validate_input(content, url: url) if options[:validate]
837
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
838
+ r = MultiJson.load(content, **mj_opts)
839
+ if r.is_a?(Hash)
840
+ res << r
841
+ elsif r.is_a?(Array)
842
+ res.concat(r)
843
+ end
810
844
  end
845
+ res
846
+ else
847
+ # Find the first script with type application/ld+json.
848
+ element = input.at_xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]") if profile
849
+ element ||= input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")
850
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
851
+
852
+ content = element.inner_html
853
+ validate_input(content, url: url) if options[:validate]
854
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
855
+ MultiJson.load(content, **mj_opts)
811
856
  end
812
- res
813
- else
814
- # Find the first script with type application/ld+json.
815
- element = input.at_xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]") if profile
816
- element ||= input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")
817
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
818
- content = element.inner_html
819
- validate_input(content, url: url) if options[:validate]
820
- mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
821
- MultiJson.load(content, **mj_opts)
857
+ rescue MultiJson::ParseError => e
858
+ raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
822
859
  end
823
- rescue MultiJson::ParseError => e
824
- raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
825
- end
826
860
 
827
- ##
828
- # The default serializer for serialzing Ruby Objects to JSON.
829
- #
830
- # Defaults to `MultiJson.dump`
831
- #
832
- # @param [Object] object
833
- # @param [Array<Object>] args
834
- # other arguments that may be passed for some specific implementation.
835
- # @param [Hash<Symbol, Object>] options
836
- # options passed from the invoking context.
837
- # @option options [Object] :serializer_opts (JSON_STATE)
838
- def self.serializer(object, *args, **options)
839
- serializer_opts = options.fetch(:serializer_opts, JSON_STATE)
840
- MultiJson.dump(object, serializer_opts)
841
- end
861
+ ##
862
+ # The default serializer for serialzing Ruby Objects to JSON.
863
+ #
864
+ # Defaults to `MultiJson.dump`
865
+ #
866
+ # @param [Object] object
867
+ # @param [Array<Object>] args
868
+ # other arguments that may be passed for some specific implementation.
869
+ # @param [Hash<Symbol, Object>] options
870
+ # options passed from the invoking context.
871
+ # @option options [Object] :serializer_opts (JSON_STATE)
872
+ def self.serializer(object, *_args, **options)
873
+ serializer_opts = options.fetch(:serializer_opts, JSON_STATE)
874
+ MultiJson.dump(object, serializer_opts)
875
+ end
842
876
 
843
- ##
844
- # Validate JSON using JsonLint, if loaded
845
- private
846
- def self.validate_input(input, url:)
847
- return unless defined?(JsonLint)
848
- jsonlint = JsonLint::Linter.new
849
- input = StringIO.new(input) unless input.respond_to?(:read)
850
- unless jsonlint.check_stream(input)
851
- raise JsonLdError::LoadingDocumentFailed, "url: #{url}\n" + jsonlint.errors[''].join("\n")
877
+ ##
878
+ # Validate JSON using JsonLint, if loaded
879
+
880
+ def self.validate_input(input, url:)
881
+ return unless defined?(JsonLint)
882
+
883
+ jsonlint = JsonLint::Linter.new
884
+ input = StringIO.new(input) unless input.respond_to?(:read)
885
+ unless jsonlint.check_stream(input)
886
+ raise JsonLdError::LoadingDocumentFailed, "url: #{url}\n" + jsonlint.errors[''].join("\n")
887
+ end
888
+
889
+ input.rewind
852
890
  end
853
- input.rewind
854
- end
855
891
 
856
- ##
857
- # A {RemoteDocument} is returned from a {documentLoader}.
858
- class RemoteDocument
859
- # The final URL of the loaded document. This is important to handle HTTP redirects properly.
860
- # @return [String]
861
- attr_accessor :documentUrl
862
-
863
- # The Content-Type of the loaded document, exclusive of any optional parameters.
864
- # @return [String]
865
- attr_reader :contentType
866
-
867
- # @return [String]
868
- # The URL of a remote context as specified by an HTTP Link header with rel=`http://www.w3.org/ns/json-ld#context`
869
- attr_accessor :contextUrl
870
-
871
- # The parsed retrieved document.
872
- # @return [Array<Hash>, Hash]
873
- attr_accessor :document
874
-
875
- # The value of any profile parameter retrieved as part of the original contentType.
876
- # @return [String]
877
- attr_accessor :profile
878
-
879
- # @param [RDF::Util::File::RemoteDocument] document
880
- # @param [String] documentUrl
881
- # The final URL of the loaded document. This is important to handle HTTP redirects properly.
882
- # @param [String] contentType
883
- # The Content-Type of the loaded document, exclusive of any optional parameters.
884
- # @param [String] contextUrl
885
- # The URL of a remote context as specified by an HTTP Link header with rel=`http://www.w3.org/ns/json-ld#context`
886
- # @param [String] profile
887
- # The value of any profile parameter retrieved as part of the original contentType.
888
- # @option options [Hash{Symbol => Object}] options
889
- def initialize(document, documentUrl: nil, contentType: nil, contextUrl: nil, profile: nil, **options)
890
- @document = document
891
- @documentUrl = documentUrl || options[:base_uri]
892
- @contentType = contentType || options[:content_type]
893
- @contextUrl = contextUrl
894
- @profile = profile
892
+ ##
893
+ # A {RemoteDocument} is returned from a {documentLoader}.
894
+ class RemoteDocument
895
+ # The final URL of the loaded document. This is important to handle HTTP redirects properly.
896
+ # @return [String]
897
+ attr_accessor :documentUrl
898
+
899
+ # The Content-Type of the loaded document, exclusive of any optional parameters.
900
+ # @return [String]
901
+ attr_reader :contentType
902
+
903
+ # @return [String]
904
+ # The URL of a remote context as specified by an HTTP Link header with rel=`http://www.w3.org/ns/json-ld#context`
905
+ attr_accessor :contextUrl
906
+
907
+ # The parsed retrieved document.
908
+ # @return [Array<Hash>, Hash]
909
+ attr_accessor :document
910
+
911
+ # The value of any profile parameter retrieved as part of the original contentType.
912
+ # @return [String]
913
+ attr_accessor :profile
914
+
915
+ # @param [RDF::Util::File::RemoteDocument] document
916
+ # @param [String] documentUrl
917
+ # The final URL of the loaded document. This is important to handle HTTP redirects properly.
918
+ # @param [String] contentType
919
+ # The Content-Type of the loaded document, exclusive of any optional parameters.
920
+ # @param [String] contextUrl
921
+ # The URL of a remote context as specified by an HTTP Link header with rel=`http://www.w3.org/ns/json-ld#context`
922
+ # @param [String] profile
923
+ # The value of any profile parameter retrieved as part of the original contentType.
924
+ # @option options [Hash{Symbol => Object}] options
925
+ def initialize(document, documentUrl: nil, contentType: nil, contextUrl: nil, profile: nil, **options)
926
+ @document = document
927
+ @documentUrl = documentUrl || options[:base_uri]
928
+ @contentType = contentType || options[:content_type]
929
+ @contextUrl = contextUrl
930
+ @profile = profile
931
+ end
895
932
  end
896
933
  end
897
934
  end
898
935
  end
899
-