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?, #subject?, #subject_reference?, #value?

Constructor Details

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

Create new evaluation context

Yields:

  • (ec)

Yield Parameters:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/json/ld/evaluation_context.rb', line 80

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`.



40
41
42
# File 'lib/json/ld/evaluation_context.rb', line 40

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.



49
50
51
# File 'lib/json/ld/evaluation_context.rb', line 49

def containers
  @containers
end

#default_languageObject

Default language

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



66
67
68
# File 'lib/json/ld/evaluation_context.rb', line 66

def default_language
  @default_language
end

#iri_to_curieObject

Reverse mappings from IRI to a term or CURIE



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

def iri_to_curie
  @iri_to_curie
end

#iri_to_termObject

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



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

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.



59
60
61
# File 'lib/json/ld/evaluation_context.rb', line 59

def languages
  @languages
end

#mappingsObject

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



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

def mappings
  @mappings
end

#optionsObject

Global options used in generating IRIs



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

def options
  @options
end

#provided_contextObject

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



73
74
75
# File 'lib/json/ld/evaluation_context.rb', line 73

def provided_context
  @provided_context
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)


354
355
356
# File 'lib/json/ld/evaluation_context.rb', line 354

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:



364
365
366
367
368
369
# File 'lib/json/ld/evaluation_context.rb', line 364

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, :object, :datatype)

    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:



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
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
# File 'lib/json/ld/evaluation_context.rb', line 486

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
    terms = mappings.keys.select {|t| mapping(t).to_s == iri}

    # Create an association term map for terms to their associated
    # term rank.
    term_map = {}

    # 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 = terms.select {|t| container(t) == '@list'}
        
      term_map = 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: #{term_map.keys.select {|t| term_map[t] == 0}}"} if term_map.any? {|t,r| r == 0}
      term_map.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 term_map.empty?
      non_list_terms = 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)

      term_map = 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: #{term_map.keys.select {|t| term_map[t] == 0}}"} if term_map.any? {|t,r| r == 0}
      term_map.delete_if {|t, r| r == 0}
    end

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

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

    # If the list of found terms is empty, append a compact IRI for
    # each term which is a prefix of iri which does not have
    # @type coercion, @container coercion or @language coercion rules
    # along with the iri itself.
    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|
        container(curie) != '@list' &&
        coerce(curie).nil? &&
        language(curie) == default_language
      end

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

      # 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 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:



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
# File 'lib/json/ld/evaluation_context.rb', line 726

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 => :datatype) == RDF::XSD[t]}
    #  # Compact native type
    #  debug {" (native)"}
    #  l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :datatype))
    #  l.canonicalize.object
    when coerce(property) == '@id' && value.has_key?('@id')
      # Compact an @id coercion
      debug {" (@id & coerce)"}
      compact_iri(value['@id'], :position => :object)
    when value['@type'] && expand_iri(value['@type'], :position => :datatype) == 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 => :object)
      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 => :datatype)
      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)


392
393
394
# File 'lib/json/ld/evaluation_context.rb', line 392

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

#dupObject



803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/json/ld/evaluation_context.rb', line 803

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, :object, :datatype)

    Useful when determining how to serialize.

Returns:

  • (RDF::URI, String)

    IRI or String, if it’s a keyword

Raises:

  • (RDF::ReaderError)

    if the iri cannot be expanded

See Also:



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/json/ld/evaluation_context.rb', line 452

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}"} unless options[:quiet]
  base = self.base unless [:predicate, :datatype].include?(options[:position])
  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.has_key?(prefix)        then uri(mappings[prefix] + suffix.to_s)
  when base                             then base.join(iri)
  else
    # Otherwise, it must be an absolute IRI
    u = uri(iri)
    u if u.absolute? || [:subject, :object].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):

  • :native (Boolean) — default: true

    use native representations

Returns:

  • (Hash)

    Object representation of value

Raises:

  • (RDF::ReaderError)

    if the iri cannot be expanded

See Also:



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
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
# File 'lib/json/ld/evaluation_context.rb', line 627

def expand_value(property, value, options = {})
  options = {:native => true}.merge(options)
  depth(options) do
    debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
    result = case value
    when TrueClass, FalseClass, RDF::Literal::Boolean
      case coerce(property)
      when RDF::XSD.double.to_s
        {"@value" => value.to_s, "@type" => RDF::XSD.double.to_s}
      else
        if options[:native]
          # 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 Integer, RDF::Literal::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[:native]
          {"@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 Float, RDF::Literal::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[:native]
          # 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 BigDecimal, RDF::Literal::Decimal
      {"@value" => value.to_s, "@type" => RDF::XSD.decimal.to_s}
    when Date, Time, DateTime
      l = RDF::Literal(value)
      {"@value" => l.to_s, "@type" => l.datatype.to_s}
    when RDF::URI, RDF::Node
      {'@id' => value.to_s}
    when RDF::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
      case coerce(property)
      when '@id'
        {'@id' => expand_iri(value, :position => :object).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



793
794
795
796
797
798
799
800
801
# File 'lib/json/ld/evaluation_context.rb', line 793

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)


415
416
417
# File 'lib/json/ld/evaluation_context.rb', line 415

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:



321
322
323
# File 'lib/json/ld/evaluation_context.rb', line 321

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.



109
110
111
112
113
114
115
116
117
118
119
120
121
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
# File 'lib/json/ld/evaluation_context.rb', line 109

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 String, nil
    debug("parse") {"remote: #{context}"}
    # Load context document, if it is a string
    ec = nil
    begin
      RDF::Util::File.open_file(context.to_s) {|f| ec = parse(f)}
      ec.provided_context = context
      debug("parse") {"=> provided_context: #{context.inspect}"}
      ec
    rescue Exception => e
      debug("parse") {"Failed to retrieve @context from remote document at #{context}: #{e.message}"}
      raise JSON::LD::InvalidContext::LoadError, "Failed to parse remote context at #{context}: #{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
    
    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 key == '@language' && (value.nil? || value.is_a?(String))
          new_ec.default_language = value
        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 && KEYWORDS.include?(key)
            raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword"
          elsif 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:



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
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/json/ld/evaluation_context.rb', line 247

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

      # 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 => :datatype) 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:



378
379
380
381
382
383
384
385
# File 'lib/json/ld/evaluation_context.rb', line 378

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)


402
403
404
405
406
407
408
409
410
# File 'lib/json/ld/evaluation_context.rb', line 402

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)


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

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:



332
333
334
335
336
337
338
339
340
341
342
# File 'lib/json/ld/evaluation_context.rb', line 332

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)


436
437
438
# File 'lib/json/ld/evaluation_context.rb', line 436

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