Skip to content

Commit d0c0587

Browse files
committed
Finish 3.0.1
2 parents 5f042eb + 99c3002 commit d0c0587

30 files changed

+445
-269
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.0
1+
3.0.1

lib/json/ld/api.rb

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class API
7171
# A context that is used to initialize the active context when expanding a document.
7272
# @option options [Boolean, String, RDF::URI] :flatten
7373
# 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.
74+
# @option options [String] :language
75+
# 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.
7476
# @option options [String] :processingMode
7577
# Processing mode, json-ld-1.0 or json-ld-1.1.
7678
# If `processingMode` is not specified, a mode of `json-ld-1.0` or `json-ld-1.1` is set, the context used for `expansion` or `compaction`.
@@ -80,12 +82,15 @@ class API
8082
# Use unique bnode identifiers, defaults to using the identifier which the node was originally initialized with (if any).
8183
# @option options [Symbol] :adapter used with MultiJson
8284
# @option options [Boolean] :validate Validate input, if a string or readable object.
85+
# @option options [Boolean] :ordered (true)
86+
# Order traversal of dictionary members by key when performing algorithms.
8387
# @yield [api]
8488
# @yieldparam [API]
8589
# @raise [JsonLdError]
8690
def initialize(input, context, rename_bnodes: true, unique_bnodes: false, **options, &block)
8791
@options = {
8892
compactArrays: true,
93+
ordered: false,
8994
documentLoader: self.class.method(:documentLoader)
9095
}.merge(options)
9196
@namer = unique_bnodes ? BlankNodeUniqer.new : (rename_bnodes ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
@@ -161,11 +166,11 @@ def initialize(input, context, rename_bnodes: true, unique_bnodes: false, **opti
161166
# @return [Object, Array<Hash>]
162167
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
163168
# @see http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm
164-
def self.expand(input, ordered: true, framing: false, **options, &block)
169+
def self.expand(input, framing: false, **options, &block)
165170
result, doc_base = nil
166171
API.new(input, options[:expandContext], options) do
167172
result = self.expand(self.value, nil, self.context,
168-
ordered: ordered,
173+
ordered: @options[:ordered],
169174
framing: framing)
170175
doc_base = @options[:base]
171176
end
@@ -216,14 +221,14 @@ def self.compact(input, context, expanded: false, **options)
216221

217222
# 1) Perform the Expansion Algorithm on the JSON-LD input.
218223
# This removes any existing context to allow the given context to be cleanly applied.
219-
expanded_input = expanded ? input : API.expand(input, options) do |res, base_iri|
224+
expanded_input = expanded ? input : API.expand(input, options.merge(ordered: false)) do |res, base_iri|
220225
options[:base] ||= base_iri if options[:compactToRelative]
221226
res
222227
end
223228

224229
API.new(expanded_input, context, no_default_base: true, **options) do
225230
log_debug(".compact") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
226-
result = compact(value)
231+
result = compact(value, ordered: @options[:ordered])
227232

228233
# xxx) Add the given context to the output
229234
ctx = self.context.serialize
@@ -274,23 +279,23 @@ def self.flatten(input, context, expanded: false, **options)
274279
create_node_map(value, graph_maps)
275280

276281
default_graph = graph_maps['@default']
277-
graph_maps.keys.sort.each do |graph_name|
282+
graph_maps.keys.opt_sort(ordered: @options[:ordered]).each do |graph_name|
278283
next if graph_name == '@default'
279284

280285
graph = graph_maps[graph_name]
281286
entry = default_graph[graph_name] ||= {'@id' => graph_name}
282287
nodes = entry['@graph'] ||= []
283-
graph.keys.sort.each do |id|
288+
graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
284289
nodes << graph[id] unless node_reference?(graph[id])
285290
end
286291
end
287-
default_graph.keys.sort.each do |id|
292+
default_graph.keys.opt_sort(ordered: @options[:ordered]).each do |id|
288293
flattened << default_graph[id] unless node_reference?(default_graph[id])
289294
end
290295

291296
if context && !flattened.empty?
292297
# 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.
293-
compacted = as_array(compact(flattened))
298+
compacted = as_array(compact(flattened, ordered: @options[:ordered]))
294299
kwgraph = self.context.compact_iri('@graph', quiet: true)
295300
flattened = self.context.serialize.merge(kwgraph => compacted)
296301
end
@@ -337,7 +342,6 @@ def self.frame(input, frame, expanded: false, **options)
337342
explicit: false,
338343
requireAll: true,
339344
omitDefault: false,
340-
omitGraph: false,
341345
documentLoader: method(:documentLoader)
342346
}.merge(options)
343347

@@ -361,19 +365,24 @@ def self.frame(input, frame, expanded: false, **options)
361365
end
362366

363367
# Expand input to simplify processing
364-
expanded_input = expanded ? input : API.expand(input, options) do |res, base_iri|
368+
expanded_input = expanded ? input : API.expand(input, options.merge(ordered: false)) do |res, base_iri|
365369
options[:base] ||= base_iri if options[:compactToRelative]
366370
res
367371
end
368372

369373
# Expand frame to simplify processing
370-
expanded_frame = API.expand(frame, framing: true, **options)
374+
expanded_frame = API.expand(frame, options.merge(framing: true, ordered: false))
371375

372376
# Initialize input using frame as context
373377
API.new(expanded_input, frame['@context'], no_default_base: true, **options) do
374378
log_debug(".frame") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
375379
log_debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE) rescue 'malformed json'}"}
376380

381+
# Set omitGraph option, if not present, based on processingMode
382+
unless options.has_key?(:omitGraph)
383+
options[:omitGraph] = @options[:processingMode] != 'json-ld-1.0'
384+
end
385+
377386
# Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
378387
create_node_map(value, framing_state[:graphMap], active_graph: '@default')
379388

@@ -391,7 +400,7 @@ def self.frame(input, frame, expanded: false, **options)
391400
framing_state[:subjects] = framing_state[:graphMap][framing_state[:graph]]
392401

393402
result = []
394-
frame(framing_state, framing_state[:subjects].keys.sort, (expanded_frame.first || {}), parent: result, **options)
403+
frame(framing_state, framing_state[:subjects].keys.opt_sort(ordered: @options[:ordered]), (expanded_frame.first || {}), parent: result, **options)
395404

396405
# Count blank node identifiers used in the document, if pruning
397406
unless @options[:processingMode] == 'json-ld-1.0'
@@ -402,7 +411,7 @@ def self.frame(input, frame, expanded: false, **options)
402411
# Initalize context from frame
403412
@context = @context.parse(frame['@context'])
404413
# Compact result
405-
compacted = compact(result)
414+
compacted = compact(result, ordered: @options[:ordered])
406415
compacted = [compacted] unless options[:omitGraph] || compacted.is_a?(Array)
407416

408417
# Add the given context to the output
@@ -443,7 +452,7 @@ def self.toRdf(input, expanded: false, **options, &block)
443452
end
444453

445454
# Expand input to simplify processing
446-
expanded_input = expanded ? input : API.expand(input, ordered: false, **options)
455+
expanded_input = expanded ? input : API.expand(input, options.merge(ordered: false))
447456

448457
API.new(expanded_input, nil, options) do
449458
# 1) Perform the Expansion Algorithm on the JSON-LD input.
@@ -487,8 +496,11 @@ def self.toRdf(input, expanded: false, **options, &block)
487496
def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &block)
488497
result = nil
489498

490-
API.new(nil, nil, options) do |api|
491-
result = api.from_statements(input, useRdfType: useRdfType, useNativeTypes: useNativeTypes)
499+
API.new(nil, nil, options) do
500+
result = from_statements(input,
501+
useRdfType: useRdfType,
502+
useNativeTypes: useNativeTypes,
503+
ordered: @options[:ordered])
492504
end
493505

494506
block_given? ? yield(result) : result

lib/json/ld/compact.rb

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ module Compact
99
#
1010
# @param [Array, Hash] element
1111
# @param [String] property (nil)
12+
# @param [Boolean] ordered (true)
13+
# Ensure output objects have keys ordered properly
1214
# @return [Array, Hash]
13-
def compact(element, property: nil)
15+
def compact(element, property: nil, ordered: false)
1416
#if property.nil?
1517
# log_debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
1618
#else
@@ -25,7 +27,7 @@ def compact(element, property: nil)
2527
case element
2628
when Array
2729
#log_debug("") {"Array #{element.inspect}"}
28-
result = element.map {|item| compact(item, property: property)}.compact
30+
result = element.map {|item| compact(item, property: property, ordered: ordered)}.compact
2931

3032
# If element has a single member and the active property has no
3133
# @container mapping to @list or @set, the compacted value is that
@@ -54,7 +56,7 @@ def compact(element, property: nil)
5456

5557
# If expanded property is @list and we're contained within a list container, recursively compact this item to an array
5658
if list?(element) && context.container(property) == %w(@list)
57-
return compact(element['@list'], property: property)
59+
return compact(element['@list'], property: property, ordered: ordered)
5860
end
5961

6062

@@ -71,7 +73,7 @@ def compact(element, property: nil)
7173
self.context = context.parse(term_context) if term_context
7274
end
7375

74-
element.keys.sort.each do |expanded_property|
76+
element.keys.opt_sort(ordered: ordered).each do |expanded_property|
7577
expanded_value = element[expanded_property]
7678
#log_debug("") {"#{expanded_property}: #{expanded_value.inspect}"}
7779

@@ -89,7 +91,7 @@ def compact(element, property: nil)
8991
end
9092

9193
if expanded_property == '@reverse'
92-
compacted_value = compact(expanded_value, property: '@reverse')
94+
compacted_value = compact(expanded_value, property: '@reverse', ordered: ordered)
9395
#log_debug("@reverse") {"compacted_value: #{compacted_value.inspect}"}
9496
# handle double-reversed properties
9597
compacted_value.each do |prop, value|
@@ -110,7 +112,7 @@ def compact(element, property: nil)
110112

111113
if expanded_property == '@preserve'
112114
# Compact using `property`
113-
compacted_value = compact(expanded_value, property: property)
115+
compacted_value = compact(expanded_value, property: property, ordered: ordered)
114116
#log_debug("@preserve") {"compacted_value: #{compacted_value.inspect}"}
115117

116118
unless compacted_value.is_a?(Array) && compacted_value.empty?
@@ -176,7 +178,7 @@ def compact(element, property: nil)
176178
else expanded_item
177179
end
178180

179-
compacted_item = compact(value, property: item_active_property)
181+
compacted_item = compact(value, property: item_active_property, ordered: ordered)
180182
#log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
181183

182184
# handle @list
@@ -267,8 +269,7 @@ def compact(element, property: nil)
267269
end
268270
end
269271

270-
# Re-order result keys
271-
result.keys.sort.each_with_object({}) {|kk, memo| memo[kk] = result[kk]}
272+
result
272273
else
273274
# For other types, the compacted value is the element value
274275
#log_debug("compact") {element.class.to_s}

lib/json/ld/context.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ def serialize(**options)
750750
ctx['@vocab'] = vocab.to_s if vocab
751751

752752
# Term Definitions
753-
term_definitions.keys.sort.each do |term|
753+
term_definitions.keys.each do |term|
754754
defn = term_definitions[term].to_context_definition(self)
755755
ctx[term] = defn if defn
756756
end
@@ -1201,7 +1201,7 @@ def compact_iri(iri, value: nil, vocab: nil, reverse: false, quiet: false, **opt
12011201
candidates << ciri unless value && term_definitions.has_key?(ciri)
12021202
end
12031203

1204-
return candidates.term_sort.first if !candidates.empty?
1204+
return candidates.sort.first if !candidates.empty?
12051205

12061206
# If we still don't have any terms and we're using standard_prefixes,
12071207
# try those, and add to mapping
@@ -1214,7 +1214,7 @@ def compact_iri(iri, value: nil, vocab: nil, reverse: false, quiet: false, **opt
12141214
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
12151215
end
12161216

1217-
return candidates.term_sort.first if !candidates.empty?
1217+
return candidates.sort.first if !candidates.empty?
12181218
end
12191219

12201220
if !vocab

lib/json/ld/expand.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module Expand
1919
# @param [Boolean] framing (false)
2020
# Special rules for expanding a frame
2121
# @return [Array<Hash{String => Object}>]
22-
def expand(input, active_property, context, ordered: true, framing: false)
22+
def expand(input, active_property, context, ordered: false, framing: false)
2323
#log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
2424
framing = false if active_property == '@default'
2525
result = case input

lib/json/ld/extensions.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ def valid_extended?
4343
end
4444

4545
class Array
46-
# Order terms, length first, then lexographically
47-
def term_sort
48-
self.sort do |a, b|
49-
len_diff = a.length <=> b.length
50-
len_diff == 0 ? a <=> b : len_diff
51-
end
46+
# Optionally order items
47+
#
48+
# @param [Boolean] ordered
49+
# @return [Array]
50+
def opt_sort(ordered: false)
51+
ordered ? self.sort : self
5252
end
5353
end

lib/json/ld/flatten.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ module Flatten
1818
# Property within current node
1919
# @param [Array] list (nil)
2020
# Used when property value is a list
21+
# @param [Boolean] ordered (true)
22+
# Ensure output objects have keys ordered properly
2123
def create_node_map(element, graph_map,
2224
active_graph: '@default',
2325
active_subject: nil,
@@ -110,7 +112,7 @@ def create_node_map(element, graph_map,
110112
active_graph: id)
111113
end
112114

113-
element.keys.sort.each do |property|
115+
element.keys.each do |property|
114116
value = element[property]
115117

116118
property = namer.get_name(property) if blank_node?(property)

lib/json/ld/frame.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ module Frame
1414
# @param [Array<String>] subjects
1515
# The subjects to filter
1616
# @param [Hash{String => Object}] frame
17-
# @param [Hash{Symbol => Object}] options ({})
18-
# @option options [Hash{String => Object}] :parent (nil)
19-
# Parent subject or top-level array
20-
# @option options [String] :property (nil)
17+
# @param [String] property (nil)
2118
# The parent property.
19+
# @param [Hash{String => Object}] parent (nil)
20+
# Parent subject or top-level array
21+
# @param [Boolean] ordered (true)
22+
# Ensure output objects have keys ordered properly
23+
# @param [Hash{Symbol => Object}] options ({})
2224
# @raise [JSON::LD::InvalidFrame]
23-
def frame(state, subjects, frame, parent: nil, property: nil, **options)
25+
def frame(state, subjects, frame, parent: nil, property: nil, ordered: false, **options)
2426
#log_depth do
2527
#log_debug("frame") {"subjects: #{subjects.inspect}"}
2628
#log_debug("frame") {"frame: #{frame.to_json(JSON_STATE)}"}
@@ -45,7 +47,7 @@ def frame(state, subjects, frame, parent: nil, property: nil, **options)
4547
matches = filter_subjects(state, subjects, frame, flags)
4648

4749
# For each id and node from the set of matched subjects ordered by id
48-
matches.keys.sort.each do |id|
50+
matches.keys.opt_sort(ordered: ordered).each do |id|
4951
subject = matches[id]
5052

5153
# Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is nil, which only occurs at the top-level.
@@ -110,7 +112,7 @@ def frame(state, subjects, frame, parent: nil, property: nil, **options)
110112
end
111113

112114
# iterate over subject properties in order
113-
subject.keys.sort.each do |prop|
115+
subject.keys.opt_sort(ordered: ordered).each do |prop|
114116
objects = subject[prop]
115117

116118
# copy keywords to output
@@ -153,7 +155,7 @@ def frame(state, subjects, frame, parent: nil, property: nil, **options)
153155
end
154156

155157
# handle defaults in order
156-
frame.keys.sort.each do |prop|
158+
frame.keys.opt_sort(ordered: ordered).each do |prop|
157159
next if prop.start_with?('@')
158160

159161
# if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject

lib/json/ld/from_rdf.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ module FromRDF
1414
# @param [Boolean] useRdfType (false)
1515
# If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
1616
# @param [Boolean] useNativeTypes (false) use native representations
17+
# @param [Boolean] ordered (true)
18+
# Ensure output objects have keys ordered properly
1719
# @return [Array<Hash>] the JSON-LD document in normalized form
18-
def from_statements(dataset, useRdfType: false, useNativeTypes: false)
20+
def from_statements(dataset, useRdfType: false, useNativeTypes: false, ordered: false)
1921
default_graph = {}
2022
graph_map = {'@default' => default_graph}
2123
referenced_once = {}
@@ -118,11 +120,11 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
118120
end
119121

120122
result = []
121-
default_graph.keys.sort.each do |subject|
123+
default_graph.keys.opt_sort(ordered: ordered).each do |subject|
122124
node = default_graph[subject]
123125
if graph_map.has_key?(subject)
124126
node['@graph'] = []
125-
graph_map[subject].keys.sort.each do |s|
127+
graph_map[subject].keys.opt_sort(ordered: ordered).each do |s|
126128
n = graph_map[subject][s]
127129
n.delete(:usages)
128130
node['@graph'] << n unless node_reference?(n)

lib/json/ld/reader.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def self.options
2121
on: ["--expand-context CONTEXT"],
2222
description: "Context to use when expanding.") {|arg| RDF::URI(arg)},
2323
RDF::CLI::Option.new(
24-
symbol: :processing_mode,
24+
symbol: :processingMode,
2525
datatype: %w(json-ld-1.0 json-ld-1.1),
2626
control: :radio,
2727
on: ["--processingMode MODE", %w(json-ld-1.0 json-ld-1.1)],

0 commit comments

Comments
 (0)