Class: JSON::LD::Context

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/json/ld/context.rb

Defined Under Namespace

Classes: TermDefinition

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#as_resource, #blank_node?, #index?, #list?, #node?, #node_reference?, #value?

Constructor Details

#initialize(options = {}) {|ec| ... } ⇒ Context

Create new evaluation context

Yields:

  • (ec)

Yield Parameters:



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/json/ld/context.rb', line 132

def initialize(options = {})
  if options[:base]
    @base = @doc_base = RDF::URI(options[:base])
    @doc_base.canonicalize!
    @doc_base.fragment = nil
    @doc_base.query = nil
  end
  @term_definitions = {}
  @iri_to_term = {
    RDF.to_uri.to_s => "rdf",
    RDF::XSD.to_uri.to_s => "xsd"
  }
  @remote_contexts = []
  @namer = BlankNodeMapper.new("t")

  @options = options

  # Load any defined prefixes
  (options[:prefixes] || {}).each_pair do |k, v|
    @iri_to_term[v.to_s] = k unless k.nil?
  end

  debug("init") {"iri_to_term: #{iri_to_term.inspect}"}
  
  yield(self) if block_given?
end

Instance Attribute Details

#baseRDF::URI

The base.

Returns:

  • (RDF::URI)

    Current base IRI, used for expanding relative IRIs.



80
81
82
# File 'lib/json/ld/context.rb', line 80

def base
  @base
end

#context_baseRDF::URI

Returns base IRI of the context, if loaded remotely. XXX.

Returns:

  • (RDF::URI)

    base IRI of the context, if loaded remotely. XXX



88
89
90
# File 'lib/json/ld/context.rb', line 88

def context_base
  @context_base
end

#default_languageString

Default language

This adds a language to plain strings that aren’t otherwise coerced

Returns:

  • (String)


104
105
106
# File 'lib/json/ld/context.rb', line 104

def default_language
  @default_language
end

#doc_baseRDF::URI (readonly)

The base.

Returns:

  • (RDF::URI)

    Document base IRI, to initialize ‘base`.



85
86
87
# File 'lib/json/ld/context.rb', line 85

def doc_base
  @doc_base
end

#iri_to_termHash{RDF::URI => String}

Returns Reverse mappings from IRI to term only for terms, not CURIEs XXX.

Returns:

  • (Hash{RDF::URI => String})

    Reverse mappings from IRI to term only for terms, not CURIEs XXX



96
97
98
# File 'lib/json/ld/context.rb', line 96

def iri_to_term
  @iri_to_term
end

#namerBlankNodeNamer

Returns:



125
126
127
# File 'lib/json/ld/context.rb', line 125

def namer
  @namer
end

#optionsHash{Symbol => Object}

Returns Global options used in generating IRIs.

Returns:

  • (Hash{Symbol => Object})

    Global options used in generating IRIs



114
115
116
# File 'lib/json/ld/context.rb', line 114

def options
  @options
end

#provided_contextContext

Returns A context provided to us that we can use without re-serializing XXX.

Returns:

  • (Context)

    A context provided to us that we can use without re-serializing XXX



117
118
119
# File 'lib/json/ld/context.rb', line 117

def provided_context
  @provided_context
end

#remote_contextsArray<String>

Returns The list of remote contexts already processed.

Returns:

  • (Array<String>)

    The list of remote contexts already processed



121
122
123
# File 'lib/json/ld/context.rb', line 121

def remote_contexts
  @remote_contexts
end

#term_definitionsHash{String => TermDefinition} (readonly)

Term definitions

Returns:



93
94
95
# File 'lib/json/ld/context.rb', line 93

def term_definitions
  @term_definitions
end

#vocabString

Default vocabulary

Sets the default vocabulary used for expanding terms which aren’t otherwise absolute IRIs

Returns:

  • (String)


111
112
113
# File 'lib/json/ld/context.rb', line 111

def vocab
  @vocab
end

Instance Method Details

#alias(value) ⇒ String

Deprecated.

FIXME: this should go away Reverse term mapping, typically used for finding aliases for keys.

Returns either the original value, or a mapping for this value.

Examples:

{"@context": {"id": "@id"}, "@id": "foo"} => {"id": "foo"}

Parameters:

  • value (RDF::URI, String)

Returns:

  • (String)


533
534
535
# File 'lib/json/ld/context.rb', line 533

def alias(value)
  iri_to_term.fetch(value, value)
end

#compact_iri(iri, options = {}) ⇒ String

Compacts an absolute IRI to the shortest matching term or compact IRI

Parameters:

  • iri (RDF::URI)
  • options (Hash{Symbol => Object}) (defaults to: {})

    ({})

Options Hash (options):

  • :value (Object)

    Value, used to select among various maps for the same IRI

  • :vocab (Boolean)

    specifies whether the passed iri should be compacted using the active context’s vocabulary mapping

  • :reverse (Boolean)

    specifies whether a reverse property is being compacted

Returns:

  • (String)

    compacted form of IRI

See Also:



694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
# File 'lib/json/ld/context.rb', line 694

def compact_iri(iri, options = {})
  return if iri.nil?
  iri = iri.to_s
  debug("compact_iri(#{iri.inspect}", options) {options.inspect} unless options[:quiet]
  depth(options) do

    value = options.fetch(:value, nil)

    if options[:vocab] && inverse_context.has_key?(iri)
      debug("") {"vocab and key in inverse context"} unless options[:quiet]
      default_language = self.default_language || @none
      containers = []
      tl, tl_value = "@language", "@null"
      containers << '@index' if index?(value)
      if options[:reverse]
        tl, tl_value = "@type", "@reverse"
        containers << '@set'
      elsif list?(value)
        debug("") {"list(#{value.inspect})"} unless options[:quiet]
        # if value is a list object, then set type/language and type/language value to the most specific values that work for all items in the list as follows:
        containers << "@list" unless index?(value)
        list = value['@list']
        common_type = nil
        common_language = default_language if list.empty?
        list.each do |item|
          item_language, item_type = "@none", "@none"
          if value?(item)
            if item.has_key?('@language')
              item_language = item['@language']
            elsif item.has_key?('@type')
              item_type = item['@type']
            else
              item_language = "@null"
            end
          else
            item_type = '@id'
          end
          common_language ||= item_language
          if item_language != common_language && value?(item)
            debug("") {"-- #{item_language} conflicts with #{common_language}, use @none"} unless options[:quiet]
            common_language = '@none'
          end
          common_type ||= item_type
          if item_type != common_type
            common_type = '@none'
            debug("") {"#{item_type} conflicts with #{common_type}, use @none"} unless options[:quiet]
          end
        end

        common_language ||= '@none'
        common_type ||= '@none'
        debug("") {"common type: #{common_type}, common language: #{common_language}"} unless options[:quiet]
        if common_type != '@none'
          tl, tl_value = '@type', common_type
        else
          tl_value = common_language
        end
        debug("") {"list: containers: #{containers.inspect}, type/language: #{tl.inspect}, type/language value: #{tl_value.inspect}"} unless options[:quiet]
      else
        if value?(value)
          if value.has_key?('@language') && !index?(value)
            tl_value = value['@language']
            containers << '@language'
          elsif value.has_key?('@type')
            tl_value = value['@type']
            tl = '@type'
          end
        else
          tl, tl_value = '@type', '@id'
        end
        containers << '@set'
        debug("") {"value: containers: #{containers.inspect}, type/language: #{tl.inspect}, type/language value: #{tl_value.inspect}"} unless options[:quiet]
      end

      containers << '@none'
      tl_value ||= '@null'
      preferred_values = []
      preferred_values << '@reverse' if tl_value == '@reverse'
      if %w(@id @reverse).include?(tl_value) && value.is_a?(Hash) && value.has_key?('@id')
        t_iri = compact_iri(value['@id'], :vocab => true, :document_relative => true)
        if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
          preferred_values.concat(%w(@vocab @id @none))
        else
          preferred_values.concat(%w(@id @vocab @none))
        end
      else
        preferred_values.concat([tl_value, '@none'])
      end
      debug("") {"preferred_values: #{preferred_values.inspect}"} unless options[:quiet]
      if p_term = select_term(iri, containers, tl, preferred_values)
        debug("") {"=> term: #{p_term.inspect}"} unless options[:quiet]
        return p_term
      end
    end

    # At this point, there is no simple term that iri can be compacted to. If vocab is true and active context has a vocabulary mapping:
    if options[:vocab] && vocab && iri.start_with?(vocab) && iri.length > vocab.length
      suffix = iri[vocab.length..-1]
      debug("") {"=> vocab suffix: #{suffix.inspect}"} unless options[:quiet]
      return suffix unless term_definitions.has_key?(suffix)
    end

    # The iri could not be compacted using the active context's vocabulary mapping. Try to create a compact IRI, starting by initializing compact IRI to null. This variable will be used to tore the created compact IRI, if any.
    candidates = []

    term_definitions.each do |term, td|
      next if term.include?(":")
      next if td.nil? || td.id.nil? || td.id == iri || !iri.start_with?(td.id)
      suffix = iri[td.id.length..-1]
      ciri = "#{term}:#{suffix}"
      candidates << ciri unless value && term_definitions.has_key?(ciri)
    end

    if !candidates.empty?
      debug("") {"=> compact iri: #{candidates.term_sort.first.inspect}"} unless options[:quiet]
      return candidates.term_sort.first
    end

    # If we still don't have any terms and we're using standard_prefixes,
    # try those, and add to mapping
    if @options[:standard_prefixes]
      candidates = RDF::Vocabulary.
        select {|v| iri.start_with?(v.to_uri.to_s)}.
        map do |v|
          prefix = v.__name__.to_s.split('::').last.downcase
          set_mapping(prefix, v.to_uri.to_s)
          iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
        end

      if !candidates.empty?
        debug("") {"=> standard prefies: #{candidates.term_sort.first.inspect}"} unless options[:quiet]
        return candidates.term_sort.first
      end
    end

    if !options[:vocab]
      # transform iri to a relative IRI using the document's base IRI
      iri = remove_base(iri)
      debug("") {"=> relative iri: #{iri.inspect}"} unless options[:quiet]
      return iri
    else
      debug("") {"=> absolute iri: #{iri.inspect}"} unless options[:quiet]
      return iri
    end
  end
end

#compact_value(property, value, options = {}) ⇒ Hash

Compact a value

FIXME: revisit the specification version of this.

Parameters:

  • property (String)

    Associated property used to find coercion rules

  • value (Hash)

    Value (literal or IRI), in full object representation, to be compacted

  • options (Hash{Symbol => Object}) (defaults to: {})

Returns:

  • (Hash)

    Object representation of value

Raises:

See Also:



934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
# File 'lib/json/ld/context.rb', line 934

def compact_value(property, value, options = {})

  depth(options) do
    debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}

    num_members = value.keys.length

    num_members -= 1 if index?(value) && container(property) == '@index'
    if num_members > 2
      debug("") {"can't compact value with # members > 2"}
      return value
    end

    result = case
    when coerce(property) == '@id' && value.has_key?('@id') && num_members == 1
      # Compact an @id coercion
      debug("") {" (@id & coerce)"}
      compact_iri(value['@id'])
    when coerce(property) == '@vocab' && value.has_key?('@id') && num_members == 1
      # Compact an @id coercion
      debug("") {" (@id & coerce & vocab)"}
      compact_iri(value['@id'], :vocab => true)
    when value.has_key?('@id')
      debug("") {" (@id)"}
      # return value as is
      value
    when value['@type'] && expand_iri(value['@type'], :vocab => true) == coerce(property)
      # Compact common datatype
      debug("") {" (@type & coerce) == #{coerce(property)}"}
      value['@value']
    when value['@language'] && (value['@language'] == language(property))
      # Compact language
      debug("") {" (@language) == #{language(property).inspect}"}
      value['@value']
    when num_members == 1 && !value['@value'].is_a?(String)
      debug("") {" (native)"}
      value['@value']
    when num_members == 1 && default_language.nil? || language(property) == false
      debug("") {" (!@language)"}
      value['@value']
    else
      # Otherwise, use original value
      debug("") {" (no change)"}
      value
    end
    
    # If the result is an object, tranform keys using any term keyword aliases
    if result.is_a?(Hash) && result.keys.any? {|k| self.alias(k) != k}
      debug("") {" (map to key aliases)"}
      new_element = {}
      result.each do |k, v|
        new_element[self.alias(k)] = v
      end
      result = new_element
    end

    debug("") {"=> #{result.inspect}"}
    result
  end
end

#container(property) ⇒ String

Retrieve container mapping, add it if ‘value` is provided

Parameters:

  • property (String)

    in unexpanded form

Returns:

  • (String)


556
557
558
559
560
# File 'lib/json/ld/context.rb', line 556

def container(property)
  return '@set' if property == '@graph'
  return property if KEYWORDS.include?(property)
  term_definitions[property] && term_definitions[property].container_mapping
end

#create_term_definition(local_context, term, defined) ⇒ Object

Create Term Definition

Term definitions are created by parsing the information in the given local context for the given term. If the given term is a compact IRI, it may omit an IRI mapping by depending on its prefix having its own term definition. If the prefix is a key in the local context, then its term definition must first be created, through recursion, before continuing. Because a term definition can depend on other term definitions, a mechanism must be used to detect cyclical dependencies. The solution employed here uses a map, defined, that keeps track of whether or not a term has been defined or is currently in the process of being defined. This map is checked before any recursion is attempted.

After all dependencies for a term have been defined, the rest of the information in the local context for the given term is taken into account, creating the appropriate IRI mapping, container mapping, and type mapping or language mapping for the term.

Parameters:

  • local_context (Hash)
  • term (String)
  • defined (Hash)

Raises:

See Also:



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/json/ld/context.rb', line 317

def create_term_definition(local_context, term, defined)
  # Expand a string value, unless it matches a keyword
  debug("create_term_definition") {"term = #{term.inspect}"}

  # If defined contains the key term, then the associated value must be true, indicating that the term definition has already been created, so return. Otherwise, a cyclical term definition has been detected, which is an error.
  case defined[term]
  when TrueClass then return
  when nil
    defined[term] = false
  else
    raise InvalidContext::CyclicIRIMapping, "Cyclical term dependency found for #{term.inspect}"
  end

  # Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
  if KEYWORDS.include?(term) && !%w(@vocab @language).include?(term)
    raise InvalidContext::KeywordRedefinition, "term #{term.inspect} must not be a keyword" if
      @options[:validate]
  elsif !term_valid?(term) && @options[:validate]
    raise InvalidContext::InvalidTermDefinition, "term #{term.inspect} is invalid"
  end

  # Remove any existing term definition for term in active context.
  term_definitions.delete(term)

  # Initialize value to a the value associated with the key term in local context.
  value = local_context.fetch(term, false)
  value = {'@id' => value} if value.is_a?(String)

  case value
  when nil, {'@id' => nil}
    # If value equals null or value is a JSON object containing the key-value pair (@id-null), then set the term definition in active context to null, set the value associated with defined's key term to true, and return.
    debug("") {"=> nil"}
    term_definitions[term] = TermDefinition.new(term)
    defined[term] = true
    return
  when Hash
    debug("") {"Hash[#{term.inspect}] = #{value.inspect}"}
    definition = TermDefinition.new(term)

    if value.has_key?('@type')
      type = value['@type']
      # SPEC FIXME: @type may be nil
      raise InvalidContext::InvalidTypeMapping, "unknown mapping for '@type' to #{type.inspect}" unless type.is_a?(String) || type.nil?
      type = expand_iri(type, :vocab => true, :documentRelative => true, :local_context => local_context, :defined => defined) if type.is_a?(String)
      debug("") {"type_mapping: #{type.inspect}"}
      definition.type_mapping = type
    end

    if value.has_key?('@reverse')
      raise InvalidContext::InvalidReverseProperty, "unexpected key in #{value.inspect}" if
        value.keys.any? {|k| %w(@id).include?(k)}
      raise InvalidContext::InvalidIRIMapping, "expected value of @reverse to be a string" unless
        value['@reverse'].is_a?(String)

      # Otherwise, set the IRI mapping of definition to the result of using the IRI Expansion algorithm, passing active context, the value associated with the @reverse key for value, true for vocab, true for document relative, local context, and defined. If the result is not an absolute IRI, i.e., it contains no colon (:), an invalid IRI mapping error has been detected and processing is aborted.
      definition.id =  expand_iri(value['@reverse'],
                                  :vocab => true,
                                  :documentRelative => true,
                                  :local_context => local_context,
                                  :defined => defined)
      raise InvalidContext::InvalidIRImapping, "non-absolute @reverse IRI: #{definition.id}" unless
        definition.id.absolute?

      # If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, nor @index, nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
      if (container = value['@container'])
        raise InvalidContext::InvalidReverseProperty,
              "unknown mapping for '@container' to #{container.inspect}" unless
               ['@set', '@index', nil].include?(container)
        definition.container_mapping = container
      end
      definition.reverse_property = true
    elsif value.has_key?('@id') && value['@id'] != term
      raise InvalidContext::InvalidIRIMapping, "expected value of @reverse to be a string" unless
        value['@id'].is_a?(String)
      definition.id = expand_iri(value['@id'],
        :vocab => true,
        :documentRelative => true,
        :local_context => local_context,
        :defined => defined)
    elsif term.include?(':')
      # If term is a compact IRI with a prefix that is a key in local context then a dependency has been found. Use this algorithm recursively passing active context, local context, the prefix as term, and defined.
      prefix, suffix = term.split(':')
      depth {create_term_definition(local_context, prefix, defined)} if local_context.has_key?(prefix)

      definition.id = if td = term_definitions[prefix]
        # If term's prefix has a term definition in active context, set the IRI mapping for definition to the result of concatenating the value associated with the prefix's IRI mapping and the term's suffix.
        td.id + suffix
      else
        # Otherwise, term is an absolute IRI. Set the IRI mapping for definition to term
        term
      end
      debug("") {"=> #{definition.id}"}
    else
      # Otherwise, active context must have a vocabulary mapping, otherwise an invalid value has been detected, which is an error. Set the IRI mapping for definition to the result of concatenating the value associated with the vocabulary mapping and term.
      raise InvalidContext::InvalidIRIMapping, "relative term definition without vocab" unless vocab
      definition.id = vocab + term
      debug("") {"=> #{definition.id}"}
    end

    if value.has_key?('@container')
      container = value['@container']
      raise InvalidContext::InvalidContainerMapping, "unknown mapping for '@container' to #{container.inspect}" unless %w(@list @set @language @index).include?(container)
      debug("") {"container_mapping: #{container.inspect}"}
      definition.container_mapping = container
    end

    if value.has_key?('@language')
      language = value['@language']
      raise InvalidContext::InvalidLanguageMapping, "language must be null or a string, was #{language.inspect}}" unless language.nil? || (language || "").is_a?(String)
      language = language.downcase if language.is_a?(String)
      debug("") {"language_mapping: #{language.inspect}"}
      definition.language_mapping = language || false
    end

    term_definitions[term] = definition
    defined[term] = true
  else
    raise InvalidContext::InvalidTermDefinition, "Term definition for #{term.inspect} is an #{value.class}"
  end
end

#dupObject



1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
# File 'lib/json/ld/context.rb', line 1003

def dup
  # Also duplicate mappings, coerce and list
  that = self
  ec = super
  ec.instance_eval do
    @term_definitions = that.term_definitions.dup
    @iri_to_term = that.iri_to_term.dup
  end
  ec
end

#expand_iri(value, options = {}) ⇒ RDF::URI, String

Expand an IRI. Relative IRIs are expanded against any document base.

Parameters:

  • value (String)

    A keyword, term, prefix:suffix or possibly relative IRI

  • options (Hash{Symbol => Object}) (defaults to: {})

Options Hash (options):

  • documentRelative (Boolean) — default: false
  • vocab (Boolean) — default: false
  • local_context (Hash)

    Used during Context Processing.

  • defined (Hash)

    Used during Context Processing.

Returns:

  • (RDF::URI, String)

    IRI or String, if it’s a keyword

Raises:

See Also:



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/json/ld/context.rb', line 616

def expand_iri(value, options = {})
  return value unless value.is_a?(String)

  return value if KEYWORDS.include?(value)
  depth(options) do
    debug("expand_iri") {"value: #{value.inspect}"} unless options[:quiet]
    local_context = options[:local_context]
    defined = options.fetch(:defined, {})

    # If local context is not null, it contains a key that equals value, and the value associated with the key that equals value in defined is not true, then invoke the Create Term Definition subalgorithm, passing active context, local context, value as term, and defined. This will ensure that a term definition is created for value in active context during Context Processing.
    if local_context && local_context.has_key?(value) && !defined[value]
      depth {create_term_definition(local_context, value, defined)}
    end

    # If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
    if options[:vocab] && (v_td = term_definitions[value])
      debug("") {"match with #{v_td.id}"} unless options[:quiet]
      return v_td.id
    end

    # If value contains a colon (:), it is either an absolute IRI or a compact IRI:
    if value.include?(':')
      prefix, suffix = value.split(':', 2)
      debug("") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]

      # If prefix is underscore (_) or suffix begins with double-forward-slash (//), return value as it is already an absolute IRI or a blank node identifier.
      return RDF::Node.new(namer.get_sym(suffix)) if prefix == '_'
      return RDF::URI(value) if suffix[0,2] == '//'

      # If local context is not null, it contains a key that equals prefix, and the value associated with the key that equals prefix in defined is not true, invoke the Create Term Definition algorithm, passing active context, local context, prefix as term, and defined. This will ensure that a term definition is created for prefix in active context during Context Processing.
      if local_context && local_context.has_key?(prefix) && !defined[prefix]
        create_term_definition(local_context, prefix, defined)
      end

      # If active context contains a term definition for prefix, return the result of concatenating the IRI mapping associated with prefix and suffix.
      result = if (td = term_definitions[prefix])
        result = td.id + suffix
      else
        # (Otherwise) Return value as it is already an absolute IRI.
        RDF::URI(value)
      end

      debug("") {"=> #{result.inspect}"} unless options[:quiet]
      return result
    end
    debug("") {"=> #{result.inspect}"} unless options[:quiet]

    result = if options[:vocab] && vocab
      # If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
      vocab + value
    elsif options[:documentRelative] && base = options.fetch(:base, self.base)
      # Otherwise, if document relative is true, set value to the result of resolving value against the base IRI. Only the basic algorithm in section 5.2 of [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization are performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [RFC3987].
      RDF::URI(base).join(value)
    elsif local_context && RDF::URI(value).relative?
      # If local context is not null and value is not an absolute IRI, an invalid IRI mapping error has been detected and processing is aborted.
      raise JSON::LD::InvalidContext::InvalidIRIMapping, "not an absolute IRI: #{value}"
    else
      RDF::URI(value)
    end
    debug("") {"=> #{result}"} unless options[:quiet]
    result
  end
end

#expand_value(property, value, options = {}) ⇒ Hash

If active property has a type mapping in the active context set to @id or @vocab, a JSON object with a single member @id whose value is the result of using the IRI Expansion algorithm on value is returned.

Otherwise, the result will be a JSON object containing an @value member whose value is the passed value. Additionally, an @type member will be included if there is a type mapping associated with the active property or an @language member if value is a string and there is language mapping associated with the active property.

Parameters:

  • property (String)

    Associated property used to find coercion rules

  • value (Hash, String)

    Value (literal or IRI) to be expanded

  • options (Hash{Symbol => Object}) (defaults to: {})

Options Hash (options):

  • :useNativeTypes (Boolean) — default: true

    use native representations

Returns:

  • (Hash)

    Object representation of value

Raises:

  • (RDF::ReaderError)

    if the iri cannot be expanded

See Also:



856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
# File 'lib/json/ld/context.rb', line 856

def expand_value(property, value, options = {})
  options = {:useNativeTypes => true}.merge(options)
  depth(options) do
    debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}"}

    # If the active property has a type mapping in active context that is @id, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
    if (td = term_definitions.fetch(property, TermDefinition.new(property))) && td.type_mapping == '@id'
      debug("") {"as relative IRI: #{value.inspect}"}
      return {'@id' => expand_iri(value, :documentRelative => true).to_s}
    end

    # If active property has a type mapping in active context that is @vocab, return a new JSON object containing a single key-value pair where the key is @id and the value is the result of using the IRI Expansion algorithm, passing active context, value, true for vocab, and true for document relative.
    if td.type_mapping == '@vocab'
      debug("") {"as vocab IRI: #{value.inspect}"}
      return {'@id' => expand_iri(value, :vocab => true, :documentRelative => true).to_s}
    end

    value = RDF::Literal(value) if
      value.is_a?(Date) ||
      value.is_a?(DateTime) ||
      value.is_a?(Time)

    result = case value
    when RDF::URI, RDF::Node
      debug("URI | BNode") { value.to_s }
      {'@id' => value.to_s}
    when RDF::Literal
      debug("Literal") {"datatype: #{value.datatype.inspect}"}
      res = {}
      if options[:useNativeTypes] && [RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double].include?(value.datatype)
        res['@value'] = value.object
        res['@type'] = uri(coerce(property)) if coerce(property)
      else
        value.canonicalize! if value.datatype == RDF::XSD.double
        res['@value'] = value.to_s
        if coerce(property)
          res['@type'] = uri(coerce(property)).to_s
        elsif value.has_datatype?
          res['@type'] = uri(value.datatype).to_s
        elsif value.has_language? || language(property)
          res['@language'] = (value.language || language(property)).to_s
        end
      end
      res
    else
      # Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
      res = {'@value' => value}

      if td.type_mapping
        res['@type'] = td.type_mapping.to_s
      elsif value.is_a?(String)
        if td.language_mapping
          res['@language'] = td.language_mapping
        elsif default_language && td.language_mapping.nil?
          res['@language'] = default_language
        end
      end
      res
    end

    debug("") {"=> #{result.inspect}"}
    result
  end
end

#inspectObject



995
996
997
998
999
1000
1001
# File 'lib/json/ld/context.rb', line 995

def inspect
  v = %w([Context)
  v << "vocab=#{vocab}" if vocab
  v << "def_language=#{default_language}" if default_language
  v << "term_definitions[#{term_definitions.length}]=#{term_definitions}"
  v.join(" ") + "]"
end

#language(property) ⇒ String

Retrieve the language associated with a property, or the default language otherwise

Returns:

  • (String)


577
578
579
580
# File 'lib/json/ld/context.rb', line 577

def language(property)
  lang = term_definitions[property] && term_definitions[property].language_mapping
  lang.nil? ? @default_language : lang
end

#languagesArray<String>

Deprecated.

FIXME: this should go away Retrieve language mappings

Returns:



567
568
569
570
571
572
# File 'lib/json/ld/context.rb', line 567

def languages
  term_definitions.inject({}) do |memo, (t,td)|
    memo[t] = td.language_mapping
    memo
  end
end

#mapping(term) ⇒ RDF::URI, String

Deprecated.

FIXME: this should go away Retrieve term mapping

Parameters:

  • term (String, #to_s)

Returns:

  • (RDF::URI, String)


499
500
501
# File 'lib/json/ld/context.rb', line 499

def mapping(term)
  term_definitions[term] ? term_definitions[term].id : nil
end

#mappingsArray<String>

Deprecated.

FIXME: this should go away Retrieve term mappings

Returns:



485
486
487
488
489
490
# File 'lib/json/ld/context.rb', line 485

def mappings
  term_definitions.inject({}) do |memo, (t,td)|
    memo[t] = td ? td.id : nil
    memo
  end
end

#parse(local_context, remote_contexts = []) ⇒ Object

Create an Evaluation Context

When processing a JSON-LD data structure, each processing rule is applied using information provided by the active context. This section describes how to produce an active context.

The active context contains the active term definitions which specify how properties and values have to be interpreted as well as the current base IRI, the vocabulary mapping and the default language. Each term definition consists of an IRI mapping, a boolean flag reverse property, an optional type mapping or language mapping, and an optional container mapping. A term definition can not only be used to map a term to an IRI, but also to map a term to a keyword, in which case it is referred to as a keyword alias.

When processing, the active context is initialized without any term definitions, vocabulary mapping, or default language. If a local context is encountered during processing, a new active context is created by cloning the existing active context. Then the information from the local context is merged into the new active context. Given that local contexts may contain references to remote contexts, this includes their retrieval.

Parameters:

Raises:

  • (InvalidContext)

    on a remote context load error, syntax error, or a reference to a term which is not defined.

See Also:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/json/ld/context.rb', line 214

def parse(local_context, remote_contexts = [])
  result = self.dup
  local_context = [local_context] unless local_context.is_a?(Array)

  local_context.each do |context|
    depth do
      case context
      when nil
        # 3.1 If niil, set to a new empty context
        result = Context.new(options)
      when Context
         debug("parse") {"context: #{context.inspect}"}
         result = context.dup
      when IO, StringIO
        debug("parse") {"io: #{context}"}
        # Load context document, if it is a string
        begin
          ctx = JSON.load(context)
          raise JSON::LD::InvalidContext::InvalidRemoteContext, "Context missing @context key" if @options[:validate] && ctx['@context'].nil?
          result = parse(ctx["@context"] ? ctx["@context"].dup : {})
          result.provided_context = ctx["@context"]
          result
        rescue JSON::ParserError => e
          debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
          raise JSON::LD::InvalidContext::InvalidRemoteContext, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
          self.dup
        end
      when String
        debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}
        # Load context document, if it is a string
        begin
          # 3.2.1) Set context to the result of resolving value against the base IRI which is established as specified in section 5.1 Establishing a Base URI of [RFC3986]. Only the basic algorithm in section 5.2 of [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization are performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [RFC3987].
          context = RDF::URI(result.context_base || result.base).join(context)

          raise InvalidContext::RecursiveContextInclusion, "#{context}" if remote_contexts.include?(context)
          @remote_contexts = @remote_contexts + [context]

          context_no_base = self.dup
          context_no_base.base = nil
          context_no_base.provided_context = context
          context_no_base.context_base = context

          RDF::Util::File.open_file(context) do |f|
            # 3.2.5) Dereference context. If the dereferenced document has no top-level JSON object with an @context member, an invalid remote context has been detected and processing is aborted; otherwise, set context to the value of that member.
            jo = JSON.load(f)
            raise InvalidContext::InvalidRemoteContext, "#{context}" unless jo.is_a?(Hash) && jo.has_key?('@context')
            context = jo['@context']
          end

          # 3.2.6) Set context to the result of recursively calling this algorithm, passing context no base for active context, context for local context, and remote contexts.
          context = context_no_base.parse(context, remote_contexts.dup)
          context.base = result.base unless result.base.nil?
          result = context
          debug("parse") {"=> provided_context: #{context.inspect}"}
        rescue Exception => e
          debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
          raise InvalidContext::InvalidRemoteContext, "#{context_no_base.context_base}", e.backtrace if @options[:validate]
        end
      when Hash
        # If context has a @vocab member: if its value is not a valid absolute IRI or null trigger an INVALID_VOCAB_MAPPING error; otherwise set the active context's vocabulary mapping to its value and remove the @vocab member from context.
        {
          '@base' => :base=,
          '@language' => :default_language=,
          '@vocab'    => :vocab=
        }.each do |key, setter|
          v = context.fetch(key, false)
          unless v == false
            context.delete(key)
            debug("parse") {"Set #{key} to #{v.inspect}"}
            result.send(setter, v)
          end
        end

        defined = {}
      # For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
        depth do
          context.keys.each do |key|
            result.create_term_definition(context, key, defined)
          end
        end
      else
        # 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
        raise InvalidContext::InvalidLocalContext
      end
    end
  end
  result
end

#reverse?(property) ⇒ Boolean

Is this a reverse term

Returns:

  • (Boolean)


585
586
587
# File 'lib/json/ld/context.rb', line 585

def reverse?(property)
  term_definitions[property] && term_definitions[property].reverse_property
end

#serialize(options = {}) ⇒ Hash

Generate @context

If a context was supplied in global options, use that, otherwise, generate one from this representation.

Parameters:

  • options (Hash{Symbol => Object}) (defaults to: {})

    ({})

Returns:



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/json/ld/context.rb', line 446

def serialize(options = {})
  depth(options) do
    # FIXME: not setting provided_context now
    use_context = case provided_context
    when RDF::URI
      debug "serlialize: reuse context: #{provided_context.inspect}"
      provided_context.to_s
    when Hash, Array
      debug "serlialize: reuse context: #{provided_context.inspect}"
      provided_context
    else
      debug("serlialize: generate context")
      debug("") {"=> context: #{inspect}"}
      ctx = {}
      ctx['@base'] = base.to_s if base && base != doc_base
      ctx['@language'] = default_language.to_s if default_language
      ctx['@vocab'] = vocab.to_s if vocab

      # Term Definitions
      term_definitions.each do |term, definition|
        ctx[term] = definition.to_context_definition(self)
      end

      debug("") {"start_doc: context=#{ctx.inspect}"}
      ctx
    end

    # Return hash with @context, or empty
    r = {}
    r['@context'] = use_context unless use_context.nil? || use_context.empty?
    r
  end
end

#set_mapping(term, value) ⇒ RDF::URI, String

Deprecated.

FIXME: this should go away Set term mapping

Parameters:

  • term (#to_s)
  • value (RDF::URI, String, nil)

Returns:

  • (RDF::URI, String)


511
512
513
514
515
516
517
518
519
520
# File 'lib/json/ld/context.rb', line 511

def set_mapping(term, value)
  debug("") {"map #{term.inspect} to #{value.inspect}"}
  term = term.to_s
  term_definitions[term] = TermDefinition.new(term, value)

  term_sym = term.empty? ? "" : term.to_sym
  iri_to_term.delete(term_definitions[term].id.to_s) if term_definitions[term].id.is_a?(String)
  @options[:prefixes][term_sym] = value if @options.has_key?(:prefixes)
  iri_to_term[value.to_s] = term
end