json-ld 2.2.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,23 +10,23 @@ module JSON::LD
10
10
  # Generate a JSON-LD array representation from an array of `RDF::Statement`.
11
11
  # Representation is in expanded form
12
12
  #
13
- # @param [Array<RDF::Statement>, RDF::Enumerable] input
13
+ # @param [Array<RDF::Statement>, RDF::Enumerable] dataset
14
14
  # @param [Boolean] useRdfType (false)
15
15
  # If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
16
16
  # @param [Boolean] useNativeTypes (false) use native representations
17
17
  # @return [Array<Hash>] the JSON-LD document in normalized form
18
- def from_statements(input, useRdfType: false, useNativeTypes: false)
18
+ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
19
19
  default_graph = {}
20
20
  graph_map = {'@default' => default_graph}
21
- node_usages_map = {}
21
+ referenced_once = {}
22
22
 
23
23
  value = nil
24
24
  ec = Context.new
25
25
 
26
26
  # Create a map for node to object representation
27
27
 
28
- # For each triple in input
29
- input.each do |statement|
28
+ # For each statement in dataset
29
+ dataset.each do |statement|
30
30
  #log_debug("statement") { statement.to_nquads.chomp}
31
31
 
32
32
  name = statement.graph_name ? ec.expand_iri(statement.graph_name).to_s : '@default'
@@ -53,16 +53,23 @@ module JSON::LD
53
53
 
54
54
  merge_value(node, statement.predicate.to_s, value)
55
55
 
56
- # If object is a blank node identifier or IRI, it might represent the a list node:
57
- if statement.object.resource?
56
+ # If object is a blank node identifier or rdf:nil, it might represent the a list node:
57
+ if statement.object == RDF.nil
58
58
  # Append a new JSON object consisting of three members, node, property, and value to the usages array. The node member is set to a reference to node, property to predicate, and value to a reference to value.
59
- merge_value(node_map[statement.object.to_s], :usages, {
59
+ object = node_map[statement.object.to_s]
60
+ merge_value(object, :usages, {
60
61
  node: node,
61
62
  property: statement.predicate.to_s,
62
63
  value: value
63
64
  })
64
-
65
- (node_usages_map[statement.object.to_s] ||= []) << node['@id']
65
+ elsif referenced_once.has_key?(statement.object.to_s)
66
+ referenced_once[statement.object.to_s] = false
67
+ elsif statement.object.node?
68
+ referenced_once[statement.object.to_s] = {
69
+ node: node,
70
+ property: statement.predicate.to_s,
71
+ value: value
72
+ }
66
73
  end
67
74
  end
68
75
 
@@ -78,30 +85,31 @@ module JSON::LD
78
85
  # If property equals rdf:rest, the value associated to the usages member of node has exactly 1 entry, node has a rdf:first and rdf:rest property, both of which have as value an array consisting of a single element, and node has no other members apart from an optional @type member whose value is an array with a single item equal to rdf:List, node represents a well-formed list node. Continue with the following steps:
79
86
  #log_debug("list element?") {node.to_json(JSON_STATE) rescue 'malformed json'}
80
87
  while property == RDF.rest.to_s &&
81
- Array(node_usages_map[node['@id']]).uniq.length == 1 &&
82
88
  blank_node?(node) &&
89
+ referenced_once[node['@id']] &&
83
90
  node.keys.none? {|k| !["@id", '@type', :usages, RDF.first.to_s, RDF.rest.to_s].include?(k)} &&
84
- Array(node[:usages]).length == 1 &&
85
91
  (f = node[RDF.first.to_s]).is_a?(Array) && f.length == 1 &&
86
92
  (r = node[RDF.rest.to_s]).is_a?(Array) && r.length == 1 &&
87
93
  ((t = node['@type']).nil? || t == [RDF.List.to_s])
88
94
  list << Array(node[RDF.first.to_s]).first
89
95
  list_nodes << node['@id']
90
- node_usage = Array(node[:usages]).first
96
+
97
+ # get next node, moving backwards through list
98
+ node_usage = referenced_once[node['@id']]
91
99
  node, property, head = node_usage[:node], node_usage[:property], node_usage[:value]
92
100
  end
93
101
 
94
102
  # If property equals rdf:first, i.e., the detected list is nested inside another list
95
- if property == RDF.first.to_s
96
- # and the value of the @id of node equals rdf:nil, i.e., the detected list is empty, continue with the next usage item. The rdf:nil node cannot be converted to a list object as it would result in a list of lists, which isn't supported.
97
- next if node['@id'] == RDF.nil.to_s
98
-
99
- # Otherwise, the list consists of at least one item. We preserve the head node and transform the rest of the linked list to a list object
100
- head_id = head['@id']
101
- head = graph_object[head_id]
102
- head = Array(head[RDF.rest.to_s]).first
103
- list.pop; list_nodes.pop
104
- end
103
+ #if property == RDF.first.to_s
104
+ # # and the value of the @id of node equals rdf:nil, i.e., the detected list is empty, continue with the next usage item. The rdf:nil node cannot be converted to a list object as it would result in a list of lists, which isn't supported.
105
+ # next if node['@id'] == RDF.nil.to_s
106
+ #
107
+ # # Otherwise, the list consists of at least one item. We preserve the head node and transform the rest of the linked list to a list object
108
+ # head_id = head['@id']
109
+ # head = graph_object[head_id]
110
+ # head = Array(head[RDF.rest.to_s]).first
111
+ # list.pop; list_nodes.pop
112
+ #end
105
113
 
106
114
  head.delete('@id')
107
115
  head['@list'] = list.reverse
@@ -76,7 +76,7 @@ module JSON::LD
76
76
  # @option options [Boolean] :stub (false)
77
77
  # This is a stand-in for another resource that has
78
78
  # not yet been retrieved (or created) from Mongo
79
- def initialize(node_definition, options = {})
79
+ def initialize(node_definition, **options)
80
80
  @context = options[:context]
81
81
  @clean = options.fetch(:clean, false)
82
82
  @new = options.fetch(:new, true)
@@ -37,6 +37,9 @@ module JSON::LD
37
37
  # Initialize literal as an RDF literal using value and datatype. If element has the key @language and datatype is xsd:string, then add the value associated with the @language key as the language of the object.
38
38
  language = item.fetch('@language', nil)
39
39
  return RDF::Literal.new(value, datatype: datatype, language: language)
40
+ elsif list?(item)
41
+ # If item is a list object, initialize list_results as an empty array, and object to the result of the List Conversion algorithm, passing the value associated with the @list key from item and list_results.
42
+ return parse_list(item['@list'], graph_name: graph_name, &block)
40
43
  end
41
44
 
42
45
  subject = item['@id'] ? as_resource(item['@id']) : node
@@ -64,8 +67,7 @@ module JSON::LD
64
67
  vv.each do |v|
65
68
  if list?(v)
66
69
  #log_debug("item_to_rdf") {"list: #{v.inspect}"}
67
- # If item is a list object, initialize list_results as an empty array, and object to the result of the List Conversion algorithm, passing the value associated with the @list key from item and list_results.
68
- object = parse_list(v['@list'], graph_name: graph_name, &block)
70
+ object = item_to_rdf(v, graph_name: graph_name, &block)
69
71
 
70
72
  # Append a triple composed of object, prediate, and object to results and add all triples from list_results to results.
71
73
  yield RDF::Statement(object, predicate, subject, graph_name: graph_name)
@@ -110,6 +110,14 @@ module JSON::LD
110
110
  end
111
111
  end
112
112
 
113
+ ##
114
+ # Represent as an array
115
+ # @param [Object] object
116
+ # @return [Array<Object>]
117
+ def as_array(object)
118
+ object.is_a?(Array) ? object : [object]
119
+ end
120
+
113
121
  ##
114
122
  # Compares two JSON-LD values for equality. Two JSON-LD values will be
115
123
  # considered equal if:
@@ -142,30 +150,33 @@ module JSON::LD
142
150
  # @param [Hash] subject the hash to add the value to.
143
151
  # @param [String] property the property that relates the value to the subject.
144
152
  # @param [Object] value the value to add.
145
- # @param [Hash{Symbol => Object}] options
146
- # @option options [Boolean] :property_is_array
147
- # true if the property is always (false)
148
- # an array, false if not.
149
- # @option options [Boolean] :allow_duplicate (true)
153
+ # @param [Boolean] property_is_array (false)
154
+ # true if the property is always an array, false if not.
155
+ # @param [Boolean] value_is_array (false)
156
+ # true if the value to be added should be preserved as an array (lists)
157
+ # @param [Boolean] allow_duplicate (true)
150
158
  # true to allow duplicates, false not to (uses
151
159
  # a simple shallow comparison of subject ID or value).
152
- def add_value(subject, property, value, options = {})
153
- options = {property_is_array: false, allow_duplicate: true}.merge!(options)
154
-
155
- if value.is_a?(Array)
156
- subject[property] = [] if value.empty? && options[:property_is_array]
157
- value.each {|v| add_value(subject, property, v, options)}
160
+ def add_value(subject, property, value, property_is_array: false, value_is_array: false, allow_duplicate: true)
161
+ if value_is_array
162
+ subject[property] = value
163
+ elsif value.is_a?(Array)
164
+ subject[property] = [] if value.empty? && property_is_array
165
+ value.each do |v|
166
+ add_value(subject, property, v,
167
+ property_is_array: property_is_array, allow_duplicate: allow_duplicate)
168
+ end
158
169
  elsif subject[property]
159
170
  # check if subject already has value if duplicates not allowed
160
- _has_value = !options[:allow_duplicate] && has_value(subject, property, value)
171
+ _has_value = !allow_duplicate && has_value(subject, property, value)
161
172
 
162
173
  # make property an array if value not present or always an array
163
- if !subject[property].is_a?(Array) && (!_has_value || options[:property_is_array])
174
+ if !subject[property].is_a?(Array) && (!_has_value || property_is_array)
164
175
  subject[property] = [subject[property]]
165
176
  end
166
177
  subject[property] << value unless _has_value
167
178
  else
168
- subject[property] = options[:property_is_array] ? [value] : value
179
+ subject[property] = property_is_array ? [value] : value
169
180
  end
170
181
  end
171
182
 
@@ -219,27 +230,6 @@ module JSON::LD
219
230
  values << value
220
231
  end
221
232
  end
222
-
223
- # Merge values into compacted results, creating arrays if necessary
224
- def merge_compacted_value(hash, key, value)
225
- return unless hash
226
- case hash[key]
227
- when nil then hash[key] = value
228
- when Array
229
- if value.is_a?(Array)
230
- hash[key].concat(value)
231
- else
232
- hash[key] << value
233
- end
234
- else
235
- hash[key] = [hash[key]]
236
- if value.is_a?(Array)
237
- hash[key].concat(value)
238
- else
239
- hash[key] << value
240
- end
241
- end
242
- end
243
233
  end
244
234
 
245
235
  ##
@@ -90,12 +90,36 @@ module JSON::LD
90
90
  control: :url2,
91
91
  on: ["--context CONTEXT"],
92
92
  description: "Context to use when compacting.") {|arg| RDF::URI(arg)},
93
+ RDF::CLI::Option.new(
94
+ symbol: :embed,
95
+ datatype: %w(@always @last @never),
96
+ control: :select,
97
+ on: ["--embed EMBED"],
98
+ description: "How to embed matched objects (@last).") {|arg| RDF::URI(arg)},
99
+ RDF::CLI::Option.new(
100
+ symbol: :explicit,
101
+ datatype: TrueClass,
102
+ control: :checkbox,
103
+ on: ["--explicit"],
104
+ description: "Only include explicitly declared properties in output (false)") {|arg| RDF::URI(arg)},
105
+ RDF::CLI::Option.new(
106
+ symbol: :omitDefault,
107
+ datatype: TrueClass,
108
+ control: :checkbox,
109
+ on: ["--omitDefault"],
110
+ description: "Omit missing properties from output (false)") {|arg| RDF::URI(arg)},
93
111
  RDF::CLI::Option.new(
94
112
  symbol: :processing_mode,
95
113
  datatype: %w(json-ld-1.0 json-ld-1.1),
96
114
  control: :radio,
97
115
  on: ["--processingMode MODE", %w(json-ld-1.0 json-ld-1.1)],
98
116
  description: "Set Processing Mode (json-ld-1.0 or json-ld-1.1)"),
117
+ RDF::CLI::Option.new(
118
+ symbol: :requireAll,
119
+ datatype: TrueClass,
120
+ control: :checkbox,
121
+ on: ["--requireAll"],
122
+ description: "Require all properties to match (true)") {|arg| RDF::URI(arg)},
99
123
  RDF::CLI::Option.new(
100
124
  symbol: :stream,
101
125
  datatype: TrueClass,
@@ -139,7 +163,7 @@ module JSON::LD
139
163
  # @yieldreturn [void]
140
164
  # @yield [writer]
141
165
  # @yieldparam [RDF::Writer] writer
142
- def initialize(output = $stdout, options = {}, &block)
166
+ def initialize(output = $stdout, **options, &block)
143
167
  options[:base_uri] ||= options[:base] if options.has_key?(:base)
144
168
  options[:base] ||= options[:base_uri] if options.has_key?(:base_uri)
145
169
  super do
@@ -3,6 +3,7 @@ require_relative 'spec_helper'
3
3
 
4
4
  describe JSON::LD::API do
5
5
  let(:logger) {RDF::Spec.logger}
6
+ before {JSON::LD::Context::PRELOADED.clear}
6
7
 
7
8
  describe "#initialize" do
8
9
  context "with string input" do
@@ -292,7 +292,47 @@ describe JSON::LD::API do
292
292
  },
293
293
  "http://example.org/vocab#contains": "this-is-not-an-IRI"
294
294
  })
295
- }
295
+ },
296
+ "Language map term with language value" => {
297
+ input: %([{"http://example/t": {"@value": "foo", "@language": "en"}}]),
298
+ context: %({"t": {"@id": "http://example/t", "@container": "@language"}}),
299
+ output: %({
300
+ "@context": {
301
+ "t": {"@id": "http://example/t", "@container": "@language"}
302
+ },
303
+ "t": {"en": "foo"}
304
+ })
305
+ },
306
+ "Datatyped term with datatyped value" => {
307
+ input: %([{"http://example/t": {"@value": "foo", "@type": "http:/example/type"}}]),
308
+ context: %({"t": {"@id": "http://example/t", "@type": "http:/example/type"}}),
309
+ output: %({
310
+ "@context": {
311
+ "t": {"@id": "http://example/t", "@type": "http:/example/type"}
312
+ },
313
+ "t": "foo"
314
+ })
315
+ },
316
+ "Datatyped term with simple value" => {
317
+ input: %([{"http://example/t": {"@value": "foo"}}]),
318
+ context: %({"t": {"@id": "http://example/t", "@type": "http:/example/type"}}),
319
+ output: %({
320
+ "@context": {
321
+ "t": {"@id": "http://example/t", "@type": "http:/example/type"}
322
+ },
323
+ "http://example/t": "foo"
324
+ })
325
+ },
326
+ "Datatyped term with object value" => {
327
+ input: %([{"http://example/t": {"@id": "http://example/id"}}]),
328
+ context: %({"t": {"@id": "http://example/t", "@type": "http:/example/type"}}),
329
+ output: %({
330
+ "@context": {
331
+ "t": {"@id": "http://example/t", "@type": "http:/example/type"}
332
+ },
333
+ "http://example/t": {"@id": "http://example/id"}
334
+ })
335
+ },
296
336
  }.each_pair do |title, params|
297
337
  it(title) {run_compact(params)}
298
338
  end
@@ -487,6 +527,72 @@ describe JSON::LD::API do
487
527
  "foo_de": ["de"]
488
528
  })
489
529
  },
530
+ "coerced @list containing an empty list" => {
531
+ input: %([{
532
+ "http://example.com/foo": [{"@list": [{"@list": []}]}]
533
+ }]),
534
+ context: %({
535
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
536
+ }),
537
+ output: %({
538
+ "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
539
+ "foo": [[]]
540
+ }),
541
+ },
542
+ "coerced @list containing a list" => {
543
+ input: %([{
544
+ "http://example.com/foo": [{"@list": [{"@list": [{"@value": "baz"}]}]}]
545
+ }]),
546
+ context: %({
547
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
548
+ }),
549
+ output: %({
550
+ "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
551
+ "foo": [["baz"]]
552
+ }),
553
+ },
554
+ "coerced @list containing an deep list" => {
555
+ input: %([{
556
+ "http://example.com/foo": [{"@list": [{"@list": [{"@list": [{"@value": "baz"}]}]}]}]
557
+ }]),
558
+ context: %({
559
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
560
+ }),
561
+ output: %({
562
+ "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
563
+ "foo": [[["baz"]]]
564
+ }),
565
+ },
566
+ "coerced @list containing multiple lists" => {
567
+ input: %([{
568
+ "http://example.com/foo": [{"@list": [
569
+ {"@list": [{"@value": "a"}]},
570
+ {"@list": [{"@value": "b"}]}
571
+ ]}]
572
+ }]),
573
+ context: %({
574
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
575
+ }),
576
+ output: %({
577
+ "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
578
+ "foo": [["a"], ["b"]]
579
+ }),
580
+ },
581
+ "coerced @list containing mixed list values" => {
582
+ input: %([{
583
+ "http://example.com/foo": [{"@list": [
584
+ {"@list": [{"@value": "a"}]},
585
+ {"@value": "b"}
586
+ ]}]
587
+ }]),
588
+ context: %({
589
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
590
+ }),
591
+ output: %({
592
+ "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
593
+ "foo": [["a"], "b"]
594
+ }),
595
+ },
490
596
  }.each_pair do |title, params|
491
597
  it(title) {run_compact(params)}
492
598
  end
@@ -526,6 +632,96 @@ describe JSON::LD::API do
526
632
  }
527
633
  })
528
634
  },
635
+ "simple map with @none node definition" => {
636
+ input: %([{
637
+ "@id": "http://example.com/article",
638
+ "http://example.com/vocab/author": [{
639
+ "@id": "http://example.org/person/1",
640
+ "@index": "regular"
641
+ }, {
642
+ "@id": "http://example.org/guest/cd24f329aa"
643
+ }]
644
+ }]),
645
+ context: %({
646
+ "author": {"@id": "http://example.com/vocab/author", "@container": "@index" }
647
+ }),
648
+ output: %({
649
+ "@context": {
650
+ "author": {
651
+ "@id": "http://example.com/vocab/author",
652
+ "@container": "@index"
653
+ }
654
+ },
655
+ "@id": "http://example.com/article",
656
+ "author": {
657
+ "regular": {
658
+ "@id": "http://example.org/person/1"
659
+ },
660
+ "@none": {
661
+ "@id": "http://example.org/guest/cd24f329aa"
662
+ }
663
+ }
664
+ }),
665
+ processingMode: 'json-ld-1.1'
666
+ },
667
+ "simple map with @none value" => {
668
+ input: %([{
669
+ "@id": "http://example.com/article",
670
+ "http://example.com/vocab/author": [{
671
+ "@value": "Gregg",
672
+ "@index": "regular"
673
+ }, {
674
+ "@value": "Manu"
675
+ }]
676
+ }]),
677
+ context: %({
678
+ "author": {"@id": "http://example.com/vocab/author", "@container": "@index" }
679
+ }),
680
+ output: %({
681
+ "@context": {
682
+ "author": {
683
+ "@id": "http://example.com/vocab/author",
684
+ "@container": "@index"
685
+ }
686
+ },
687
+ "@id": "http://example.com/article",
688
+ "author": {
689
+ "regular": "Gregg",
690
+ "@none": "Manu"
691
+ }
692
+ }),
693
+ processingMode: 'json-ld-1.1'
694
+ },
695
+ "simple map with @none value using alias of @none" => {
696
+ input: %([{
697
+ "@id": "http://example.com/article",
698
+ "http://example.com/vocab/author": [{
699
+ "@value": "Gregg",
700
+ "@index": "regular"
701
+ }, {
702
+ "@value": "Manu"
703
+ }]
704
+ }]),
705
+ context: %({
706
+ "author": {"@id": "http://example.com/vocab/author", "@container": "@index" },
707
+ "none": "@none"
708
+ }),
709
+ output: %({
710
+ "@context": {
711
+ "author": {
712
+ "@id": "http://example.com/vocab/author",
713
+ "@container": "@index"
714
+ },
715
+ "none": "@none"
716
+ },
717
+ "@id": "http://example.com/article",
718
+ "author": {
719
+ "regular": "Gregg",
720
+ "none": "Manu"
721
+ }
722
+ }),
723
+ processingMode: 'json-ld-1.1'
724
+ },
529
725
  }.each_pair do |title, params|
530
726
  it(title) {run_compact(params)}
531
727
  end
@@ -560,6 +756,66 @@ describe JSON::LD::API do
560
756
  }
561
757
  })
562
758
  },
759
+ "with no @language" => {
760
+ input: %([
761
+ {
762
+ "@id": "http://example.com/queen",
763
+ "http://example.com/vocab/label": [
764
+ {"@value": "The Queen", "@language": "en"},
765
+ {"@value": "Die Königin", "@language": "de"},
766
+ {"@value": "Ihre Majestät"}
767
+ ]
768
+ }
769
+ ]),
770
+ context: %({
771
+ "vocab": "http://example.com/vocab/",
772
+ "label": {"@id": "vocab:label", "@container": "@language"}
773
+ }),
774
+ output: %({
775
+ "@context": {
776
+ "vocab": "http://example.com/vocab/",
777
+ "label": {"@id": "vocab:label", "@container": "@language"}
778
+ },
779
+ "@id": "http://example.com/queen",
780
+ "label": {
781
+ "en": "The Queen",
782
+ "de": "Die Königin",
783
+ "@none": "Ihre Majestät"
784
+ }
785
+ }),
786
+ processingMode: "json-ld-1.1"
787
+ },
788
+ "with no @language using alias of @none" => {
789
+ input: %([
790
+ {
791
+ "@id": "http://example.com/queen",
792
+ "http://example.com/vocab/label": [
793
+ {"@value": "The Queen", "@language": "en"},
794
+ {"@value": "Die Königin", "@language": "de"},
795
+ {"@value": "Ihre Majestät"}
796
+ ]
797
+ }
798
+ ]),
799
+ context: %({
800
+ "vocab": "http://example.com/vocab/",
801
+ "label": {"@id": "vocab:label", "@container": "@language"},
802
+ "none": "@none"
803
+ }),
804
+ output: %({
805
+ "@context": {
806
+ "vocab": "http://example.com/vocab/",
807
+ "label": {"@id": "vocab:label", "@container": "@language"},
808
+ "none": "@none"
809
+ },
810
+ "@id": "http://example.com/queen",
811
+ "label": {
812
+ "en": "The Queen",
813
+ "de": "Die Königin",
814
+ "none": "Ihre Majestät"
815
+ }
816
+ }),
817
+ processingMode: "json-ld-1.1"
818
+ },
563
819
  }.each_pair do |title, params|
564
820
  it(title) {run_compact(params)}
565
821
  end
@@ -633,6 +889,52 @@ describe JSON::LD::API do
633
889
  }
634
890
  })
635
891
  },
892
+ "Indexes using @none" => {
893
+ input: %([{
894
+ "http://example/idmap": [
895
+ {"http://example/label": [{"@value": "Object with no @id"}]}
896
+ ]
897
+ }]),
898
+ context: %({
899
+ "@vocab": "http://example/",
900
+ "ex": "http://example.org/",
901
+ "idmap": {"@container": "@id"}
902
+ }),
903
+ output: %({
904
+ "@context": {
905
+ "@vocab": "http://example/",
906
+ "ex": "http://example.org/",
907
+ "idmap": {"@container": "@id"}
908
+ },
909
+ "idmap": {
910
+ "@none": {"label": "Object with no @id"}
911
+ }
912
+ })
913
+ },
914
+ "Indexes using @none with alias" => {
915
+ input: %([{
916
+ "http://example/idmap": [
917
+ {"http://example/label": [{"@value": "Object with no @id"}]}
918
+ ]
919
+ }]),
920
+ context: %({
921
+ "@vocab": "http://example/",
922
+ "ex": "http://example.org/",
923
+ "idmap": {"@container": "@id"},
924
+ "none": "@none"
925
+ }),
926
+ output: %({
927
+ "@context": {
928
+ "@vocab": "http://example/",
929
+ "ex": "http://example.org/",
930
+ "idmap": {"@container": "@id"},
931
+ "none": "@none"
932
+ },
933
+ "idmap": {
934
+ "none": {"label": "Object with no @id"}
935
+ }
936
+ })
937
+ },
636
938
  }.each_pair do |title, params|
637
939
  it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
638
940
  end
@@ -738,6 +1040,52 @@ describe JSON::LD::API do
738
1040
  }
739
1041
  })
740
1042
  },
1043
+ "Indexes using @none" => {
1044
+ input: %([{
1045
+ "http://example/typemap": [
1046
+ {"http://example/label": [{"@value": "Object with no @type"}]}
1047
+ ]
1048
+ }]),
1049
+ context: %({
1050
+ "@vocab": "http://example/",
1051
+ "ex": "http://example.org/",
1052
+ "typemap": {"@container": "@type"}
1053
+ }),
1054
+ output: %({
1055
+ "@context": {
1056
+ "@vocab": "http://example/",
1057
+ "ex": "http://example.org/",
1058
+ "typemap": {"@container": "@type"}
1059
+ },
1060
+ "typemap": {
1061
+ "@none": {"label": "Object with no @type"}
1062
+ }
1063
+ })
1064
+ },
1065
+ "Indexes using @none with alias" => {
1066
+ input: %([{
1067
+ "http://example/typemap": [
1068
+ {"http://example/label": [{"@value": "Object with no @id"}]}
1069
+ ]
1070
+ }]),
1071
+ context: %({
1072
+ "@vocab": "http://example/",
1073
+ "ex": "http://example.org/",
1074
+ "typemap": {"@container": "@type"},
1075
+ "none": "@none"
1076
+ }),
1077
+ output: %({
1078
+ "@context": {
1079
+ "@vocab": "http://example/",
1080
+ "ex": "http://example.org/",
1081
+ "typemap": {"@container": "@type"},
1082
+ "none": "@none"
1083
+ },
1084
+ "typemap": {
1085
+ "none": {"label": "Object with no @id"}
1086
+ }
1087
+ })
1088
+ },
741
1089
  }.each_pair do |title, params|
742
1090
  it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
743
1091
  end
@@ -832,10 +1180,57 @@ describe JSON::LD::API do
832
1180
  },
833
1181
  "input": {
834
1182
  "@id": "http://example.org/id",
835
- "@graph": [{"value": "x"}]
1183
+ "@graph": {"value": "x"}
836
1184
  }
837
1185
  })
838
1186
  },
1187
+ "Odd framing test" => {
1188
+ input: %([
1189
+ {
1190
+ "http://example.org/claim": [
1191
+ {
1192
+ "@graph": [
1193
+ {
1194
+ "@id": "http://example.org/1",
1195
+ "https://example.com#test": [
1196
+ {
1197
+ "@value": "foo"
1198
+ }
1199
+ ]
1200
+ }
1201
+ ]
1202
+ }
1203
+ ]
1204
+ }
1205
+ ]
1206
+ ),
1207
+ context: %( {
1208
+ "@version": 1.1,
1209
+ "@vocab": "https://example.com#",
1210
+ "ex": "http://example.org/",
1211
+ "claim": {
1212
+ "@id": "ex:claim",
1213
+ "@container": "@graph"
1214
+ },
1215
+ "id": "@id"
1216
+ }),
1217
+ output: %({
1218
+ "@context": {
1219
+ "@version": 1.1,
1220
+ "@vocab": "https://example.com#",
1221
+ "ex": "http://example.org/",
1222
+ "claim": {
1223
+ "@id": "ex:claim",
1224
+ "@container": "@graph"
1225
+ },
1226
+ "id": "@id"
1227
+ },
1228
+ "claim": {
1229
+ "id": "ex:1",
1230
+ "test": "foo"
1231
+ }
1232
+ })
1233
+ }
839
1234
  }.each_pair do |title, params|
840
1235
  it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
841
1236
  end
@@ -888,6 +1283,28 @@ describe JSON::LD::API do
888
1283
  }
889
1284
  })
890
1285
  },
1286
+ "Compacts simple graph with no @index" => {
1287
+ input: %([{
1288
+ "http://example.org/input": [{
1289
+ "@graph": [{
1290
+ "http://example.org/value": [{"@value": "x"}]
1291
+ }]
1292
+ }]
1293
+ }]),
1294
+ context: %({
1295
+ "@vocab": "http://example.org/",
1296
+ "input": {"@container": ["@graph", "@index", "@set"]}
1297
+ }),
1298
+ output: %({
1299
+ "@context": {
1300
+ "@vocab": "http://example.org/",
1301
+ "input": {"@container": ["@graph", "@index", "@set"]}
1302
+ },
1303
+ "input": {
1304
+ "@none": [{"value": "x"}]
1305
+ }
1306
+ })
1307
+ },
891
1308
  "Does not compact graph with @id" => {
892
1309
  input: %([{
893
1310
  "http://example.org/input": [{
@@ -910,7 +1327,7 @@ describe JSON::LD::API do
910
1327
  "input": {
911
1328
  "@id": "http://example.org/id",
912
1329
  "@index": "g1",
913
- "@graph": [{"value": "x"}]
1330
+ "@graph": {"value": "x"}
914
1331
  }
915
1332
  })
916
1333
  },
@@ -939,7 +1356,7 @@ describe JSON::LD::API do
939
1356
  "input": {"@container": ["@graph", "@id"]}
940
1357
  },
941
1358
  "input": {
942
- "_:b0": {"value": "x"}
1359
+ "@none": {"value": "x"}
943
1360
  }
944
1361
  })
945
1362
  },
@@ -960,7 +1377,7 @@ describe JSON::LD::API do
960
1377
  "@vocab": "http://example.org/",
961
1378
  "input": {"@container": ["@graph", "@id", "@set"]}
962
1379
  },
963
- "input": {"_:b0": [{"value": "x"}]}
1380
+ "input": {"@none": [{"value": "x"}]}
964
1381
  })
965
1382
  },
966
1383
  "Compacts simple graph with @index" => {
@@ -982,7 +1399,7 @@ describe JSON::LD::API do
982
1399
  "input": {"@container": ["@graph", "@id"]}
983
1400
  },
984
1401
  "input": {
985
- "_:b0": {"value": "x"}
1402
+ "@none": {"value": "x"}
986
1403
  }
987
1404
  })
988
1405
  },
@@ -1032,6 +1449,52 @@ describe JSON::LD::API do
1032
1449
  }
1033
1450
  })
1034
1451
  },
1452
+ "Compacts graph without @id" => {
1453
+ input: %([{
1454
+ "http://example.org/input": [{
1455
+ "@graph": [{
1456
+ "http://example.org/value": [{"@value": "x"}]
1457
+ }]
1458
+ }]
1459
+ }]),
1460
+ context: %({
1461
+ "@vocab": "http://example.org/",
1462
+ "input": {"@container": ["@graph", "@id"]}
1463
+ }),
1464
+ output: %({
1465
+ "@context": {
1466
+ "@vocab": "http://example.org/",
1467
+ "input": {"@container": ["@graph", "@id"]}
1468
+ },
1469
+ "input": {
1470
+ "@none" : {"value": "x"}
1471
+ }
1472
+ })
1473
+ },
1474
+ "Compacts graph without @id using alias of @none" => {
1475
+ input: %([{
1476
+ "http://example.org/input": [{
1477
+ "@graph": [{
1478
+ "http://example.org/value": [{"@value": "x"}]
1479
+ }]
1480
+ }]
1481
+ }]),
1482
+ context: %({
1483
+ "@vocab": "http://example.org/",
1484
+ "input": {"@container": ["@graph", "@id"]},
1485
+ "none": "@none"
1486
+ }),
1487
+ output: %({
1488
+ "@context": {
1489
+ "@vocab": "http://example.org/",
1490
+ "input": {"@container": ["@graph", "@id"]},
1491
+ "none": "@none"
1492
+ },
1493
+ "input": {
1494
+ "none" : {"value": "x"}
1495
+ }
1496
+ })
1497
+ },
1035
1498
  }.each_pair do |title, params|
1036
1499
  it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
1037
1500
  end
@@ -1040,7 +1503,7 @@ describe JSON::LD::API do
1040
1503
 
1041
1504
  context "@nest" do
1042
1505
  {
1043
- "Indexes to @nest for property with @container: @nest" => {
1506
+ "Indexes to @nest for property with @nest" => {
1044
1507
  input: %([{
1045
1508
  "http://example.org/p1": [{"@value": "v1"}],
1046
1509
  "http://example.org/p2": [{"@value": "v2"}]
@@ -1060,7 +1523,7 @@ describe JSON::LD::API do
1060
1523
  }
1061
1524
  })
1062
1525
  },
1063
- "Indexes to @nest for all properties with @container: @nest" => {
1526
+ "Indexes to @nest for all properties with @nest" => {
1064
1527
  input: %([{
1065
1528
  "http://example.org/p1": [{"@value": "v1"}],
1066
1529
  "http://example.org/p2": [{"@value": "v2"}]
@@ -1566,6 +2029,28 @@ describe JSON::LD::API do
1566
2029
  "c": "C in example"
1567
2030
  }),
1568
2031
  },
2032
+ "orders lexicographically" => {
2033
+ input: %([{
2034
+ "@type": ["http://example/t2", "http://example/t1"],
2035
+ "http://example.org/foo": [
2036
+ {"@id": "urn:bar"}
2037
+ ]
2038
+ }]),
2039
+ context: %({
2040
+ "@vocab": "http://example/",
2041
+ "t1": {"@context": {"foo": {"@id": "http://example.com/foo"}}},
2042
+ "t2": {"@context": {"foo": {"@id": "http://example.org/foo", "@type": "@id"}}}
2043
+ }),
2044
+ output: %({
2045
+ "@context": {
2046
+ "@vocab": "http://example/",
2047
+ "t1": {"@context": {"foo": {"@id": "http://example.com/foo"}}},
2048
+ "t2": {"@context": {"foo": {"@id": "http://example.org/foo", "@type": "@id"}}}
2049
+ },
2050
+ "@type": ["t2", "t1"],
2051
+ "foo": "urn:bar"
2052
+ }),
2053
+ },
1569
2054
  "with @container: @type" => {
1570
2055
  input: %([{
1571
2056
  "http://example/typemap": [
@@ -1605,41 +2090,53 @@ describe JSON::LD::API do
1605
2090
  validate: true,
1606
2091
  exception: JSON::LD::JsonLdError::InvalidTermDefinition
1607
2092
  },
1608
- }.each_pair do |title, params|
1609
- it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
1610
- end
1611
- end
1612
-
1613
- context "exceptions" do
1614
- {
1615
- "@list containing @list" => {
1616
- input: %({
1617
- "http://example.org/foo": {"@list": [{"@list": ["baz"]}]}
2093
+ "applies context for all values" => {
2094
+ input: %([
2095
+ {
2096
+ "@id": "http://example.org/id",
2097
+ "@type": ["http://example/type"],
2098
+ "http://example/a": [{
2099
+ "@id": "http://example.org/Foo",
2100
+ "@type": ["http://example/Foo"],
2101
+ "http://example/bar": [{"@id": "http://example.org/baz"}]
2102
+ }]
2103
+ }
2104
+ ]),
2105
+ context: %({
2106
+ "@vocab": "http://example/",
2107
+ "id": "@id",
2108
+ "type": "@type",
2109
+ "Foo": {"@context": {"id": null, "type": null}}
1618
2110
  }),
1619
- context: {},
1620
- exception: JSON::LD::JsonLdError::ListOfLists
1621
- },
1622
- "@list containing @list (with coercion)" => {
1623
- input: %({
1624
- "@context": {"http://example.org/foo": {"@container": "@list"}},
1625
- "http://example.org/foo": [{"@list": ["baz"]}]
2111
+ output: %({
2112
+ "@context": {
2113
+ "@vocab": "http://example/",
2114
+ "id": "@id",
2115
+ "type": "@type",
2116
+ "Foo": {"@context": {"id": null, "type": null}}
2117
+ },
2118
+ "id": "http://example.org/id",
2119
+ "type": "http://example/type",
2120
+ "a": {
2121
+ "@id": "http://example.org/Foo",
2122
+ "@type": "Foo",
2123
+ "bar": {"@id": "http://example.org/baz"}
2124
+ }
1626
2125
  }),
1627
- context: {},
1628
- exception: JSON::LD::JsonLdError::ListOfLists
1629
2126
  },
1630
- }.each do |title, params|
1631
- it(title) {run_compact(params)}
2127
+ }.each_pair do |title, params|
2128
+ it(title) {run_compact({processingMode: "json-ld-1.1"}.merge(params))}
1632
2129
  end
1633
2130
  end
1634
2131
 
1635
2132
  context "compact IRI selection" do
1636
2133
  {
1637
- "compacts using expanded term in 1.0" => {
2134
+ "does not compact using expanded term in 1.0" => {
1638
2135
  input: %({"http://example.org/foo": "term"}),
1639
2136
  context: %({"ex": {"@id": "http://example.org/"}}),
1640
2137
  output: %({
1641
2138
  "@context": {"ex": {"@id": "http://example.org/"}},
1642
- "ex:foo": "term"
2139
+ "http://example.org/foo": "term"
1643
2140
  }),
1644
2141
  processingMode: "json-ld-1.0"
1645
2142
  },
@@ -1716,6 +2213,14 @@ describe JSON::LD::API do
1716
2213
  "ex:bar": "term"
1717
2214
  })
1718
2215
  },
2216
+ "compacts using base if @vocab: relative" => {
2217
+ input: %({"http://example.org/foo/bar": "term"}),
2218
+ context: %({"@base": "http://example.org/foo/", "@vocab": ""}),
2219
+ output: %({
2220
+ "@context": {"@base": "http://example.org/foo/", "@vocab": ""},
2221
+ "bar": "term"
2222
+ })
2223
+ },
1719
2224
  }.each do |title, params|
1720
2225
  it(title) {run_compact(params)}
1721
2226
  end