Class: JSON::LD::EvaluationContext

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

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

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

Constructor Details

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

Create new evaluation context

Yields:

  • (ec)

Yield Parameters:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/json/ld/evaluation_context.rb', line 93

def initialize(options = {})
  @base = RDF::URI(options[:base]) if options[:base]
  @mappings =  {}
  @coercions = {}
  @containers = {}
  @languages = {}
  @iri_to_curie = {}
  @iri_to_term = {
    RDF.to_uri.to_s => "rdf",
    RDF::XSD.to_uri.to_s => "xsd"
  }

  @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

#baseObject (readonly)

The base.

The document base IRI, used for expanding relative IRIs.



14
15
16
# File 'lib/json/ld/evaluation_context.rb', line 14

def base
  @base
end

#coercionsObject

Type coersion

The @type keyword is used to specify type coersion rules for the data. For each key in the map, the key is a String representation of the property for which String values will be coerced and the value is the datatype (or @id) to coerce to. Type coersion for the value ‘@id` asserts that all vocabulary terms listed should undergo coercion to an IRI, including CURIE processing for compact IRI Expressions like `foaf:homepage`.



45
46
47
# File 'lib/json/ld/evaluation_context.rb', line 45

def coercions
  @coercions
end

#containersObject

List coercion

The @container keyword is used to specify how arrays are to be treated. A value of @list indicates that arrays of values are to be treated as an ordered list. A value of @set indicates that arrays are to be treated as unordered and that singular values are always coerced to an array form on expansion and compaction.



54
55
56
# File 'lib/json/ld/evaluation_context.rb', line 54

def containers
  @containers
end

#context_baseObject

The base IRI of the context, if loaded remotely.



19
20
21
# File 'lib/json/ld/evaluation_context.rb', line 19

def context_base
  @context_base
end

#default_languageObject

Default language

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



71
72
73
# File 'lib/json/ld/evaluation_context.rb', line 71

def default_language
  @default_language
end

#iri_to_curieObject

Reverse mappings from IRI to a term or CURIE



29
30
31
# File 'lib/json/ld/evaluation_context.rb', line 29

def iri_to_curie
  @iri_to_curie
end

#iri_to_termObject

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



34
35
36
# File 'lib/json/ld/evaluation_context.rb', line 34

def iri_to_term
  @iri_to_term
end

#languagesObject

Language coercion

The @language keyword is used to specify language coercion rules for the data. For each key in the map, the key is a String representation of the property for which String values will be coerced and the value is the language to coerce to. If no property-specific language is given, any default language from the context is used.



64
65
66
# File 'lib/json/ld/evaluation_context.rb', line 64

def languages
  @languages
end

#mappingsObject

A list of current, in-scope mappings from term to IRI.



24
25
26
# File 'lib/json/ld/evaluation_context.rb', line 24

def mappings
  @mappings
end

#optionsObject

Global options used in generating IRIs



83
84
85
# File 'lib/json/ld/evaluation_context.rb', line 83

def options
  @options
end

#provided_contextObject

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



86
87
88
# File 'lib/json/ld/evaluation_context.rb', line 86

def provided_context
  @provided_context
end

#vocabObject

Default vocabulary

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



79
80
81
# File 'lib/json/ld/evaluation_context.rb', line 79

def vocab
  @vocab
end

Instance Method Details

#alias(value) ⇒ String

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:

Returns:

  • (String)


389
390
391
# File 'lib/json/ld/evaluation_context.rb', line 389

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

#coerce(property) ⇒ RDF::URI, '@id'

Retrieve term coercion

Parameters:

  • property (String)

    in unexpanded form

Returns:



399
400
401
402
403
404
# File 'lib/json/ld/evaluation_context.rb', line 399

def coerce(property)
  # Map property, if it's not an RDF::Value
  # @type and @graph always is an IRI
  return '@id' if [RDF.type, '@type', '@graph'].include?(property)
  @coercions.fetch(property, nil)
end

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

Compact an IRI

Parameters:

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

    ({})

Options Hash (options):

  • position (:subject, :predicate, :type)

    Useful when determining how to serialize.

  • :value (Object)

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

  • :not_term (Boolean) — default: false

    Don’t return a term, but only a CURIE or IRI.

Returns:

  • (String)

    compacted form of IRI

See Also:



524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
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
# File 'lib/json/ld/evaluation_context.rb', line 524

def compact_iri(iri, options = {})
  depth(options) do
    debug {"compact_iri(#{iri.inspect}, #{options.inspect})"}

    value = options.fetch(:value, nil)

    # Get a list of terms which map to iri
    matched_terms = mappings.keys.select {|t| mapping(t).to_s == iri}
    debug("compact_iri", "initial terms: #{matched_terms.inspect}")

    # Create an empty list of terms _terms_ that will be populated with terms that are ranked according to how closely they match value. Initialize highest rank to 0, and set a flag list container to false.
    terms = {}

    # If value is a @list add a term rank for each
    # term mapping to iri which has @container @list.
    debug("compact_iri", "#{value.inspect} is a list? #{list?(value).inspect}")
    if list?(value)
      list_terms = matched_terms.select {|t| container(t) == '@list'}
        
      terms = list_terms.inject({}) do |memo, t|
        memo[t] = term_rank(t, value)
        memo
      end unless list_terms.empty?
      debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
      terms.delete_if {|t, r| r == 0}
    end
    
    # Otherwise, value is @value or a native type.
    # Add a term rank for each term mapping to iri
    # which does not have @container @list
    if terms.empty?
      non_list_terms = matched_terms.reject {|t| container(t) == '@list'}

      # If value is a @list, exclude from term map those terms
      # with @container @set
      non_list_terms.reject {|t| container(t) == '@set'} if list?(value)

      terms = non_list_terms.inject({}) do |memo, t|
        memo[t] = term_rank(t, value)
        memo
      end unless non_list_terms.empty?
      debug("term map") {"remove zero rank terms: #{terms.keys.select {|t| terms[t] == 0}}"} if terms.any? {|t,r| r == 0}
      terms.delete_if {|t, r| r == 0}
    end

    # If we don't want terms, remove anything that's not a CURIE or IRI
    terms.keep_if {|t, v| t.index(':') } if options.fetch(:not_term, false)

    # Find terms having the greatest term match value
    least_distance = terms.values.max
    terms = terms.keys.select {|t| terms[t] == least_distance}

    # If terms is empty, and the active context has a @vocab which is a  prefix of iri where the resulting relative IRI is not a term in the  active context. The resulting relative IRI is the unmatched part of iri.
    if vocab && terms.empty? && iri.to_s.index(vocab) == 0 &&
       [:predicate, :type].include?(options[:position])
      terms << iri.to_s.sub(vocab, '')
      debug("vocab") {"vocab: #{vocab}, rel: #{terms.first}"}
    end

    # If terms is empty, add a compact IRI representation of iri for each 
    # term in the active context which maps to an IRI which is a prefix for 
    # iri where the resulting compact IRI is not a term in the active 
    # context. The resulting compact IRI is the term associated with the 
    # partially matched IRI in the active context concatenated with a colon 
    # (:) character and the unmatched part of iri.
    if terms.empty?
      debug("curies") {"mappings: #{mappings.inspect}"}
      curies = mappings.keys.map do |k|
        debug("curies[#{k}]") {"#{mapping(k).inspect}"}
        #debug("curies[#{k}]") {"#{(mapping(k).to_s.length > 0).inspect}, #{iri.to_s.index(mapping(k).to_s)}"}
        iri.to_s.sub(mapping(k).to_s, "#{k}:") if
          mapping(k).to_s.length > 0 &&
          iri.to_s.index(mapping(k).to_s) == 0 &&
          iri.to_s != mapping(k).to_s
      end.compact

      debug("curies") do
        curies.map do |c|
          "#{c}: " +
          "container: #{container(c).inspect}, " +
          "coerce: #{coerce(c).inspect}, " +
          "lang: #{language(c).inspect}"
        end.inspect
      end

      terms = curies.select do |curie|
        (options[:position] != :predicate || container(curie) != '@list') &&
        coerce(curie).nil? &&
        language(curie) == default_language
      end

      debug("curies") {"selected #{terms.inspect}"}
    end

    # If we still don't have any terms and we're using standard_prefixes,
    # try those, and add to mapping
    if terms.empty? && @options[:standard_prefixes]
      terms = RDF::Vocabulary.
        select {|v| iri.index(v.to_uri.to_s) == 0}.
        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
      debug("curies") {"using standard prefies: #{terms.inspect}"}
    end

    if terms.empty?
      # If there is a mapping from the complete IRI to null, return null,
      # otherwise, return the complete IRI.
      if mappings.has_key?(iri.to_s) && !mapping(iri)
        debug("iri") {"use nil IRI mapping"}
        terms << nil
      else
        terms << iri.to_s
      end
    end

    # Get the first term based on distance and lexecographical order
    # Prefer terms that don't have @container @set over other terms, unless as set is true
    terms = terms.sort do |a, b|
      debug("term sort") {"c(a): #{container(a).inspect}, c(b): #{container(b)}"}
      if a.to_s.length == b.to_s.length
        a.to_s <=> b.to_s
      else
        a.to_s.length <=> b.to_s.length
      end
    end
    debug("sorted terms") {terms.inspect}
    result = terms.first

    debug {"=> #{result.inspect}"}
    result
  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:



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
840
841
842
843
844
845
846
847
848
849
850
851
852
# File 'lib/json/ld/evaluation_context.rb', line 787

def compact_value(property, value, options = {})
  raise ProcessingError::Lossy, "attempt to compact a non-object value: #{value.inspect}" unless value.is_a?(Hash)

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

    result = case
      #when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :type) == RDF::XSD[t]}
    #  # Compact native type
    #  debug {" (native)"}
    #  l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :type))
    #  l.canonicalize.object
    when coerce(property) == '@id' && value.has_key?('@id')
      # Compact an @id coercion
      debug {" (@id & coerce)"}
      compact_iri(value['@id'], :position => :subject)
    when value['@type'] && expand_iri(value['@type'], :position => :type) == coerce(property)
      # Compact common datatype
      debug {" (@type & coerce) == #{coerce(property)}"}
      value['@value']
    when value.has_key?('@id')
      # Compact an IRI
      value[self.alias('@id')] = compact_iri(value['@id'], :position => :subject)
      debug {" (#{self.alias('@id')} => #{value['@id']})"}
      value
    when value['@language'] && value['@language'] == language(property)
      # Compact language
      debug {" (@language) == #{language(property).inspect}"}
      value['@value']
    when value['@value'] && !value['@value'].is_a?(String)
      # Compact simple literal to string
      debug {" (@value not string)"}
      value['@value']
    when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !default_language
      # Compact simple literal to string
      debug {" (@value && !@language && !@type && !coerce && !language)"}
      value['@value']
    when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !language(property)
      # Compact simple literal to string
      debug {" (@value && !@language && !@type && !coerce && language(property).false)"}
      value['@value']
    when value['@type']
      # Compact datatype
      debug {" (@type)"}
      value[self.alias('@type')] = compact_iri(value['@type'], :position => :type)
      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)


427
428
429
# File 'lib/json/ld/evaluation_context.rb', line 427

def container(property)
  @containers.fetch(property.to_s, nil)
end

#dupObject



864
865
866
867
868
869
870
871
872
873
874
875
876
# File 'lib/json/ld/evaluation_context.rb', line 864

def dup
  # Also duplicate mappings, coerce and list
  ec = super
  ec.mappings = mappings.dup
  ec.coercions = coercions.dup
  ec.containers = containers.dup
  ec.languages = languages.dup
  ec.default_language = default_language
  ec.options = options
  ec.iri_to_term = iri_to_term.dup
  ec.iri_to_curie = iri_to_curie.dup
  ec
end

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

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

Parameters:

  • iri (String)

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

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

Options Hash (options):

  • position (:subject, :predicate, :type)

    Useful when determining how to serialize.

  • base (RDF::URI) — default: self.base

    Base IRI to use when expanding relative IRIs.

Returns:

  • (RDF::URI, String)

    IRI or String, if it’s a keyword

Raises:

  • (RDF::ReaderError)

    if the iri cannot be expanded

See Also:



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/json/ld/evaluation_context.rb', line 489

def expand_iri(iri, options = {})
  return iri unless iri.is_a?(String)
  prefix, suffix = iri.split(':', 2)
  return mapping(iri) if mapping(iri) # If it's an exact match
  debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]
  base = [:subject].include?(options[:position]) ? options.fetch(:base, self.base) : nil
  prefix = prefix.to_s
  case
  when prefix == '_' && suffix          then bnode(suffix)
  when iri.to_s[0,1] == "@"             then iri
  when suffix.to_s[0,2] == '//'         then uri(iri)
  when mappings.fetch(prefix, false)    then uri(mappings[prefix] + suffix.to_s)
  when base                             then base.join(iri)
  when vocab                            then uri("#{vocab}#{iri}")
  else
    # Otherwise, it must be an absolute IRI
    u = uri(iri)
    u if u.absolute? || [:subject].include?(options[:position])
  end
end

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

Expand a value from compacted to expanded form making the context unnecessary. This method is used as part of more general expansion and operates on RHS values, using a supplied key to determine @type and @container coercion rules.

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:



676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
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
# File 'lib/json/ld/evaluation_context.rb', line 676

def expand_value(property, value, options = {})
  options = {:useNativeTypes => true}.merge(options)
  depth(options) do
    debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
    value = RDF::Literal(value) if RDF::Literal(value).has_datatype?
    dt = case value
    when RDF::Literal
      case value.datatype
      when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double then value.datatype
      else value
      end
    when RDF::Term then value.class.name
    else value
    end

    result = case dt
    when RDF::XSD.boolean
      debug("xsd:boolean")
      case coerce(property)
      when RDF::XSD.double.to_s
        {"@value" => value.to_s, "@type" => RDF::XSD.double.to_s}
      else
        if options[:useNativeTypes]
          # Unless there's coercion, to not modify representation
          {"@value" => (value.is_a?(RDF::Literal::Boolean) ? value.object : value)}
        else
          {"@value" => value.to_s, "@type" => RDF::XSD.boolean.to_s}
        end
      end
    when RDF::XSD.integer
      debug("xsd:integer")
      case coerce(property)
      when RDF::XSD.double.to_s
        {"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
      when RDF::XSD.integer.to_s, nil
        # Unless there's coercion, to not modify representation
        if options[:useNativeTypes]
          {"@value" => value.is_a?(RDF::Literal::Integer) ? value.object : value}
        else
          {"@value" => value.to_s, "@type" => RDF::XSD.integer.to_s}
        end
      else
        res = Hash.ordered
        res['@value'] = value.to_s
        res['@type'] = coerce(property)
        res
      end
    when RDF::XSD.double
      debug("xsd:double")
      case coerce(property)
      when RDF::XSD.integer.to_s
        {"@value" => value.to_int.to_s, "@type" => RDF::XSD.integer.to_s}
      when RDF::XSD.double.to_s
        {"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
      when nil
        if options[:useNativeTypes]
          # Unless there's coercion, to not modify representation
          {"@value" => value.is_a?(RDF::Literal::Double) ? value.object : value}
        else
          {"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
        end
      else
        res = Hash.ordered
        res['@value'] = value.to_s
        res['@type'] = coerce(property)
        res
      end
    when "RDF::URI", "RDF::Node"
      debug("URI | BNode") { value.to_s }
      {'@id' => value.to_s}
    when RDF::Literal
      debug("Literal")
      res = Hash.ordered
      res['@value'] = value.to_s
      res['@type'] = value.datatype.to_s if value.has_datatype?
      res['@language'] = value.language.to_s if value.has_language?
      res
    else
      debug("else")
      case coerce(property)
      when '@id'
        {'@id' => expand_iri(value, :position => :subject).to_s}
      when nil
        debug("expand value") {"lang(prop): #{language(property).inspect}, def: #{default_language.inspect}"}
        language(property) ? {"@value" => value.to_s, "@language" => language(property)} : {"@value" => value.to_s}
      else
        res = Hash.ordered
        res['@value'] = value.to_s
        res['@type'] = coerce(property).to_s
        res
      end
    end
    
    debug {"=> #{result.inspect}"}
    result
  end
end

#inspectObject



854
855
856
857
858
859
860
861
862
# File 'lib/json/ld/evaluation_context.rb', line 854

def inspect
  v = %w([EvaluationContext)
  v << "def_language=#{default_language}"
  v << "languages[#{languages.keys.length}]=#{languages}"
  v << "mappings[#{mappings.keys.length}]=#{mappings}"
  v << "coercions[#{coercions.keys.length}]=#{coercions}"
  v << "containers[#{containers.length}]=#{containers}"
  v.join(", ") + "]"
end

#language(property) ⇒ String

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

Returns:

  • (String)


450
451
452
# File 'lib/json/ld/evaluation_context.rb', line 450

def language(property)
  @languages.fetch(property.to_s, @default_language) if !coerce(property)
end

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

Retrieve term mapping

Parameters:

  • term (String, #to_s)

Returns:



356
357
358
# File 'lib/json/ld/evaluation_context.rb', line 356

def mapping(term)
  @mappings.fetch(term.to_s, nil)
end

#parse(context) ⇒ Object

Create an Evaluation Context using an existing context as a start by parsing the input.

Parameters:

  • context (String, #read, Array, Hash, EvaluatoinContext)

Raises:

  • (InvalidContext)

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



122
123
124
125
126
127
128
129
130
131
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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
# File 'lib/json/ld/evaluation_context.rb', line 122

def parse(context)
  case context
  when nil
    EvaluationContext.new
  when EvaluationContext
    debug("parse") {"context: #{context.inspect}"}
    context.dup
  when IO, StringIO
    debug("parse") {"io: #{context}"}
    # Load context document, if it is a string
    begin
      ctx = JSON.load(context)
      parse(ctx["@context"] || {})
    rescue JSON::ParserError => e
      debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
      raise JSON::LD::InvalidContext::Syntax, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
      self.dup
    end
  when nil
    debug("parse") {"nil"}
    # Load context document, if it is a string
    ec = EvaluationContext.new(options)
  when String
    debug("parse") {"remote: #{context}"}
    # Load context document, if it is a string
    ec = nil
    begin
      url = expand_iri(context, :base => context_base || base, :position => :subject)
      ecdup = self.dup
      ecdup.context_base = url  # Set context_base for recursive remote contexts
      RDF::Util::File.open_file(url) {|f| ec = ecdup.parse(f)}
      ec.provided_context = context
      ec.context_base = url
      debug("parse") {"=> provided_context: #{context.inspect}"}
      ec
    rescue Exception => e
      debug("parse") {"Failed to retrieve @context from remote document at #{context.inspect}: #{e.message}"}
      raise JSON::LD::InvalidContext::LoadError, "Failed to retrieve remote context at #{context.inspect}: #{e.message}", e.backtrace if @options[:validate]
      self.dup
    end
  when Array
    # Process each member of the array in order, updating the active context
    # Updates evaluation context serially during parsing
    debug("parse") {"Array"}
    ec = self
    context.each {|c| ec = ec.parse(c)}
    ec.provided_context = context
    debug("parse") {"=> provided_context: #{context.inspect}"}
    ec
  when Hash
    new_ec = self.dup
    new_ec.provided_context = context.dup

    {
      '@language' => :default_language=,
      '@vocab'    => :vocab=
    }.each do |key, setter|
      v = context.fetch(key, false)
      if v.nil? || v.is_a?(String)
        context.delete(key)
        debug("parse") {"Set #{key} to #{v.inspect}"}
        new_ec.send(setter, v)
      elsif v
        raise InvalidContext::Syntax, "#{key.inspect} is invalid"
      end
    end

    num_updates = 1
    while num_updates > 0 do
      num_updates = 0

      # Map terms to IRIs/keywords first
      context.each do |key, value|
        # Expand a string value, unless it matches a keyword
        debug("parse") {"Hash[#{key}] = #{value.inspect}"}

        if KEYWORDS.include?(key)
          raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword"
        elsif term_valid?(key)
          # Remove all coercion information for the property
          new_ec.set_coerce(key, nil)
          new_ec.set_container(key, nil)
          @languages.delete(key)

          # Extract IRI mapping. This is complicated, as @id may have been aliased
          value = value.fetch('@id', nil) if value.is_a?(Hash)
          raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.class}" unless value.is_a?(String) || value.nil?

          iri = new_ec.expand_iri(value, :position => :predicate) if value.is_a?(String)
          if iri && new_ec.mappings.fetch(key, nil) != iri
            # Record term definition
            new_ec.set_mapping(key, iri)
            num_updates += 1
          elsif value.nil?
            new_ec.set_mapping(key, nil)
          end
        else
          raise InvalidContext::Syntax, "key #{key.inspect} is invalid"
        end
      end
    end

    # Next, look for coercion using new_ec
    context.each do |key, value|
      # Expand a string value, unless it matches a keyword
      debug("parse") {"coercion/list: Hash[#{key}] = #{value.inspect}"}
      case value
      when Hash
        # Must have one of @id, @language, @type or @container
        raise InvalidContext::Syntax, "mapping for #{key.inspect} missing one of @id, @language, @type or @container" if (%w(@id @language @type @container) & value.keys).empty?
        value.each do |key2, value2|
          iri = new_ec.expand_iri(value2, :position => :predicate) if value2.is_a?(String)
          case key2
          when '@type'
            raise InvalidContext::Syntax, "unknown mapping for '@type' to #{value2.class}" unless value2.is_a?(String) || value2.nil?
            if new_ec.coerce(key) != iri
              raise InvalidContext::Syntax, "unknown mapping for '@type' to #{iri.inspect}" unless RDF::URI(iri).absolute? || iri == '@id'
              # Record term coercion
              new_ec.set_coerce(key, iri)
            end
          when '@container'
            raise InvalidContext::Syntax, "unknown mapping for '@container' to #{value2.class}" unless %w(@list @set).include?(value2)
            if new_ec.container(key) != value2
              debug("parse") {"container #{key.inspect} as #{value2.inspect}"}
              new_ec.set_container(key, value2)
            end
          when '@language'
            if !new_ec.languages.has_key?(key) || new_ec.languages[key] != value2
              debug("parse") {"language #{key.inspect} as #{value2.inspect}"}
              new_ec.set_language(key, value2)
            end
          end
        end
      
        # If value has no @id, create a mapping from key
        # to the expanded key IRI
        unless value.has_key?('@id')
          iri = new_ec.expand_iri(key, :position => :predicate)
          new_ec.set_mapping(key, iri)
        end
      when nil, String
        # handled in previous loop
      else
        raise InvalidContext::Syntax, "attempt to map #{key.inspect} to #{value.class}"
      end
    end

    new_ec
  end
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:



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
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
# File 'lib/json/ld/evaluation_context.rb', line 281

def serialize(options = {})
  depth(options) do
    use_context = if provided_context
      debug "serlialize: reuse context: #{provided_context.inspect}"
      provided_context
    else
      debug("serlialize: generate context")
      debug {"=> context: #{inspect}"}
      ctx = Hash.ordered
      ctx['@language'] = default_language.to_s if default_language
      ctx['@vocab'] = vocab.to_s if vocab

      # Mappings
      mappings.keys.sort{|a, b| a.to_s <=> b.to_s}.each do |k|
        next unless term_valid?(k.to_s)
        debug {"=> mappings[#{k}] => #{mappings[k]}"}
        ctx[k] = mappings[k].to_s
      end

      unless coercions.empty? && containers.empty? && languages.empty?
        # Coerce
        (coercions.keys + containers.keys + languages.keys).uniq.sort.each do |k|
          next if k == '@type'

          # Turn into long form
          ctx[k] ||= Hash.ordered
          if ctx[k].is_a?(String)
            defn = Hash.ordered
            defn["@id"] = compact_iri(ctx[k], :position => :subject, :not_term => true)
            ctx[k] = defn
          end

          debug {"=> coerce(#{k}) => #{coerce(k)}"}
          if coerce(k) && !NATIVE_DATATYPES.include?(coerce(k))
            dt = coerce(k)
            dt = compact_iri(dt, :position => :type) unless dt == '@id'
            # Fold into existing definition
            ctx[k]["@type"] = dt
            debug {"=> datatype[#{k}] => #{dt}"}
          end

          debug {"=> container(#{k}) => #{container(k)}"}
          if %w(@list @set).include?(container(k))
            ctx[k]["@container"] = container(k)
            debug {"=> container[#{k}] => #{container(k).inspect}"}
          end

          debug {"=> language(#{k}) => #{language(k)}"}
          if language(k) != default_language
            ctx[k]["@language"] = language(k) ? language(k) : nil
            debug {"=> language[#{k}] => #{language(k).inspect}"}
          end
          
          # Remove an empty definition
          ctx.delete(k) if ctx[k].empty?
        end
      end

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

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

#set_coerce(property, value) ⇒ RDF::URI, '@id'

Set term coercion

Parameters:

  • property (String)

    in unexpanded form

  • value (RDF::URI, '@id')

Returns:



413
414
415
416
417
418
419
420
# File 'lib/json/ld/evaluation_context.rb', line 413

def set_coerce(property, value)
  debug {"coerce #{property.inspect} to #{value.inspect}"} unless @coercions[property.to_s] == value
  if value
    @coercions[property] = value
  else
    @coercions.delete(property)
  end
end

#set_container(property, value) ⇒ Boolean

Set container mapping

Parameters:

  • property (String)
  • value (String)

    one of @list, @set or nil

Returns:

  • (Boolean)


437
438
439
440
441
442
443
444
445
# File 'lib/json/ld/evaluation_context.rb', line 437

def set_container(property, value)
  return if @containers[property.to_s] == value
  debug {"coerce #{property.inspect} to #{value.inspect}"} 
  if value
    @containers[property.to_s] = value
  else
    @containers.delete(value)
  end
end

#set_language(property, value) ⇒ String

Set language mapping

Parameters:

  • property (String)
  • value (String)

Returns:

  • (String)


460
461
462
463
# File 'lib/json/ld/evaluation_context.rb', line 460

def set_language(property, value)
  # Use false for nil language
  @languages[property.to_s] = value ? value : false
end

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

Set term mapping

Parameters:

Returns:



367
368
369
370
371
372
373
374
375
376
377
# File 'lib/json/ld/evaluation_context.rb', line 367

def set_mapping(term, value)
  term = term.to_s
  term_sym = term.empty? ? "" : term.to_sym
#      raise InvalidContext::Syntax, "mapping term #{term.inspect} must be a string" unless term.is_a?(String)
#      raise InvalidContext::Syntax, "mapping value #{value.inspect} must be an RDF::URI" unless value.nil? || value.to_s[0,1] == '@' || value.is_a?(RDF::URI)
  debug {"map #{term.inspect} to #{value.inspect}"}
  iri_to_term.delete(@mappings[term].to_s) if @mappings[term]
  @mappings[term] = value
  @options[:prefixes][term_sym] = value if @options.has_key?(:prefixes)
  iri_to_term[value.to_s] = term
end

#term_valid?(term) ⇒ Boolean

Determine if ‘term` is a suitable term. Term may be any valid JSON string.

Parameters:

  • term (String)

Returns:

  • (Boolean)


471
472
473
# File 'lib/json/ld/evaluation_context.rb', line 471

def term_valid?(term)
  term.is_a?(String)
end