json-ld 3.1.0 → 3.1.5

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.
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  # frozen_string_literal: true
3
3
  $:.unshift(File.expand_path("../ld", __FILE__))
4
- require 'rdf' # @see http://rubygems.org/gems/rdf
4
+ require 'rdf' # @see https://rubygems.org/gems/rdf
5
5
  require 'multi_json'
6
6
  require 'set'
7
7
 
@@ -19,7 +19,7 @@ module JSON
19
19
  # end
20
20
  # end
21
21
  #
22
- # @see http://rubygems.org/gems/rdf
22
+ # @see https://rubygems.org/gems/rdf
23
23
  # @see http://www.w3.org/TR/REC-rdf-syntax/
24
24
  #
25
25
  # @author [Gregg Kellogg](http://greggkellogg.net/)
@@ -34,6 +34,8 @@ module JSON
34
34
  autoload :Normalize, 'json/ld/normalize'
35
35
  autoload :Reader, 'json/ld/reader'
36
36
  autoload :Resource, 'json/ld/resource'
37
+ autoload :StreamingReader, 'json/ld/streaming_reader'
38
+ autoload :StreamingWriter, 'json/ld/streaming_writer'
37
39
  autoload :VERSION, 'json/ld/version'
38
40
  autoload :Writer, 'json/ld/writer'
39
41
 
@@ -52,13 +54,13 @@ module JSON
52
54
  @direction
53
55
  @embed
54
56
  @explicit
55
- @json
56
- @id
57
- @included
58
- @index
59
57
  @first
60
58
  @graph
59
+ @id
61
60
  @import
61
+ @included
62
+ @index
63
+ @json
62
64
  @language
63
65
  @list
64
66
  @nest
@@ -66,6 +68,7 @@ module JSON
66
68
  @omitDefault
67
69
  @propagate
68
70
  @protected
71
+ @preserve
69
72
  @requireAll
70
73
  @reverse
71
74
  @set
@@ -115,7 +118,7 @@ module JSON
115
118
  class CyclicIRIMapping < JsonLdError; @code = "cyclic IRI mapping"; end
116
119
  class InvalidBaseIRI < JsonLdError; @code = "invalid base IRI"; end
117
120
  class InvalidContainerMapping < JsonLdError; @code = "invalid container mapping"; end
118
- class InvalidContextMember < JsonLdError; @code = "invalid context member"; end
121
+ class InvalidContextEntry < JsonLdError; @code = "invalid context entry"; end
119
122
  class InvalidContextNullification < JsonLdError; @code = "invalid context nullification"; end
120
123
  class InvalidDefaultLanguage < JsonLdError; @code = "invalid default language"; end
121
124
  class InvalidIdValue < JsonLdError; @code = "invalid @id value"; end
@@ -142,6 +145,7 @@ module JSON
142
145
  class InvalidScopedContext < JsonLdError; @code = "invalid scoped context"; end
143
146
  class InvalidScriptElement < JsonLdError; @code = "invalid script element"; end
144
147
  class InvalidSetOrListObject < JsonLdError; @code = "invalid set or list object"; end
148
+ class InvalidStreamingKeyOrder < JsonLdError; @code = 'invalid streaming key order' end
145
149
  class InvalidTermDefinition < JsonLdError; @code = "invalid term definition"; end
146
150
  class InvalidBaseDirection < JsonLdError; @code = "invalid base direction"; end
147
151
  class InvalidTypedValue < JsonLdError; @code = "invalid typed value"; end
@@ -154,7 +158,7 @@ module JSON
154
158
  class KeywordRedefinition < JsonLdError; @code = "keyword redefinition"; end
155
159
  class LoadingDocumentFailed < JsonLdError; @code = "loading document failed"; end
156
160
  class LoadingRemoteContextFailed < JsonLdError; @code = "loading remote context failed"; end
157
- class ContextOverflow < JsonLdError; @code = "maximum number of @context URLs exceeded"; end
161
+ class ContextOverflow < JsonLdError; @code = "context overflow"; end
158
162
  class MissingIncludedReferent < JsonLdError; @code = "missing @included referent"; end
159
163
  class MultipleContextLinkHeaders < JsonLdError; @code = "multiple context link headers"; end
160
164
  class ProtectedTermRedefinition < JsonLdError; @code = "protected term redefinition"; end
@@ -35,7 +35,7 @@ module JSON::LD
35
35
 
36
36
  # Options used for open_file
37
37
  OPEN_OPTS = {
38
- headers: {"Accept" => "application/ld+json, text/html;q=0.8, application/json;q=0.5"}
38
+ headers: {"Accept" => "application/ld+json, text/html;q=0.8, application/xhtml+xml;q=0.8, application/json;q=0.5"}
39
39
  }
40
40
 
41
41
  # The following constants are used to reduce object allocations
@@ -66,7 +66,7 @@ module JSON::LD
66
66
  # @param [String, #read, Hash, Array, JSON::LD::Context] context
67
67
  # An external context to use additionally to the context embedded in input when expanding the input.
68
68
  # @param [Hash{Symbol => Object}] options
69
- # @option options [String, #to_s] :base
69
+ # @option options [RDF::URI, String, #to_s] :base
70
70
  # The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context. If not specified, and a base IRI is found from `input`, options[:base] will be modified with this value.
71
71
  # @option options [Boolean] :compactArrays (true)
72
72
  # 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.
@@ -108,6 +108,7 @@ module JSON::LD
108
108
  }.merge(options)
109
109
  @namer = unique_bnodes ? BlankNodeUniqer.new : (rename_bnodes ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
110
110
 
111
+ @options[:base] = RDF::URI(@options[:base]) if @options[:base] && !@options[:base].is_a?(RDF::URI)
111
112
  # For context via Link header
112
113
  _, context_ref = nil, nil
113
114
 
@@ -117,7 +118,7 @@ module JSON::LD
117
118
  remote_doc = self.class.loadRemoteDocument(input, **@options)
118
119
 
119
120
  context_ref = remote_doc.contextUrl
120
- @options[:base] = remote_doc.documentUrl if remote_doc.documentUrl && !@options[:no_default_base]
121
+ @options[:base] = RDF::URI(remote_doc.documentUrl) if remote_doc.documentUrl && !@options[:no_default_base]
121
122
 
122
123
  case remote_doc.document
123
124
  when String
@@ -130,7 +131,7 @@ module JSON::LD
130
131
 
131
132
  # If not provided, first use context from document, or from a Link header
132
133
  context ||= context_ref || {}
133
- @context = Context.parse(context || {}, **@options)
134
+ @context = Context.parse(context, **@options)
134
135
 
135
136
  if block_given?
136
137
  case block.arity
@@ -163,10 +164,9 @@ module JSON::LD
163
164
  # If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
164
165
  # @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
165
166
  def self.expand(input, framing: false, **options, &block)
166
- result, doc_base = nil
167
+ result = doc_base = nil
167
168
  API.new(input, options[:expandContext], **options) do
168
169
  result = self.expand(self.value, nil, self.context,
169
- ordered: @options[:ordered],
170
170
  framing: framing)
171
171
  doc_base = @options[:base]
172
172
  end
@@ -218,21 +218,21 @@ module JSON::LD
218
218
  # 1) Perform the Expansion Algorithm on the JSON-LD input.
219
219
  # This removes any existing context to allow the given context to be cleanly applied.
220
220
  expanded_input = expanded ? input : API.expand(input, ordered: false, **options) do |res, base_iri|
221
- options[:base] ||= base_iri if options[:compactToRelative]
221
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
222
222
  res
223
223
  end
224
224
 
225
225
  API.new(expanded_input, context, no_default_base: true, **options) do
226
226
  log_debug(".compact") {"expanded input: #{expanded_input.to_json(JSON_STATE) rescue 'malformed json'}"}
227
- result = compact(value, ordered: @options[:ordered])
227
+ result = compact(value)
228
228
 
229
229
  # xxx) Add the given context to the output
230
- ctx = self.context.serialize
230
+ ctx = self.context.serialize(provided_context: context)
231
231
  if result.is_a?(Array)
232
- kwgraph = self.context.compact_iri('@graph', vocab: true, quiet: true)
232
+ kwgraph = self.context.compact_iri('@graph', vocab: true)
233
233
  result = result.empty? ? {} : {kwgraph => result}
234
234
  end
235
- result = ctx.merge(result) unless ctx.empty?
235
+ result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
236
236
  end
237
237
  block_given? ? yield(result) : result
238
238
  end
@@ -265,7 +265,7 @@ module JSON::LD
265
265
 
266
266
  # Expand input to simplify processing
267
267
  expanded_input = expanded ? input : API.expand(input, **options) do |result, base_iri|
268
- options[:base] ||= base_iri if options[:compactToRelative]
268
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
269
269
  result
270
270
  end
271
271
 
@@ -294,9 +294,11 @@ module JSON::LD
294
294
 
295
295
  if context && !flattened.empty?
296
296
  # 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]))
298
- kwgraph = self.context.compact_iri('@graph', quiet: true)
299
- flattened = self.context.serialize.merge(kwgraph => compacted)
297
+ compacted = as_array(compact(flattened))
298
+ kwgraph = self.context.compact_iri('@graph')
299
+ flattened = self.context.
300
+ serialize(provided_context: context).
301
+ merge(kwgraph => compacted)
300
302
  end
301
303
  end
302
304
 
@@ -313,11 +315,11 @@ module JSON::LD
313
315
  # @param [String, #read, Hash, Array] frame
314
316
  # The frame to use when re-arranging the data.
315
317
  # @option options (see #initialize)
316
- # @option options ['@always', '@first', '@last', '@link', '@once', '@never'] :embed ('@last')
318
+ # @option options ['@always', '@link', '@once', '@never'] :embed ('@once')
317
319
  # a flag specifying that objects should be directly embedded in the output, instead of being referred to by their IRI.
318
320
  # @option options [Boolean] :explicit (false)
319
321
  # a flag specifying that for properties to be included in the output, they must be explicitly declared in the framing context.
320
- # @option options [Boolean] :requireAll (true)
322
+ # @option options [Boolean] :requireAll (false)
321
323
  # A flag specifying that all properties present in the input frame must either have a default value or be present in the JSON-LD input for the frame to match.
322
324
  # @option options [Boolean] :omitDefault (false)
323
325
  # a flag specifying that properties that are missing from the JSON-LD input should be omitted from the output.
@@ -335,7 +337,7 @@ module JSON::LD
335
337
  def self.frame(input, frame, expanded: false, **options)
336
338
  result = nil
337
339
  options = {
338
- base: (input if input.is_a?(String)),
340
+ base: (RDF::URI(input) if input.is_a?(String)),
339
341
  compactArrays: true,
340
342
  compactToRelative: true,
341
343
  embed: '@once',
@@ -369,7 +371,7 @@ module JSON::LD
369
371
 
370
372
  # Expand input to simplify processing
371
373
  expanded_input = expanded ? input : API.expand(input, ordered: false, **options) do |res, base_iri|
372
- options[:base] ||= base_iri if options[:compactToRelative]
374
+ options[:base] ||= RDF::URI(base_iri) if base_iri && options[:compactToRelative]
373
375
  res
374
376
  end
375
377
 
@@ -394,7 +396,7 @@ module JSON::LD
394
396
  # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
395
397
  create_node_map(value, framing_state[:graphMap], active_graph: '@default')
396
398
 
397
- frame_keys = frame.keys.map {|k| context.expand_iri(k, vocab: true, quiet: true)}
399
+ frame_keys = frame.keys.map {|k| context.expand_iri(k, vocab: true)}
398
400
  if frame_keys.include?('@graph')
399
401
  # If frame contains @graph, it matches the default graph.
400
402
  framing_state[:graph] = '@default'
@@ -426,7 +428,7 @@ module JSON::LD
426
428
  log_debug(".frame") {"expanded result: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
427
429
 
428
430
  # Compact result
429
- compacted = compact(result, ordered: @options[:ordered])
431
+ compacted = compact(result)
430
432
 
431
433
  # @replace `@null` with nil, compacting arrays
432
434
  compacted = cleanup_null(compacted)
@@ -434,11 +436,14 @@ module JSON::LD
434
436
 
435
437
  # Add the given context to the output
436
438
  result = if !compacted.is_a?(Array)
437
- context.serialize.merge(compacted)
439
+ compacted
438
440
  else
439
- kwgraph = context.compact_iri('@graph', quiet: true)
440
- context.serialize.merge({kwgraph => compacted})
441
+ kwgraph = context.compact_iri('@graph')
442
+ {kwgraph => compacted}
441
443
  end
444
+ # Only add context if one was provided
445
+ result = context.serialize(provided_context: frame).merge(result) if frame['@context']
446
+
442
447
  log_debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
443
448
  result
444
449
  end
@@ -521,8 +526,7 @@ module JSON::LD
521
526
  API.new(nil, nil, **options) do
522
527
  result = from_statements(input,
523
528
  useRdfType: useRdfType,
524
- useNativeTypes: useNativeTypes,
525
- ordered: @options[:ordered])
529
+ useNativeTypes: useNativeTypes)
526
530
  end
527
531
 
528
532
  block_given? ? yield(result) : result
@@ -532,16 +536,18 @@ module JSON::LD
532
536
  # Uses built-in or provided documentLoader to retrieve a parsed document.
533
537
  #
534
538
  # @param [RDF::URI, String] url
539
+ # @param [String, RDF::URI] base
540
+ # Location to use as documentUrl instead of `url`.
541
+ # @option options [Proc] :documentLoader
542
+ # The callback of the loader to be used to retrieve remote documents and contexts.
535
543
  # @param [Boolean] extractAllScripts
536
544
  # 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.
537
545
  # @param [String] profile
538
- # When the resulting `contentType` is `text/html`, this option determines the profile to use for selecting a JSON-LD script elements.
546
+ # 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.
539
547
  # @param [String] requestProfile
540
548
  # One or more IRIs to use in the request as a profile parameter.
541
- # @param [Boolean] validate
549
+ # @param [Boolean] validate (false)
542
550
  # Allow only appropriate content types
543
- # @param [String, RDF::URI] base
544
- # Location to use as documentUrl instead of `url`.
545
551
  # @param [Hash<Symbol => Object>] options
546
552
  # @yield remote_document
547
553
  # @yieldparam [RemoteDocumentRemoteDocument, RDF::Util::File::RemoteDocument] remote_document
@@ -550,13 +556,14 @@ module JSON::LD
550
556
  # If a block is given, the result of evaluating the block is returned, otherwise, the retrieved remote document and context information unless block given
551
557
  # @raise [JsonLdError]
552
558
  def self.loadRemoteDocument(url,
559
+ base: nil,
560
+ documentLoader: nil,
553
561
  extractAllScripts: false,
554
562
  profile: nil,
555
563
  requestProfile: nil,
556
564
  validate: false,
557
- base: nil,
558
565
  **options)
559
- documentLoader = options.fetch(:documentLoader, self.method(:documentLoader))
566
+ documentLoader ||= self.method(:documentLoader)
560
567
  options = OPEN_OPTS.merge(options)
561
568
  if requestProfile
562
569
  # Add any request profile
@@ -612,7 +619,7 @@ module JSON::LD
612
619
  # Parse any HTML
613
620
  if remote_doc.document.is_a?(String)
614
621
  remote_doc.document = case remote_doc.contentType
615
- when 'text/html'
622
+ when 'text/html', 'application/xhtml+xml'
616
623
  load_html(remote_doc.document,
617
624
  url: remote_doc.documentUrl,
618
625
  extractAllScripts: extractAllScripts,
@@ -628,7 +635,7 @@ module JSON::LD
628
635
 
629
636
  if remote_doc.contentType && validate
630
637
  raise IOError, "url: #{url}, contentType: #{remote_doc.contentType}" unless
631
- remote_doc.contentType.match?(/application\/(.+\+)?json|text\/html/)
638
+ remote_doc.contentType.match?(/application\/(.+\+)?json|text\/html|application\/xhtml\+xml/)
632
639
  end
633
640
  block_given? ? yield(remote_doc) : remote_doc
634
641
  end
@@ -642,7 +649,7 @@ module JSON::LD
642
649
  # @param [Boolean] extractAllScripts
643
650
  # 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.
644
651
  # @param [String] profile
645
- # When the resulting `contentType` is `text/html`, this option determines the profile to use for selecting a JSON-LD script elements.
652
+ # 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.
646
653
  # @param [String] requestProfile
647
654
  # One or more IRIs to use in the request as a profile parameter.
648
655
  # @param [Hash<Symbol => Object>] options
@@ -703,14 +710,14 @@ module JSON::LD
703
710
  require "json/ld/html/#{library}"
704
711
 
705
712
  # Parse HTML using the appropriate library
706
- @implementation = case library
713
+ implementation = case library
707
714
  when :nokogiri then Nokogiri
708
715
  when :rexml then REXML
709
716
  end
710
- self.extend(@implementation)
717
+ self.extend(implementation)
711
718
 
712
719
  input = begin
713
- initialize_html(input, **options)
720
+ self.send("initialize_html_#{library}".to_sym, input, **options)
714
721
  rescue
715
722
  raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Malformed HTML document: #{$!.message}"
716
723
  end
@@ -729,8 +736,8 @@ module JSON::LD
729
736
  id = CGI.unescape(url.fragment)
730
737
  # Find script with an ID based on that fragment.
731
738
  element = input.at_xpath("//script[@id='#{id}']")
732
- raise JSON::LD::JsonLdError::InvalidScriptElement, "No script tag found with id=#{id}" unless element
733
- raise JSON::LD::JsonLdError::InvalidScriptElement, "Script tag has type=#{element.attributes['type']}" unless element.attributes['type'].to_s.start_with?('application/ld+json')
739
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found with id=#{id}" unless element
740
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Script tag has type=#{element.attributes['type']}" unless element.attributes['type'].to_s.start_with?('application/ld+json')
734
741
  content = element.inner_html
735
742
  validate_input(content, url: url) if options[:validate]
736
743
  MultiJson.load(content, **options)
@@ -739,7 +746,7 @@ module JSON::LD
739
746
  elements = if profile
740
747
  es = input.xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]")
741
748
  # If no profile script, just take a single script without profile
742
- es = [input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")] if es.empty?
749
+ es = [input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")].compact if es.empty?
743
750
  es
744
751
  else
745
752
  input.xpath("//script[starts-with(@type, 'application/ld+json')]")
@@ -759,11 +766,12 @@ module JSON::LD
759
766
  # Find the first script with type application/ld+json.
760
767
  element = input.at_xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]") if profile
761
768
  element ||= input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")
762
- content = element ? element.inner_html : "[]"
769
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
770
+ content = element.inner_html
763
771
  validate_input(content, url: url) if options[:validate]
764
772
  MultiJson.load(content, **options)
765
773
  end
766
- rescue JSON::LD::JsonLdError::LoadingDocumentFailed, MultiJson::ParseError => e
774
+ rescue MultiJson::ParseError => e
767
775
  raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
768
776
  end
769
777
 
@@ -5,28 +5,23 @@ module JSON::LD
5
5
  include Utils
6
6
 
7
7
  # The following constant is used to reduce object allocations in #compact below
8
- CONTAINER_MAPPING_ID = %w(@id).freeze
9
- CONTAINER_MAPPING_INDEX = %w(@index).freeze
10
- CONTAINER_MAPPING_LANGUAGE = %w(@language).freeze
11
8
  CONTAINER_MAPPING_LANGUAGE_INDEX_ID_TYPE = Set.new(%w(@language @index @id @type)).freeze
12
- CONTAINER_MAPPING_LIST = %w(@list).freeze
13
- CONTAINER_MAPPING_TYPE = %w(@type).freeze
14
9
  EXPANDED_PROPERTY_DIRECTION_INDEX_LANGUAGE_VALUE = %w(@direction @index @language @value).freeze
15
10
 
16
11
  ##
17
12
  # 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.
18
13
  #
19
14
  # @param [Array, Hash] element
20
- # @param [String] property (nil)
21
- # @param [Boolean] ordered (true)
15
+ # @param [String, RDF::URI] base (nil)
22
16
  # Ensure output objects have keys ordered properly
17
+ # @param [String] property (nil)
18
+ # Extra validatation
23
19
  # @return [Array, Hash]
24
- def compact(element, property: nil, ordered: false)
25
- #if property.nil?
26
- # log_debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
27
- #else
28
- # log_debug("compact") {"property: #{property.inspect}"}
29
- #end
20
+ def compact(element,
21
+ base: nil,
22
+ property: nil,
23
+ log_depth: nil)
24
+ log_debug("compact", depth: log_depth.to_i) {"element: #{element.inspect}, ec: #{context.inspect}"}
30
25
 
31
26
  # If the term definition for active property itself contains a context, use that for compacting values.
32
27
  input_context = self.context
@@ -34,17 +29,19 @@ module JSON::LD
34
29
  case element
35
30
  when Array
36
31
  #log_debug("") {"Array #{element.inspect}"}
37
- result = element.map {|item| compact(item, property: property, ordered: ordered)}.compact
32
+ result = element.map do |item|
33
+ compact(item, base: base, property: property, log_depth: log_depth.to_i + 1)
34
+ end.compact
38
35
 
39
36
  # If element has a single member and the active property has no
40
37
  # @container mapping to @list or @set, the compacted value is that
41
38
  # member; otherwise the compacted value is element
42
39
  if result.length == 1 &&
43
40
  !context.as_array?(property) && @options[:compactArrays]
44
- #log_debug("=> extract single element: #{result.first.inspect}")
41
+ log_debug("=> extract single element", depth: log_depth.to_i) {result.first.inspect}
45
42
  result.first
46
43
  else
47
- #log_debug("=> array result: #{result.inspect}")
44
+ log_debug("=> array result", depth: log_depth.to_i) {result.inspect}
48
45
  result
49
46
  end
50
47
  when Hash
@@ -55,24 +52,31 @@ module JSON::LD
55
52
 
56
53
  # Revert any previously type-scoped (non-preserved) context
57
54
  if context.previous_context && !element.key?('@value') && element.keys != %w(@id)
55
+ log_debug("revert ec", depth: log_depth.to_i) {"previous context: #{context.previous_context.inspect}"}
58
56
  self.context = context.previous_context
59
57
  end
60
58
 
61
59
  # Look up term definintions from property using the original type-scoped context, if it exists, but apply them to the now current previous context
62
60
  td = input_context.term_definitions[property] if property
63
- self.context = context.parse(td.context, override_protected: true) if td && td.context
61
+ if td && !td.context.nil?
62
+ self.context = context.parse(td.context,
63
+ override_protected: true)
64
+ log_debug("prop-scoped", depth: log_depth.to_i) {"context: #{self.context.inspect}"}
65
+ end
64
66
 
65
67
  if element.key?('@id') || element.key?('@value')
66
- result = context.compact_value(property, element, log_depth: @options[:log_depth])
68
+ result = context.compact_value(property, element, base: @options[:base])
67
69
  if !result.is_a?(Hash) || context.coerce(property) == '@json'
68
- #log_debug("") {"=> scalar result: #{result.inspect}"}
70
+ log_debug("", depth: log_depth.to_i) {"=> scalar result: #{result.inspect}"}
69
71
  return result
70
72
  end
71
73
  end
72
74
 
73
75
  # If expanded property is @list and we're contained within a list container, recursively compact this item to an array
74
- if list?(element) && context.container(property) == CONTAINER_MAPPING_LIST
75
- return compact(element['@list'], property: property, ordered: ordered)
76
+ if list?(element) && context.container(property).include?('@list')
77
+ return compact(element['@list'], base: base,
78
+ property: property,
79
+ log_depth: log_depth.to_i + 1)
76
80
  end
77
81
 
78
82
  inside_reverse = property == '@reverse'
@@ -85,15 +89,18 @@ module JSON::LD
85
89
  sort.
86
90
  each do |term|
87
91
  term_context = input_context.term_definitions[term].context if input_context.term_definitions[term]
88
- self.context = context.parse(term_context, propagate: false) if term_context
92
+ self.context = context.parse(term_context, propagate: false) unless term_context.nil?
93
+ log_debug("type-scoped", depth: log_depth.to_i) {"context: #{self.context.inspect}"}
89
94
  end
90
95
 
91
- element.keys.opt_sort(ordered: ordered).each do |expanded_property|
96
+ element.keys.opt_sort(ordered: @options[:ordered]).each do |expanded_property|
92
97
  expanded_value = element[expanded_property]
93
- #log_debug("") {"#{expanded_property}: #{expanded_value.inspect}"}
98
+ log_debug("", depth: log_depth.to_i) {"#{expanded_property}: #{expanded_value.inspect}"}
94
99
 
95
100
  if expanded_property == '@id'
96
- compacted_value = Array(expanded_value).map {|expanded_id| context.compact_iri(expanded_id)}
101
+ compacted_value = Array(expanded_value).map do |expanded_id|
102
+ context.compact_iri(expanded_id, base: @options[:base])
103
+ end
97
104
 
98
105
  kw_alias = context.compact_iri('@id', vocab: true)
99
106
  as_array = compacted_value.length > 1
@@ -103,21 +110,24 @@ module JSON::LD
103
110
  end
104
111
 
105
112
  if expanded_property == '@type'
106
- compacted_value = Array(expanded_value).map {|expanded_type| input_context.compact_iri(expanded_type, vocab: true)}
113
+ compacted_value = Array(expanded_value).map do |expanded_type|
114
+ input_context.compact_iri(expanded_type, vocab: true)
115
+ end
107
116
 
108
117
  kw_alias = context.compact_iri('@type', vocab: true)
109
118
  as_array = compacted_value.length > 1 ||
110
119
  (context.as_array?(kw_alias) &&
111
120
  !value?(element) &&
112
121
  context.processingMode('json-ld-1.1'))
113
- compacted_value = compacted_value.first unless as_array
114
- result[kw_alias] = compacted_value
122
+ add_value(result, kw_alias, compacted_value, property_is_array: as_array)
115
123
  next
116
124
  end
117
125
 
118
126
  if expanded_property == '@reverse'
119
- compacted_value = compact(expanded_value, property: '@reverse', ordered: ordered)
120
- #log_debug("@reverse") {"compacted_value: #{compacted_value.inspect}"}
127
+ compacted_value = compact(expanded_value, base: base,
128
+ property: '@reverse',
129
+ log_depth: log_depth.to_i + 1)
130
+ log_debug("@reverse", depth: log_depth.to_i) {"compacted_value: #{compacted_value.inspect}"}
121
131
  # handle double-reversed properties
122
132
  compacted_value.each do |prop, value|
123
133
  if context.reverse?(prop)
@@ -128,8 +138,8 @@ module JSON::LD
128
138
  end
129
139
 
130
140
  unless compacted_value.empty?
131
- al = context.compact_iri('@reverse', quiet: true)
132
- #log_debug("") {"remainder: #{al} => #{compacted_value.inspect}"}
141
+ al = context.compact_iri('@reverse')
142
+ log_debug("", depth: log_depth.to_i) {"remainder: #{al} => #{compacted_value.inspect}"}
133
143
  result[al] = compacted_value
134
144
  end
135
145
  next
@@ -137,8 +147,10 @@ module JSON::LD
137
147
 
138
148
  if expanded_property == '@preserve'
139
149
  # Compact using `property`
140
- compacted_value = compact(expanded_value, property: property, ordered: ordered)
141
- #log_debug("@preserve") {"compacted_value: #{compacted_value.inspect}"}
150
+ compacted_value = compact(expanded_value, base: base,
151
+ property: property,
152
+ log_depth: log_depth.to_i + 1)
153
+ log_debug("@preserve", depth: log_depth.to_i) {"compacted_value: #{compacted_value.inspect}"}
142
154
 
143
155
  unless compacted_value.is_a?(Array) && compacted_value.empty?
144
156
  result['@preserve'] = compacted_value
@@ -146,15 +158,15 @@ module JSON::LD
146
158
  next
147
159
  end
148
160
 
149
- if expanded_property == '@index' && context.container(property) == CONTAINER_MAPPING_INDEX
150
- #log_debug("@index") {"drop @index"}
161
+ if expanded_property == '@index' && context.container(property).include?('@index')
162
+ log_debug("@index", depth: log_depth.to_i) {"drop @index"}
151
163
  next
152
164
  end
153
165
 
154
166
  # Otherwise, if expanded property is @direction, @index, @value, or @language:
155
167
  if EXPANDED_PROPERTY_DIRECTION_INDEX_LANGUAGE_VALUE.include?(expanded_property)
156
- al = context.compact_iri(expanded_property, vocab: true, quiet: true)
157
- #log_debug(expanded_property) {"#{al} => #{expanded_value.inspect}"}
168
+ al = context.compact_iri(expanded_property, vocab: true)
169
+ log_debug(expanded_property, depth: log_depth.to_i) {"#{al} => #{expanded_value.inspect}"}
158
170
  result[al] = expanded_value
159
171
  next
160
172
  end
@@ -164,8 +176,7 @@ module JSON::LD
164
176
  context.compact_iri(expanded_property,
165
177
  value: expanded_value,
166
178
  vocab: true,
167
- reverse: inside_reverse,
168
- log_depth: @options[:log_depth])
179
+ reverse: inside_reverse)
169
180
 
170
181
  if nest_prop = context.nest(item_active_property)
171
182
  result[nest_prop] ||= {}
@@ -183,8 +194,7 @@ module JSON::LD
183
194
  context.compact_iri(expanded_property,
184
195
  value: expanded_item,
185
196
  vocab: true,
186
- reverse: inside_reverse,
187
- log_depth: @options[:log_depth])
197
+ reverse: inside_reverse)
188
198
 
189
199
 
190
200
  nest_result = if nest_prop = context.nest(item_active_property)
@@ -203,17 +213,19 @@ module JSON::LD
203
213
  else expanded_item
204
214
  end
205
215
 
206
- compacted_item = compact(value, property: item_active_property, ordered: ordered)
207
- #log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
216
+ compacted_item = compact(value, base: base,
217
+ property: item_active_property,
218
+ log_depth: log_depth.to_i + 1)
219
+ log_debug("", depth: log_depth.to_i) {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
208
220
 
209
221
  # handle @list
210
222
  if list?(expanded_item)
211
223
  compacted_item = as_array(compacted_item)
212
- unless container == CONTAINER_MAPPING_LIST
213
- al = context.compact_iri('@list', vocab: true, quiet: true)
224
+ unless container.include?('@list')
225
+ al = context.compact_iri('@list', vocab: true)
214
226
  compacted_item = {al => compacted_item}
215
227
  if expanded_item.has_key?('@index')
216
- key = context.compact_iri('@index', vocab: true, quiet: true)
228
+ key = context.compact_iri('@index', vocab: true)
217
229
  compacted_item[key] = expanded_item['@index']
218
230
  end
219
231
  else
@@ -231,11 +243,11 @@ module JSON::LD
231
243
  map_object = nest_result[item_active_property] ||= {}
232
244
  # If there is no @id, create a blank node identifier to use as an index
233
245
  map_key = if container.include?('@id') && expanded_item['@id']
234
- context.compact_iri(expanded_item['@id'], quiet: true)
246
+ context.compact_iri(expanded_item['@id'], base: @options[:base])
235
247
  elsif container.include?('@index') && expanded_item['@index']
236
- context.compact_iri(expanded_item['@index'], quiet: true)
248
+ context.compact_iri(expanded_item['@index'], vocab: true)
237
249
  else
238
- context.compact_iri('@none', vocab: true, quiet: true)
250
+ context.compact_iri('@none', vocab: true)
239
251
  end
240
252
  add_value(map_object, map_key, compacted_item,
241
253
  property_is_array: as_array)
@@ -243,7 +255,7 @@ module JSON::LD
243
255
  # container includes @graph but not @id or @index and value is a simple graph object
244
256
  if compacted_item.is_a?(Array) && compacted_item.length > 1
245
257
  # Mutple objects in the same graph can't be represented directly, as they would be interpreted as two different graphs. Need to wrap in @included.
246
- included_key = context.compact_iri('@included', vocab: true).to_s
258
+ included_key = context.compact_iri('@included', vocab: true)
247
259
  compacted_item = {included_key => compacted_item}
248
260
  end
249
261
  # Drop through, where compacted_item will be added
@@ -251,35 +263,34 @@ module JSON::LD
251
263
  property_is_array: as_array)
252
264
  else
253
265
  # container does not include @graph or otherwise does not match one of the previous cases, redo compacted_item
254
- al = context.compact_iri('@graph', vocab: true, quiet: true)
266
+ al = context.compact_iri('@graph', vocab: true)
255
267
  compacted_item = {al => compacted_item}
256
268
  if expanded_item['@id']
257
- al = context.compact_iri('@id', vocab: true, quiet: true)
258
- compacted_item[al] = context.compact_iri(expanded_item['@id'], vocab: false, quiet: true).to_s
269
+ al = context.compact_iri('@id', vocab: true)
270
+ compacted_item[al] = context.compact_iri(expanded_item['@id'], vocab: false)
259
271
  end
260
272
  if expanded_item.has_key?('@index')
261
- key = context.compact_iri('@index', vocab: true, quiet: true)
273
+ key = context.compact_iri('@index', vocab: true)
262
274
  compacted_item[key] = expanded_item['@index']
263
275
  end
264
276
  add_value(nest_result, item_active_property, compacted_item,
265
277
  property_is_array: as_array)
266
278
  end
267
- elsif container.any? { |key| CONTAINER_MAPPING_LANGUAGE_INDEX_ID_TYPE.include?(key) } && !container.include?('@graph')
279
+ elsif container.intersect?(CONTAINER_MAPPING_LANGUAGE_INDEX_ID_TYPE) && !container.include?('@graph')
268
280
  map_object = nest_result[item_active_property] ||= {}
269
281
  c = container.first
270
- container_key = context.compact_iri(c, vocab: true, quiet: true)
271
- compacted_item = case container
272
- when CONTAINER_MAPPING_ID
282
+ container_key = context.compact_iri(c, vocab: true)
283
+ compacted_item = case
284
+ when container.include?('@id')
273
285
  map_key = compacted_item[container_key]
274
286
  compacted_item.delete(container_key)
275
287
  compacted_item
276
- when CONTAINER_MAPPING_INDEX
288
+ when container.include?('@index')
277
289
  index_key = context.term_definitions[item_active_property].index || '@index'
278
290
  if index_key == '@index'
279
291
  map_key = expanded_item['@index']
280
- compacted_item.delete(container_key) if compacted_item.is_a?(Hash)
281
292
  else
282
- container_key = context.compact_iri(index_key, vocab: true, quiet: true)
293
+ container_key = context.compact_iri(index_key, vocab: true)
283
294
  map_key, *others = Array(compacted_item[container_key])
284
295
  if map_key.is_a?(String)
285
296
  case others.length
@@ -288,15 +299,15 @@ module JSON::LD
288
299
  else compacted_item[container_key] = others
289
300
  end
290
301
  else
291
- map_key = context.compact_iri('@none', vocab: true, quiet: true)
302
+ map_key = context.compact_iri('@none', vocab: true)
292
303
  end
293
304
  end
294
305
  # Note, if compacted_item is a node reference and key is @id-valued, then this could be compacted further.
295
306
  compacted_item
296
- when CONTAINER_MAPPING_LANGUAGE
307
+ when container.include?('@language')
297
308
  map_key = expanded_item['@language']
298
309
  value?(expanded_item) ? expanded_item['@value'] : compacted_item
299
- when CONTAINER_MAPPING_TYPE
310
+ when container.include?('@type')
300
311
  map_key, *types = Array(compacted_item[container_key])
301
312
  case types.length
302
313
  when 0 then compacted_item.delete(container_key)
@@ -306,11 +317,14 @@ module JSON::LD
306
317
 
307
318
  # if compacted_item contains a single entry who's key maps to @id, then recompact the item without @type
308
319
  if compacted_item.keys.length == 1 && expanded_item.keys.include?('@id')
309
- compacted_item = compact({'@id' => expanded_item['@id']}, property: item_active_property)
320
+ compacted_item = compact({'@id' => expanded_item['@id']},
321
+ base: base,
322
+ property: item_active_property,
323
+ log_depth: log_depth.to_i + 1)
310
324
  end
311
325
  compacted_item
312
326
  end
313
- map_key ||= context.compact_iri('@none', vocab: true, quiet: true)
327
+ map_key ||= context.compact_iri('@none', vocab: true)
314
328
  add_value(map_object, map_key, compacted_item,
315
329
  property_is_array: as_array)
316
330
  else
@@ -323,7 +337,7 @@ module JSON::LD
323
337
  result
324
338
  else
325
339
  # For other types, the compacted value is the element value
326
- #log_debug("compact") {element.class.to_s}
340
+ log_debug("compact", depth: log_depth.to_i) {element.class.to_s}
327
341
  element
328
342
  end
329
343