json-ld 0.1.6.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.markdown +5 -2
- data/README.markdown +2 -1
- data/VERSION +1 -1
- data/lib/json/ld.rb +1 -0
- data/lib/json/ld/api.rb +23 -18
- data/lib/json/ld/compact.rb +12 -7
- data/lib/json/ld/evaluation_context.rb +42 -37
- data/lib/json/ld/expand.rb +1 -1
- data/lib/json/ld/from_rdf.rb +2 -1
- data/lib/json/ld/resource.rb +243 -0
- data/lib/json/ld/to_rdf.rb +3 -3
- data/spec/compact_spec.rb +128 -18
- data/spec/evaluation_context_spec.rb +65 -8
- data/spec/resource_spec.rb +7 -0
- data/spec/suite_compact_spec.rb +32 -0
- data/spec/suite_expand_spec.rb +37 -0
- data/spec/suite_frame_spec.rb +32 -0
- data/spec/suite_from_rdf_spec.rb +33 -0
- data/spec/suite_helper.rb +28 -109
- data/spec/suite_to_rdf_spec.rb +76 -0
- data/spec/to_rdf_spec.rb +6 -0
- metadata +15 -4
- data/spec/suite_spec.rb +0 -105
data/History.markdown
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
=== 0.
|
2
|
-
*
|
1
|
+
=== 0.3.0
|
2
|
+
* Fix regression on opening self-relative contexts.
|
3
|
+
* When generating RDF, allow @type to be a BNode. This fixes issue #2
|
4
|
+
* Add {JSON::LD::Resource} class for simple ruby-like management of JSON-LD nodes.
|
5
|
+
* Term Rank and IRI compaction updates.
|
3
6
|
|
4
7
|
=== 0.1.6
|
5
8
|
* Added flattening API, and updated algorithm.
|
data/README.markdown
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# JSON-LD reader/writer
|
2
2
|
|
3
|
-
[
|
3
|
+
[](http://travis-ci.org/gkellogg/json-ld)
|
4
|
+
[JSON-LD][] reader/writer for [RDF.rb][RDF.rb] and fully conforming [JSON-LD][] processor.
|
4
5
|
|
5
6
|
## Features
|
6
7
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/json/ld.rb
CHANGED
@@ -29,6 +29,7 @@ module JSON
|
|
29
29
|
autoload :EvaluationContext, 'json/ld/evaluation_context'
|
30
30
|
autoload :Normalize, 'json/ld/normalize'
|
31
31
|
autoload :Reader, 'json/ld/reader'
|
32
|
+
autoload :Resource, 'json/ld/resource'
|
32
33
|
autoload :VERSION, 'json/ld/version'
|
33
34
|
autoload :Writer, 'json/ld/writer'
|
34
35
|
|
data/lib/json/ld/api.rb
CHANGED
@@ -33,11 +33,25 @@ module JSON::LD
|
|
33
33
|
# @param [String, #read, Hash, Array] input
|
34
34
|
# @param [String, #read,, Hash, Array] context
|
35
35
|
# An external context to use additionally to the context embedded in input when expanding the input.
|
36
|
-
# @param
|
36
|
+
# @param [Hash{Symbol => Object}] options
|
37
|
+
# @option options [Boolean] :base
|
38
|
+
# 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.
|
39
|
+
# @option options [Boolean] :compactArrays (true)
|
40
|
+
# 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.
|
41
|
+
# @option options [Proc] :conformanceCallback
|
42
|
+
# The purpose of this option is to instruct the processor about whether or not it should continue processing. If the value is null, the processor should ignore any key-value pair associated with any recoverable conformance issue and continue processing. More details about this feature can be found in the ConformanceCallback section.
|
43
|
+
# @option options [Boolean, String, RDF::URI] :flatten
|
44
|
+
# 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.
|
45
|
+
# @option options [Boolean] :optimize (false)
|
46
|
+
# If set to `true`, the JSON-LD processor is allowed to optimize the output of the Compaction Algorithm to produce even compacter representations. The algorithm for compaction optimization is beyond the scope of this specification and thus not defined. Consequently, different implementations *MAY* implement different optimization algorithms.
|
47
|
+
# (Presently, this is a noop).
|
48
|
+
# @option options [Boolean] :useNativeDatatypes (true)
|
49
|
+
# If set to `true`, the JSON-LD processor will use the expanded `rdf:type` IRI as the property instead of `@type` when converting from RDF.
|
50
|
+
# @option options [Boolean] :useRdfType (false) If set to `true`, the JSON-LD processor will try to convert datatyped literals to JSON native types instead of using the expanded object form when converting from RDF. `xsd:boolean` values will be converted to `true` or `false`. `xsd:integer` and `xsd:double` values will be converted to JSON numbers.
|
37
51
|
# @yield [api]
|
38
52
|
# @yieldparam [API]
|
39
53
|
def initialize(input, context, options = {}, &block)
|
40
|
-
@options = options
|
54
|
+
@options = {:compactArrays => true}.merge(options)
|
41
55
|
@value = case input
|
42
56
|
when Array, Hash then input.dup
|
43
57
|
when IO, StringIO then JSON.parse(input.read)
|
@@ -72,8 +86,7 @@ module JSON::LD
|
|
72
86
|
# @param [Proc] callback (&block)
|
73
87
|
# Alternative to using block, with same parameters.
|
74
88
|
# @param [Hash{Symbol => Object}] options
|
75
|
-
#
|
76
|
-
# Base IRI to use when processing relative IRIs.
|
89
|
+
# See options in {#initialize}
|
77
90
|
# @raise [InvalidContext]
|
78
91
|
# @yield jsonld
|
79
92
|
# @yieldparam [Array<Hash>] jsonld
|
@@ -115,10 +128,8 @@ module JSON::LD
|
|
115
128
|
# @param [Proc] callback (&block)
|
116
129
|
# Alternative to using block, with same parameters.
|
117
130
|
# @param [Hash{Symbol => Object}] options
|
131
|
+
# See options in {#initialize}
|
118
132
|
# Other options passed to {#expand}
|
119
|
-
# @option options [Boolean] :optimize (false)
|
120
|
-
# Perform further optimmization of the compacted output.
|
121
|
-
# (Presently, this is a noop).
|
122
133
|
# @yield jsonld
|
123
134
|
# @yieldparam [Hash] jsonld
|
124
135
|
# The compacted JSON-LD document
|
@@ -169,16 +180,8 @@ module JSON::LD
|
|
169
180
|
# @param [Proc] callback (&block)
|
170
181
|
# Alternative to using block, with same parameters.
|
171
182
|
# @param [Hash{Symbol => Object}] options
|
183
|
+
# See options in {#initialize}
|
172
184
|
# Other options passed to {#expand}
|
173
|
-
# @option options [Boolean] :embed (true)
|
174
|
-
# a flag specifying that objects should be directly embedded in the output,
|
175
|
-
# instead of being referred to by their IRI.
|
176
|
-
# @option options [Boolean] :explicit (false)
|
177
|
-
# a flag specifying that for properties to be included in the output,
|
178
|
-
# they must be explicitly declared in the framing context.
|
179
|
-
# @option options [Boolean] :omitDefault (false)
|
180
|
-
# a flag specifying that properties that are missing from the JSON-LD
|
181
|
-
# input should be omitted from the output.
|
182
185
|
# @yield jsonld
|
183
186
|
# @yieldparam [Hash] jsonld
|
184
187
|
# The framed JSON-LD document
|
@@ -240,6 +243,7 @@ module JSON::LD
|
|
240
243
|
# @param [Proc] callback (&block)
|
241
244
|
# Alternative to using block, with same parameters.
|
242
245
|
# @param [Hash{Symbol => Object}] options
|
246
|
+
# See options in {#initialize}
|
243
247
|
# Other options passed to {#expand}
|
244
248
|
# @option options [Boolean] :embed (true)
|
245
249
|
# a flag specifying that objects should be directly embedded in the output,
|
@@ -338,6 +342,7 @@ module JSON::LD
|
|
338
342
|
# @param [Proc] callback (&block)
|
339
343
|
# Alternative to using block, with same parameteres.
|
340
344
|
# @param [{Symbol,String => Object}] options
|
345
|
+
# See options in {#initialize}
|
341
346
|
# Options passed to {#expand}
|
342
347
|
# @raise [InvalidContext]
|
343
348
|
# @yield statement
|
@@ -368,14 +373,14 @@ module JSON::LD
|
|
368
373
|
# @param [Proc] callback (&block)
|
369
374
|
# Alternative to using block, with same parameteres.
|
370
375
|
# @param [Hash{Symbol => Object}] options
|
371
|
-
#
|
372
|
-
# @option options [Boolean] :useNativeDatatypes FIXME
|
376
|
+
# See options in {#initialize}
|
373
377
|
# @yield jsonld
|
374
378
|
# @yieldparam [Hash] jsonld
|
375
379
|
# The JSON-LD document in expanded form
|
376
380
|
# @return [Array<Hash>]
|
377
381
|
# The JSON-LD document in expanded form
|
378
382
|
def self.fromRDF(input, callback = nil, options = {})
|
383
|
+
options = {:useNativeTypes => true}.merge(options)
|
379
384
|
result = nil
|
380
385
|
|
381
386
|
API.new(nil, nil, options) do |api|
|
data/lib/json/ld/compact.rb
CHANGED
@@ -25,7 +25,7 @@ module JSON::LD
|
|
25
25
|
# If element has a single member and the active property has no
|
26
26
|
# @container mapping to @list or @set, the compacted value is that
|
27
27
|
# member; otherwise the compacted value is element
|
28
|
-
if result.length == 1
|
28
|
+
if result.length == 1 && @options[:compactArrays]
|
29
29
|
debug("=> extract single element: #{result.first.inspect}")
|
30
30
|
result.first
|
31
31
|
else
|
@@ -44,13 +44,17 @@ module JSON::LD
|
|
44
44
|
|
45
45
|
case k
|
46
46
|
when '@value', '@id'
|
47
|
-
# If element has an @value property or element is a node reference, return the result of performing
|
48
|
-
# Value Compaction on element using active property.
|
47
|
+
# If element has an @value property or element is a node reference, return the result of performing Value Compaction on element using active property.
|
49
48
|
v = context.compact_value(property, element, :depth => @depth)
|
50
49
|
debug("compact") {"value optimization, return as #{v.inspect}"}
|
51
50
|
return v
|
52
51
|
when '@list'
|
53
|
-
# Otherwise, if the active property has a @container mapping to @list and element has a corresponding @list property, recursively compact that property's value passing a copy of the active context and the active property ensuring that the result is an array
|
52
|
+
# Otherwise, if the active property has a @container mapping to @list and element has a corresponding @list property, recursively compact that property's value passing a copy of the active context and the active property ensuring that the result is an array with all null values removed.
|
53
|
+
|
54
|
+
# If there already exists a value for active property in element and the full IRI of property is also coerced to @list, return an error.
|
55
|
+
# FIXME: check for full-iri list coercion
|
56
|
+
|
57
|
+
# Otherwise store the resulting array as value of active property if empty or property otherwise.
|
54
58
|
compacted_key = context.compact_iri(k, :position => :predicate, :depth => @depth)
|
55
59
|
v = depth { compact(element[k], property) }
|
56
60
|
|
@@ -66,18 +70,19 @@ module JSON::LD
|
|
66
70
|
debug("compact") {"#{key}: #{value.inspect}"}
|
67
71
|
|
68
72
|
if %(@id @type).include?(key)
|
73
|
+
position = key == '@id' ? :subject : :type
|
69
74
|
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
|
70
75
|
|
71
76
|
result[compacted_key] = case value
|
72
77
|
when String
|
73
78
|
# If value is a string, the compacted value is the result of performing IRI Compaction on value.
|
74
79
|
debug {" => compacted string for #{key}"}
|
75
|
-
context.compact_iri(value, :position =>
|
80
|
+
context.compact_iri(value, :position => position, :depth => @depth)
|
76
81
|
when Array
|
77
82
|
# Otherwise, value must be an array. Perform IRI Compaction on every entry of value. If value contains just one entry, value is set to that entry
|
78
|
-
compacted_value = value.map {|v| context.compact_iri(v, :position =>
|
83
|
+
compacted_value = value.map {|v| context.compact_iri(v, :position => position, :depth => @depth)}
|
79
84
|
debug {" => compacted value(#{key}): #{compacted_value.inspect}"}
|
80
|
-
compacted_value = compacted_value.first if compacted_value.length == 1
|
85
|
+
compacted_value = compacted_value.first if compacted_value.length == 1 && @options[:compactArrays]
|
81
86
|
compacted_value
|
82
87
|
end
|
83
88
|
else
|
@@ -183,7 +183,7 @@ module JSON::LD
|
|
183
183
|
new_ec.send(setter, v)
|
184
184
|
elsif v
|
185
185
|
raise InvalidContext::Syntax, "#{key.inspect} is invalid"
|
186
|
-
|
186
|
+
end
|
187
187
|
end
|
188
188
|
|
189
189
|
num_updates = 1
|
@@ -313,7 +313,7 @@ module JSON::LD
|
|
313
313
|
debug {"=> coerce(#{k}) => #{coerce(k)}"}
|
314
314
|
if coerce(k) && !NATIVE_DATATYPES.include?(coerce(k))
|
315
315
|
dt = coerce(k)
|
316
|
-
dt = compact_iri(dt, :position => :
|
316
|
+
dt = compact_iri(dt, :position => :type) unless dt == '@id'
|
317
317
|
# Fold into existing definition
|
318
318
|
ctx[k]["@type"] = dt
|
319
319
|
debug {"=> datatype[#{k}] => #{dt}"}
|
@@ -478,7 +478,7 @@ module JSON::LD
|
|
478
478
|
# @param [String] iri
|
479
479
|
# A keyword, term, prefix:suffix or possibly relative IRI
|
480
480
|
# @param [Hash{Symbol => Object}] options
|
481
|
-
# @option options [:subject, :predicate, :
|
481
|
+
# @option options [:subject, :predicate, :type] position
|
482
482
|
# Useful when determining how to serialize.
|
483
483
|
# @option options [RDF::URI] base (self.base)
|
484
484
|
# Base IRI to use when expanding relative IRIs.
|
@@ -491,7 +491,7 @@ module JSON::LD
|
|
491
491
|
prefix, suffix = iri.split(':', 2)
|
492
492
|
return mapping(iri) if mapping(iri) # If it's an exact match
|
493
493
|
debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]
|
494
|
-
base = [:subject
|
494
|
+
base = [:subject].include?(options[:position]) ? options.fetch(:base, self.base) : nil
|
495
495
|
prefix = prefix.to_s
|
496
496
|
case
|
497
497
|
when prefix == '_' && suffix then bnode(suffix)
|
@@ -503,7 +503,7 @@ module JSON::LD
|
|
503
503
|
else
|
504
504
|
# Otherwise, it must be an absolute IRI
|
505
505
|
u = uri(iri)
|
506
|
-
u if u.absolute? || [:subject
|
506
|
+
u if u.absolute? || [:subject].include?(options[:position])
|
507
507
|
end
|
508
508
|
end
|
509
509
|
|
@@ -512,7 +512,7 @@ module JSON::LD
|
|
512
512
|
#
|
513
513
|
# @param [RDF::URI] iri
|
514
514
|
# @param [Hash{Symbol => Object}] options ({})
|
515
|
-
# @option options [:subject, :predicate, :
|
515
|
+
# @option options [:subject, :predicate, :type] position
|
516
516
|
# Useful when determining how to serialize.
|
517
517
|
# @option options [Object] :value
|
518
518
|
# Value, used to select among various maps for the same IRI
|
@@ -528,55 +528,54 @@ module JSON::LD
|
|
528
528
|
value = options.fetch(:value, nil)
|
529
529
|
|
530
530
|
# Get a list of terms which map to iri
|
531
|
-
|
531
|
+
matched_terms = mappings.keys.select {|t| mapping(t).to_s == iri}
|
532
|
+
debug("compact_iri", "initial terms: #{matched_terms.inspect}")
|
532
533
|
|
533
|
-
# Create an
|
534
|
-
|
535
|
-
term_map = {}
|
534
|
+
# Create an empty list of terms _terms_ that will be populated with terms that are ranked according to how closely they match value. Initialize highest rank to 0, and set a flag list container to false.
|
535
|
+
terms = {}
|
536
536
|
|
537
537
|
# If value is a @list add a term rank for each
|
538
538
|
# term mapping to iri which has @container @list.
|
539
539
|
debug("compact_iri", "#{value.inspect} is a list? #{list?(value).inspect}")
|
540
540
|
if list?(value)
|
541
|
-
list_terms =
|
541
|
+
list_terms = matched_terms.select {|t| container(t) == '@list'}
|
542
542
|
|
543
|
-
|
543
|
+
terms = list_terms.inject({}) do |memo, t|
|
544
544
|
memo[t] = term_rank(t, value)
|
545
545
|
memo
|
546
546
|
end unless list_terms.empty?
|
547
|
-
debug("term map") {"remove zero rank terms: #{
|
548
|
-
|
547
|
+
debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
|
548
|
+
terms.delete_if {|t, r| r == 0}
|
549
549
|
end
|
550
550
|
|
551
551
|
# Otherwise, value is @value or a native type.
|
552
552
|
# Add a term rank for each term mapping to iri
|
553
553
|
# which does not have @container @list
|
554
|
-
if
|
555
|
-
non_list_terms =
|
554
|
+
if terms.empty?
|
555
|
+
non_list_terms = matched_terms.reject {|t| container(t) == '@list'}
|
556
556
|
|
557
557
|
# If value is a @list, exclude from term map those terms
|
558
558
|
# with @container @set
|
559
559
|
non_list_terms.reject {|t| container(t) == '@set'} if list?(value)
|
560
560
|
|
561
|
-
|
561
|
+
terms = non_list_terms.inject({}) do |memo, t|
|
562
562
|
memo[t] = term_rank(t, value)
|
563
563
|
memo
|
564
564
|
end unless non_list_terms.empty?
|
565
|
-
debug("term map") {"remove zero rank terms: #{
|
566
|
-
|
565
|
+
debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
|
566
|
+
terms.delete_if {|t, r| r == 0}
|
567
567
|
end
|
568
568
|
|
569
569
|
# If we don't want terms, remove anything that's not a CURIE or IRI
|
570
|
-
|
570
|
+
terms.keep_if {|t, v| t.index(':') } if options.fetch(:not_term, false)
|
571
571
|
|
572
572
|
# Find terms having the greatest term match value
|
573
|
-
least_distance =
|
574
|
-
terms =
|
573
|
+
least_distance = terms.values.max
|
574
|
+
terms = terms.keys.select {|t| terms[t] == least_distance}
|
575
575
|
|
576
|
-
# If terms is empty, and the active context has a @vocab which is a
|
577
|
-
|
578
|
-
|
579
|
-
if vocab && terms.empty? && iri.to_s.index(vocab) == 0
|
576
|
+
# If terms is empty, and the active context has a @vocab which is a prefix of iri where the resulting relative IRI is not a term in the active context. The resulting relative IRI is the unmatched part of iri.
|
577
|
+
if vocab && terms.empty? && iri.to_s.index(vocab) == 0 &&
|
578
|
+
[:predicate, :type].include?(options[:position])
|
580
579
|
terms << iri.to_s.sub(vocab, '')
|
581
580
|
debug("vocab") {"vocab: #{vocab}, rel: #{terms.first}"}
|
582
581
|
end
|
@@ -608,7 +607,7 @@ module JSON::LD
|
|
608
607
|
end
|
609
608
|
|
610
609
|
terms = curies.select do |curie|
|
611
|
-
container(curie) != '@list' &&
|
610
|
+
(options[:position] != :predicate || container(curie) != '@list') &&
|
612
611
|
coerce(curie).nil? &&
|
613
612
|
language(curie) == default_language
|
614
613
|
end
|
@@ -755,7 +754,7 @@ module JSON::LD
|
|
755
754
|
debug("else")
|
756
755
|
case coerce(property)
|
757
756
|
when '@id'
|
758
|
-
{'@id' => expand_iri(value, :position => :
|
757
|
+
{'@id' => expand_iri(value, :position => :subject).to_s}
|
759
758
|
when nil
|
760
759
|
debug("expand value") {"lang(prop): #{language(property).inspect}, def: #{default_language.inspect}"}
|
761
760
|
language(property) ? {"@value" => value.to_s, "@language" => language(property)} : {"@value" => value.to_s}
|
@@ -792,22 +791,22 @@ module JSON::LD
|
|
792
791
|
debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
|
793
792
|
|
794
793
|
result = case
|
795
|
-
#when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :
|
794
|
+
#when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :type) == RDF::XSD[t]}
|
796
795
|
# # Compact native type
|
797
796
|
# debug {" (native)"}
|
798
|
-
# l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :
|
797
|
+
# l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :type))
|
799
798
|
# l.canonicalize.object
|
800
799
|
when coerce(property) == '@id' && value.has_key?('@id')
|
801
800
|
# Compact an @id coercion
|
802
801
|
debug {" (@id & coerce)"}
|
803
|
-
compact_iri(value['@id'], :position => :
|
804
|
-
when value['@type'] && expand_iri(value['@type'], :position => :
|
802
|
+
compact_iri(value['@id'], :position => :subject)
|
803
|
+
when value['@type'] && expand_iri(value['@type'], :position => :type) == coerce(property)
|
805
804
|
# Compact common datatype
|
806
805
|
debug {" (@type & coerce) == #{coerce(property)}"}
|
807
806
|
value['@value']
|
808
807
|
when value.has_key?('@id')
|
809
808
|
# Compact an IRI
|
810
|
-
value[self.alias('@id')] = compact_iri(value['@id'], :position => :
|
809
|
+
value[self.alias('@id')] = compact_iri(value['@id'], :position => :subject)
|
811
810
|
debug {" (#{self.alias('@id')} => #{value['@id']})"}
|
812
811
|
value
|
813
812
|
when value['@language'] && value['@language'] == language(property)
|
@@ -829,7 +828,7 @@ module JSON::LD
|
|
829
828
|
when value['@type']
|
830
829
|
# Compact datatype
|
831
830
|
debug {" (@type)"}
|
832
|
-
value[self.alias('@type')] = compact_iri(value['@type'], :position => :
|
831
|
+
value[self.alias('@type')] = compact_iri(value['@type'], :position => :type)
|
833
832
|
value
|
834
833
|
else
|
835
834
|
# Otherwise, use original value
|
@@ -928,7 +927,7 @@ module JSON::LD
|
|
928
927
|
end
|
929
928
|
elsif value?(value)
|
930
929
|
val_type = value.fetch('@type', nil)
|
931
|
-
val_lang = value.
|
930
|
+
val_lang = value['@language'] || false if value.has_key?('@language')
|
932
931
|
debug("term rank") {"@val_type: #{val_type.inspect}, val_lang: #{val_lang.inspect}"}
|
933
932
|
if val_type
|
934
933
|
coerce(term) == val_type ? 3 : (default_term ? 1 : 0)
|
@@ -936,9 +935,15 @@ module JSON::LD
|
|
936
935
|
default_term ? 2 : 1
|
937
936
|
elsif val_lang.nil?
|
938
937
|
debug("val_lang.nil") {"#{language(term).inspect} && #{coerce(term).inspect}"}
|
939
|
-
|
938
|
+
language(term) == false || (default_term && default_language.nil?) ? 3 : 0
|
940
939
|
else
|
941
|
-
val_lang == language(term)
|
940
|
+
if val_lang == language(term) || (default_term && default_language == val_lang)
|
941
|
+
3
|
942
|
+
elsif default_term
|
943
|
+
1
|
944
|
+
else
|
945
|
+
0
|
946
|
+
end
|
942
947
|
end
|
943
948
|
else # node definition/reference
|
944
949
|
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
|
data/lib/json/ld/expand.rb
CHANGED
@@ -181,7 +181,7 @@ module JSON::LD
|
|
181
181
|
end
|
182
182
|
else
|
183
183
|
# Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
|
184
|
-
context.expand_value(active_property, input, :position => :
|
184
|
+
context.expand_value(active_property, input, :position => :subject, :depth => @depth) unless input.nil?
|
185
185
|
end
|
186
186
|
|
187
187
|
debug {" => #{result.inspect}"}
|
data/lib/json/ld/from_rdf.rb
CHANGED
@@ -71,6 +71,7 @@ module JSON::LD
|
|
71
71
|
# append the string representation of object to the array value for the key @type, creating
|
72
72
|
# an entry if necessary
|
73
73
|
(value['@type'] ||= []) << object
|
74
|
+
# FIXME: 3.7) If object is a typed literal and the useNativeTypes option is set to true:
|
74
75
|
elsif statement.object == RDF.nil
|
75
76
|
# Otherwise, if object is http://www.w3.org/1999/02/22-rdf-syntax-ns#nil, let
|
76
77
|
# key be the string representation of predicate. Set the value
|
@@ -139,7 +140,7 @@ module JSON::LD
|
|
139
140
|
end
|
140
141
|
|
141
142
|
# Return array as the graph representation.
|
142
|
-
debug("
|
143
|
+
debug("fromRDF") {array.to_json(JSON_STATE)}
|
143
144
|
array
|
144
145
|
end
|
145
146
|
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
module JSON::LD
|
2
|
+
# Simple Ruby reflector class to provide native
|
3
|
+
# access to JSON-LD objects
|
4
|
+
class Resource
|
5
|
+
# Object representation of resource
|
6
|
+
#
|
7
|
+
# @attr_reader [Hash<String => Object] attributes
|
8
|
+
attr_reader :attributes
|
9
|
+
|
10
|
+
# ID of this resource
|
11
|
+
#
|
12
|
+
# @attr_reader [String] id
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
# Context associated with this resource
|
16
|
+
#
|
17
|
+
# @attr_reader [JSON::LD::EvaluationContext] context
|
18
|
+
attr_reader :context
|
19
|
+
|
20
|
+
# Is this resource clean (i.e., saved to mongo?)
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
def clean?; @clean; end
|
24
|
+
|
25
|
+
# Is this resource dirty (i.e., not yet saved to mongo?)
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def dirty?; !clean?; end
|
29
|
+
|
30
|
+
# Has this resource been reconciled against a mongo ID?
|
31
|
+
#
|
32
|
+
# @return [Boolean]
|
33
|
+
def reconciled?; @reconciled; end
|
34
|
+
|
35
|
+
# Has this resource been resolved so that
|
36
|
+
# all references are to other Resources?
|
37
|
+
#
|
38
|
+
# @return [Boolean]
|
39
|
+
def resolved?; @resolved; end
|
40
|
+
|
41
|
+
# Anonymous resources have BNode ids or no schema:url
|
42
|
+
#
|
43
|
+
# @return [Boolean]
|
44
|
+
def anonymous?; @anon; end
|
45
|
+
|
46
|
+
# Is this a stub resource, which has not yet been
|
47
|
+
# synched or created within the DB?
|
48
|
+
def stub?; !!@stub; end
|
49
|
+
|
50
|
+
# Is this a new resource, which has not yet been
|
51
|
+
# synched or created within the DB?
|
52
|
+
def new?; !!@new; end
|
53
|
+
|
54
|
+
# Manage contexts used by resources.
|
55
|
+
#
|
56
|
+
# @param [String] ctx
|
57
|
+
# @return [JSON::LD::EvaluationContext]
|
58
|
+
def self.set_context(ctx)
|
59
|
+
(@@contexts ||= {})[ctx] = JSON::LD::EvaluationContext.new.parse(ctx)
|
60
|
+
end
|
61
|
+
|
62
|
+
# A new resource from the parsed graph
|
63
|
+
# @param [Hash{String => Object}] node_definition
|
64
|
+
# @param [Hash{Symbol => Object}] options
|
65
|
+
# @option options [String] :context
|
66
|
+
# Resource context, used for finding
|
67
|
+
# appropriate collection and JSON-LD context.
|
68
|
+
# @option options [Boolean] :clean (false)
|
69
|
+
# @option options [Boolean] :compact (false)
|
70
|
+
# Assume `node_definition` is in expanded form
|
71
|
+
# and compact using `context`.
|
72
|
+
# @option options [Boolean] :reconciled (!new)
|
73
|
+
# node_definition is not based on Mongo IDs
|
74
|
+
# and must be reconciled against Mongo, or merged
|
75
|
+
# into another resource.
|
76
|
+
# @option options [Boolean] :new (true)
|
77
|
+
# This is a new resource, not yet saved to Mongo
|
78
|
+
# @option options [Boolean] :stub (false)
|
79
|
+
# This is a stand-in for another resource that has
|
80
|
+
# not yet been retrieved (or created) from Mongo
|
81
|
+
def initialize(node_definition, options = {})
|
82
|
+
@context_name = options[:context]
|
83
|
+
@context = self.class.set_context(@context_name)
|
84
|
+
@clean = options.fetch(:clean, false)
|
85
|
+
@new = options.fetch(:new, true)
|
86
|
+
@reconciled = options.fetch(:reconciled, !@new)
|
87
|
+
@resolved = false
|
88
|
+
@attributes = if options[:compact]
|
89
|
+
JSON::LD::API.compact(node_definition, @context)
|
90
|
+
else
|
91
|
+
node_definition
|
92
|
+
end
|
93
|
+
@id = @attributes['@id']
|
94
|
+
@anon = @id.nil? || @id.to_s[0,2] == '_:'
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return a hash of this object, suitable for use by for ETag
|
98
|
+
# @return [Fixnum]
|
99
|
+
def hash
|
100
|
+
self.deresolve.hash
|
101
|
+
end
|
102
|
+
|
103
|
+
# Reverse resolution of resource attributes.
|
104
|
+
# Just returns `attributes` if
|
105
|
+
# resource is unresolved. Otherwise, replaces `Resource`
|
106
|
+
# values with node references.
|
107
|
+
#
|
108
|
+
# Result is expanded and re-compacted to get to normalized
|
109
|
+
# representation.
|
110
|
+
#
|
111
|
+
# @return [Hash] deresolved attribute hash
|
112
|
+
def deresolve
|
113
|
+
node_definition = if resolved?
|
114
|
+
deresolved = attributes.keys.inject({}) do |memo, prop|
|
115
|
+
value = attributes[prop]
|
116
|
+
memo[prop] = case value
|
117
|
+
when Resource
|
118
|
+
{'id' => value.id}
|
119
|
+
when Array
|
120
|
+
value.map do |v|
|
121
|
+
v.is_a?(Resource) ? {'id' => v.id} : v
|
122
|
+
end
|
123
|
+
else
|
124
|
+
value
|
125
|
+
end
|
126
|
+
memo
|
127
|
+
end
|
128
|
+
deresolved
|
129
|
+
else
|
130
|
+
attributes
|
131
|
+
end
|
132
|
+
|
133
|
+
compacted = nil
|
134
|
+
JSON::LD::API.expand(node_definition, @context) do |expanded|
|
135
|
+
compacted = JSON::LD::API.compact(expanded, @context)
|
136
|
+
end
|
137
|
+
compacted.delete_if {|k, v| k == '@context'}
|
138
|
+
end
|
139
|
+
|
140
|
+
# Serialize to JSON-LD, minus `@context` using
|
141
|
+
# a deresolved version of the attributes
|
142
|
+
#
|
143
|
+
# @param [Hash] options
|
144
|
+
# @return [String] serizlied JSON representation of resource
|
145
|
+
def to_json(options = nil)
|
146
|
+
deresolve.to_json(options)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Update node references using the provided map.
|
150
|
+
# This replaces node references with Resources,
|
151
|
+
# either stub or instantiated.
|
152
|
+
#
|
153
|
+
# Node references with ids not in the reference_map
|
154
|
+
# will cause stub resources to be added to the map.
|
155
|
+
#
|
156
|
+
# @param [Hash{String => Resource}] reference_map
|
157
|
+
# @return [Resource] self
|
158
|
+
def resolve(reference_map)
|
159
|
+
return if resolved?
|
160
|
+
def update_obj(obj, reference_map)
|
161
|
+
case obj
|
162
|
+
when Array
|
163
|
+
obj.map {|o| update_obj(o, reference_map)}
|
164
|
+
when Hash
|
165
|
+
if obj.node_ref?
|
166
|
+
reference_map[obj['id']] ||= Resource.new(obj,
|
167
|
+
:context => @context_name,
|
168
|
+
:clean => false,
|
169
|
+
:stub => true
|
170
|
+
)
|
171
|
+
else
|
172
|
+
obj.keys.each do |k|
|
173
|
+
obj[k] = update_obj(obj[k], reference_map)
|
174
|
+
end
|
175
|
+
obj
|
176
|
+
end
|
177
|
+
else
|
178
|
+
obj
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
#$logger.debug "resolve(0): #{attributes.inspect}"
|
183
|
+
@attributes.each do |k, v|
|
184
|
+
next if %w(id type).include?(k)
|
185
|
+
@attributes[k] = update_obj(@attributes[k], reference_map)
|
186
|
+
end
|
187
|
+
#$logger.debug "resolve(1): #{attributes.inspect}"
|
188
|
+
@resolved = true
|
189
|
+
self
|
190
|
+
end
|
191
|
+
|
192
|
+
# Merge resources
|
193
|
+
# FIXME: If unreconciled or unresolved resources are merged
|
194
|
+
# against reconciled/resolved resources, they will appear
|
195
|
+
# to not match, even if they are really the same thing.
|
196
|
+
#
|
197
|
+
# @param [Resource] resource
|
198
|
+
# @return [Resource] self
|
199
|
+
def merge(resource)
|
200
|
+
if attributes.neq?(resource.attributes)
|
201
|
+
resource.attributes.each do |p, v|
|
202
|
+
next if p == 'id'
|
203
|
+
if v.nil? or (v.is_a?(Array) and v.empty?)
|
204
|
+
attributes.delete(p)
|
205
|
+
else
|
206
|
+
attributes[p] = v
|
207
|
+
end
|
208
|
+
end
|
209
|
+
@resolved = @clean = false
|
210
|
+
end
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Override this method to implement save using
|
216
|
+
# an appropriate storage mechanism.
|
217
|
+
#
|
218
|
+
# Save the object to the Mongo collection
|
219
|
+
# use Upsert to create things that don't exist.
|
220
|
+
# First makes sure that the resource is valid.
|
221
|
+
#
|
222
|
+
# @return [Boolean] true or false if resource not saved
|
223
|
+
def save
|
224
|
+
raise NotImplemented
|
225
|
+
end
|
226
|
+
|
227
|
+
# Access individual fields, from subject definition
|
228
|
+
def property(prop_name); @attributes.fetch(prop_name, nil); end
|
229
|
+
|
230
|
+
# Access individual fields, from subject definition
|
231
|
+
def method_missing(method, *args)
|
232
|
+
property(method.to_s)
|
233
|
+
end
|
234
|
+
|
235
|
+
def inspect
|
236
|
+
"<Resource" +
|
237
|
+
attributes.map do |k, v|
|
238
|
+
"\n #{k}: #{v.inspect}"
|
239
|
+
end.join(" ") +
|
240
|
+
">"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|