Skip to content

Commit 27f8065

Browse files
committed
Keep document base out of context, and pass in through other algorithms. This allows for improved caching of initial contexts.
1 parent 2fbcdb7 commit 27f8065

File tree

9 files changed

+401
-260
lines changed

9 files changed

+401
-260
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ group :development do
3434
gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop"
3535
gem 'fasterer'
3636
gem 'earl-report'
37-
gem 'ruby-prof'
37+
gem 'ruby-prof', platforms: :mri
3838
end
3939

4040
group :development, :test do

lib/json/ld/api.rb

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def initialize(input, context, rename_bnodes: true, unique_bnodes: false, **opti
130130

131131
# If not provided, first use context from document, or from a Link header
132132
context ||= context_ref || {}
133-
@context = Context.parse(context || {}, **@options)
133+
@context = Context.parse(context, **@options)
134134

135135
if block_given?
136136
case block.arity
@@ -163,9 +163,10 @@ def initialize(input, context, rename_bnodes: true, unique_bnodes: false, **opti
163163
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
164164
# @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
165165
def self.expand(input, framing: false, **options, &block)
166-
result, doc_base = nil
166+
result = doc_base = nil
167167
API.new(input, options[:expandContext], **options) do
168168
result = self.expand(self.value, nil, self.context,
169+
base: (RDF::URI(@options[:base]) if @options[:base]),
169170
ordered: @options[:ordered],
170171
framing: framing)
171172
doc_base = @options[:base]
@@ -224,15 +225,17 @@ def self.compact(input, context, expanded: false, **options)
224225

225226
API.new(expanded_input, context, no_default_base: true, **options) do
226227
log_debug(".compact") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
227-
result = compact(value, ordered: @options[:ordered])
228+
result = compact(value,
229+
base: (RDF::URI(@options[:base]) if @options[:base]),
230+
ordered: options[:ordered])
228231

229232
# xxx) Add the given context to the output
230-
ctx = self.context.serialize
233+
ctx = self.context.serialize(provided_context: context)
231234
if result.is_a?(Array)
232235
kwgraph = self.context.compact_iri('@graph', vocab: true)
233236
result = result.empty? ? {} : {kwgraph => result}
234237
end
235-
result = ctx.merge(result) unless ctx.empty?
238+
result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
236239
end
237240
block_given? ? yield(result) : result
238241
end
@@ -294,9 +297,14 @@ def self.flatten(input, context, expanded: false, **options)
294297

295298
if context && !flattened.empty?
296299
# 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.
297-
compacted = as_array(compact(flattened, ordered: @options[:ordered]))
300+
compacted = as_array(
301+
compact(flattened,
302+
base: (RDF::URI(options[:base]) if options[:base]),
303+
ordered: options[:ordered]))
298304
kwgraph = self.context.compact_iri('@graph')
299-
flattened = self.context.serialize.merge(kwgraph => compacted)
305+
flattened = self.context.
306+
serialize(provided_context: context).
307+
merge(kwgraph => compacted)
300308
end
301309
end
302310

@@ -335,7 +343,7 @@ def self.flatten(input, context, expanded: false, **options)
335343
def self.frame(input, frame, expanded: false, **options)
336344
result = nil
337345
options = {
338-
base: (input if input.is_a?(String)),
346+
base: (RDF::URI(input) if input.is_a?(String)),
339347
compactArrays: true,
340348
compactToRelative: true,
341349
embed: '@once',
@@ -426,19 +434,24 @@ def self.frame(input, frame, expanded: false, **options)
426434
log_debug(".frame") {"expanded result: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
427435

428436
# Compact result
429-
compacted = compact(result, ordered: @options[:ordered])
437+
compacted = compact(result,
438+
base: (RDF::URI(options[:base]) if options[:base]),
439+
ordered: options[:ordered])
430440

431441
# @replace `@null` with nil, compacting arrays
432442
compacted = cleanup_null(compacted)
433443
compacted = [compacted] unless options[:omitGraph] || compacted.is_a?(Array)
434444

435445
# Add the given context to the output
436446
result = if !compacted.is_a?(Array)
437-
context.serialize.merge(compacted)
447+
compacted
438448
else
439449
kwgraph = context.compact_iri('@graph')
440-
context.serialize.merge({kwgraph => compacted})
450+
{kwgraph => compacted}
441451
end
452+
# Only add context if one was provided
453+
result = context.serialize(provided_context: frame).merge(result) if frame['@context']
454+
442455
log_debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
443456
result
444457
end
@@ -520,9 +533,10 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &bl
520533

521534
API.new(nil, nil, **options) do
522535
result = from_statements(input,
536+
base: (RDF::URI(options[:base]) if options[:base]),
537+
ordered: @options[:ordered],
523538
useRdfType: useRdfType,
524-
useNativeTypes: useNativeTypes,
525-
ordered: @options[:ordered])
539+
useNativeTypes: useNativeTypes)
526540
end
527541

528542
block_given? ? yield(result) : result
@@ -532,16 +546,18 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &bl
532546
# Uses built-in or provided documentLoader to retrieve a parsed document.
533547
#
534548
# @param [RDF::URI, String] url
549+
# @param [String, RDF::URI] base
550+
# Location to use as documentUrl instead of `url`.
551+
# @option options [Proc] :documentLoader
552+
# The callback of the loader to be used to retrieve remote documents and contexts.
535553
# @param [Boolean] extractAllScripts
536554
# 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.
537555
# @param [String] profile
538556
# 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.
539557
# @param [String] requestProfile
540558
# One or more IRIs to use in the request as a profile parameter.
541-
# @param [Boolean] validate
559+
# @param [Boolean] validate (false)
542560
# Allow only appropriate content types
543-
# @param [String, RDF::URI] base
544-
# Location to use as documentUrl instead of `url`.
545561
# @param [Hash<Symbol => Object>] options
546562
# @yield remote_document
547563
# @yieldparam [RemoteDocumentRemoteDocument, RDF::Util::File::RemoteDocument] remote_document
@@ -550,13 +566,14 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &bl
550566
# If a block is given, the result of evaluating the block is returned, otherwise, the retrieved remote document and context information unless block given
551567
# @raise [JsonLdError]
552568
def self.loadRemoteDocument(url,
569+
base: nil,
570+
documentLoader: nil,
553571
extractAllScripts: false,
554572
profile: nil,
555573
requestProfile: nil,
556574
validate: false,
557-
base: nil,
558575
**options)
559-
documentLoader = options.fetch(:documentLoader, self.method(:documentLoader))
576+
documentLoader ||= self.method(:documentLoader)
560577
options = OPEN_OPTS.merge(options)
561578
if requestProfile
562579
# Add any request profile

lib/json/ld/compact.rb

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ module Compact
1212
# This algorithm compacts a JSON-LD document, such that the given context is applied. This must result in shortening any applicable IRIs to terms or compact IRIs, any applicable keywords to keyword aliases, and any applicable JSON-LD values expressed in expanded form to simple values such as strings or numbers.
1313
#
1414
# @param [Array, Hash] element
15-
# @param [String] property (nil)
16-
# @param [Boolean] ordered (true)
15+
# @param [String, RDF::URI] base (nil)
1716
# Ensure output objects have keys ordered properly
17+
# @param [Boolean] ordered (true)
18+
# @param [String] property (nil)
19+
# Extra validatation
1820
# @return [Array, Hash]
19-
def compact(element, property: nil, ordered: false)
21+
def compact(element,
22+
base: nil,
23+
ordered: false,
24+
property: nil)
2025
#if property.nil?
2126
# log_debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
2227
#else
@@ -29,7 +34,9 @@ def compact(element, property: nil, ordered: false)
2934
case element
3035
when Array
3136
#log_debug("") {"Array #{element.inspect}"}
32-
result = element.map {|item| compact(item, property: property, ordered: ordered)}.compact
37+
result = element.map do |item|
38+
compact(item, base: base, ordered: ordered, property: property)
39+
end.compact
3340

3441
# If element has a single member and the active property has no
3542
# @container mapping to @list or @set, the compacted value is that
@@ -55,10 +62,13 @@ def compact(element, property: nil, ordered: false)
5562

5663
# Look up term definintions from property using the original type-scoped context, if it exists, but apply them to the now current previous context
5764
td = input_context.term_definitions[property] if property
58-
self.context = context.parse(td.context, override_protected: true) if td && td.context
65+
if td && !td.context.nil?
66+
self.context = context.parse(td.context,
67+
override_protected: true)
68+
end
5969

6070
if element.key?('@id') || element.key?('@value')
61-
result = context.compact_value(property, element, log_depth: @options[:log_depth])
71+
result = context.compact_value(property, element, base: base)
6272
if !result.is_a?(Hash) || context.coerce(property) == '@json'
6373
#log_debug("") {"=> scalar result: #{result.inspect}"}
6474
return result
@@ -67,7 +77,8 @@ def compact(element, property: nil, ordered: false)
6777

6878
# If expanded property is @list and we're contained within a list container, recursively compact this item to an array
6979
if list?(element) && context.container(property).include?('@list')
70-
return compact(element['@list'], property: property, ordered: ordered)
80+
return compact(element['@list'], base: base,
81+
ordered: ordered, property: property)
7182
end
7283

7384
inside_reverse = property == '@reverse'
@@ -80,15 +91,17 @@ def compact(element, property: nil, ordered: false)
8091
sort.
8192
each do |term|
8293
term_context = input_context.term_definitions[term].context if input_context.term_definitions[term]
83-
self.context = context.parse(term_context, propagate: false) if term_context
94+
self.context = context.parse(term_context, propagate: false) unless term_context.nil?
8495
end
8596

8697
element.keys.opt_sort(ordered: ordered).each do |expanded_property|
8798
expanded_value = element[expanded_property]
8899
#log_debug("") {"#{expanded_property}: #{expanded_value.inspect}"}
89100

90101
if expanded_property == '@id'
91-
compacted_value = Array(expanded_value).map {|expanded_id| context.compact_iri(expanded_id)}
102+
compacted_value = Array(expanded_value).map do |expanded_id|
103+
context.compact_iri(expanded_id, base: base)
104+
end
92105

93106
kw_alias = context.compact_iri('@id', vocab: true)
94107
as_array = compacted_value.length > 1
@@ -98,7 +111,9 @@ def compact(element, property: nil, ordered: false)
98111
end
99112

100113
if expanded_property == '@type'
101-
compacted_value = Array(expanded_value).map {|expanded_type| input_context.compact_iri(expanded_type, vocab: true)}
114+
compacted_value = Array(expanded_value).map do |expanded_type|
115+
input_context.compact_iri(expanded_type, vocab: true)
116+
end
102117

103118
kw_alias = context.compact_iri('@type', vocab: true)
104119
as_array = compacted_value.length > 1 ||
@@ -110,7 +125,8 @@ def compact(element, property: nil, ordered: false)
110125
end
111126

112127
if expanded_property == '@reverse'
113-
compacted_value = compact(expanded_value, property: '@reverse', ordered: ordered)
128+
compacted_value = compact(expanded_value, base: base,
129+
ordered: ordered, property: '@reverse')
114130
#log_debug("@reverse") {"compacted_value: #{compacted_value.inspect}"}
115131
# handle double-reversed properties
116132
compacted_value.each do |prop, value|
@@ -131,7 +147,8 @@ def compact(element, property: nil, ordered: false)
131147

132148
if expanded_property == '@preserve'
133149
# Compact using `property`
134-
compacted_value = compact(expanded_value, property: property, ordered: ordered)
150+
compacted_value = compact(expanded_value, base: base,
151+
ordered: ordered, property: property)
135152
#log_debug("@preserve") {"compacted_value: #{compacted_value.inspect}"}
136153

137154
unless compacted_value.is_a?(Array) && compacted_value.empty?
@@ -158,8 +175,7 @@ def compact(element, property: nil, ordered: false)
158175
context.compact_iri(expanded_property,
159176
value: expanded_value,
160177
vocab: true,
161-
reverse: inside_reverse,
162-
log_depth: @options[:log_depth])
178+
reverse: inside_reverse)
163179

164180
if nest_prop = context.nest(item_active_property)
165181
result[nest_prop] ||= {}
@@ -177,8 +193,7 @@ def compact(element, property: nil, ordered: false)
177193
context.compact_iri(expanded_property,
178194
value: expanded_item,
179195
vocab: true,
180-
reverse: inside_reverse,
181-
log_depth: @options[:log_depth])
196+
reverse: inside_reverse)
182197

183198

184199
nest_result = if nest_prop = context.nest(item_active_property)
@@ -197,7 +212,8 @@ def compact(element, property: nil, ordered: false)
197212
else expanded_item
198213
end
199214

200-
compacted_item = compact(value, property: item_active_property, ordered: ordered)
215+
compacted_item = compact(value, base: base,
216+
ordered: ordered, property: item_active_property)
201217
#log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
202218

203219
# handle @list
@@ -225,9 +241,9 @@ def compact(element, property: nil, ordered: false)
225241
map_object = nest_result[item_active_property] ||= {}
226242
# If there is no @id, create a blank node identifier to use as an index
227243
map_key = if container.include?('@id') && expanded_item['@id']
228-
context.compact_iri(expanded_item['@id'])
244+
context.compact_iri(expanded_item['@id'], base: base)
229245
elsif container.include?('@index') && expanded_item['@index']
230-
context.compact_iri(expanded_item['@index'])
246+
context.compact_iri(expanded_item['@index'], vocab: true)
231247
else
232248
context.compact_iri('@none', vocab: true)
233249
end
@@ -299,7 +315,10 @@ def compact(element, property: nil, ordered: false)
299315

300316
# if compacted_item contains a single entry who's key maps to @id, then recompact the item without @type
301317
if compacted_item.keys.length == 1 && expanded_item.keys.include?('@id')
302-
compacted_item = compact({'@id' => expanded_item['@id']}, property: item_active_property)
318+
compacted_item = compact({'@id' => expanded_item['@id']},
319+
base: base,
320+
ordered: ordered,
321+
property: item_active_property)
303322
end
304323
compacted_item
305324
end

0 commit comments

Comments
 (0)