Class: JSON::LD::Context

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

Defined Under Namespace

Classes: TermDefinition

Constant Summary collapse

PRELOADED =

Preloaded contexts. To avoid runtime context parsing and downloading, contexts may be pre-loaded by implementations.

Returns:

{}
INITIAL_CONTEXTS =

Initial contexts, defined on first access

{}
CACHE_SIZE =

Defines the maximum number of interned URI references that can be held cached in memory at any one time.

100
ID_NULL_OBJECT =

The following constants are used to reduce object allocations in #create_term_definition below

{ '@id' => nil }.freeze
NON_TERMDEF_KEYS =
Set.new(%w[@base @direction @language @protected @version @vocab]).freeze
JSON_LD_10_EXPECTED_KEYS =
Set.new(%w[@container @id @language @reverse @type]).freeze
JSON_LD_11_EXPECTED_KEYS =
Set.new(%w[@context @direction @index @nest @prefix @protected]).freeze
JSON_LD_EXPECTED_KEYS =
(JSON_LD_10_EXPECTED_KEYS + JSON_LD_11_EXPECTED_KEYS).freeze
JSON_LD_10_TYPE_VALUES =
Set.new(%w[@id @vocab]).freeze
JSON_LD_11_TYPE_VALUES =
Set.new(%w[@json @none]).freeze
PREFIX_URI_ENDINGS =
Set.new(%w(: / ? # [ ] @)).freeze
CONTAINERS_GRAPH =

The following constants are used to reduce object allocations in #compact_iri below

%w[@graph@id @graph@id@set].freeze
CONTAINERS_GRAPH_INDEX =
%w[@graph@index @graph@index@set].freeze
CONTAINERS_GRAPH_INDEX_INDEX =
%w[@graph@index @graph@index@set @index @index@set].freeze
CONTAINERS_GRAPH_SET =
%w[@graph @graph@set @set].freeze
CONTAINERS_ID_TYPE =
%w[@id @id@set @type @set@type].freeze
CONTAINERS_ID_VOCAB =
%w[@id @vocab @none].freeze
CONTAINERS_INDEX_SET =
%w[@index @index@set].freeze
CONTAINERS_LANGUAGE =
%w[@language @language@set].freeze
CONTAINERS_VALUE =
%w[@value].freeze
CONTAINERS_VOCAB_ID =
%w[@vocab @id @none].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#add_value, #as_array, #as_resource, #blank_node?, #compare_values, #graph?, #has_value?, #index?, #list?, #node?, #node_or_ref?, #node_reference?, #property?, #simple_graph?, #value?

Constructor Details

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

Create new evaluation context

Parameters:

  • options (Hash)

Options Hash (**options):

  • :prefixes (Hash{Symbol => String})

    See ‘RDF::Reader#initialize`

  • :vocab (String, #to_s)

    Initial value for @vocab

  • :language (String, #to_s)

    Initial value for @langauge

Yields:

  • (ec)

Yield Parameters:



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

def initialize(**options)
  @processingMode = 'json-ld-1.0' if options[:processingMode] == 'json-ld-1.0'
  @term_definitions = {}
  @iri_to_term = {
    RDF.to_uri.to_s => "rdf",
    RDF::XSD.to_uri.to_s => "xsd"
  }
  @namer = BlankNodeMapper.new("t")

  @options = options

  # Load any defined prefixes
  (options[:prefixes] || {}).each_pair do |k, v|
    next if k.nil?

    @iri_to_term[v.to_s] = k
    @term_definitions[k.to_s] = TermDefinition.new(k, id: v.to_s, simple: true, prefix: true)
  end

  self.vocab = options[:vocab] if options[:vocab]
  self.default_language = options[:language] if /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(options[:language])
  @term_definitions = options[:term_definitions] if options[:term_definitions]

  # log_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.



57
58
59
# File 'lib/json/ld/context.rb', line 57

def base
  @base
end

#context_baseRDF::URI

Returns base IRI of the context, if loaded remotely.

Returns:

  • (RDF::URI)

    base IRI of the context, if loaded remotely.



60
61
62
# File 'lib/json/ld/context.rb', line 60

def context_base
  @context_base
end

#default_direction"lrt", "rtl"

Default direction

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

Returns:

  • ("lrt", "rtl")


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

def default_direction
  @default_direction
end

#default_languageString

Default language

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

Returns:

  • (String)


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

def default_language
  @default_language
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



67
68
69
# File 'lib/json/ld/context.rb', line 67

def iri_to_term
  @iri_to_term
end

#namerBlankNodeNamer

Returns:



100
101
102
# File 'lib/json/ld/context.rb', line 100

def namer
  @namer
end

#optionsHash{Symbol => Object}

Returns Global options used in generating IRIs.

Returns:

  • (Hash{Symbol => Object})

    Global options used in generating IRIs



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

def options
  @options
end

#previous_contextContext

Previous definition for this context. This is used for rolling back type-scoped contexts.

Returns:



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

def previous_context
  @previous_context
end

#property_scopedBoolean

Context is property-scoped

Returns:

  • (Boolean)


75
76
77
# File 'lib/json/ld/context.rb', line 75

def property_scoped
  @property_scoped
end

#term_definitionsHash{String => TermDefinition} (readonly)

Term definitions

Returns:



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

def term_definitions
  @term_definitions
end

#vocabRDF::URI

Default vocabulary

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

Returns:



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

def vocab
  @vocab
end

Class Method Details

.add_preloaded(url, context = nil, &block) ⇒ Object

Add preloaded context. In the block form, the context is lazy evaulated on first use.

Parameters:

Yield Returns:



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

def add_preloaded(url, context = nil, &block)
  PRELOADED[url.to_s.freeze] = context || block
end

.alias_preloaded(a, url) ⇒ Object

Alias a previousliy loaded context

Parameters:



42
43
44
# File 'lib/json/ld/context.rb', line 42

def alias_preloaded(a, url)
  PRELOADED[a.to_s.freeze] = PRELOADED[url.to_s.freeze]
end

.cacheRDF::Util::Cache

Class-level cache used for retaining parsed remote contexts.

Returns:

  • (RDF::Util::Cache)


138
139
140
# File 'lib/json/ld/context.rb', line 138

def self.cache
  @cache ||= RDF::Util::Cache.new(CACHE_SIZE)
end

.inverse_cacheRDF::Util::Cache

Class-level cache inverse contexts.

Returns:

  • (RDF::Util::Cache)


147
148
149
# File 'lib/json/ld/context.rb', line 147

def self.inverse_cache
  @inverse_cache ||= RDF::Util::Cache.new(CACHE_SIZE)
end

.new(**options) ⇒ Object

Allow caching of well-known contexts



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

def self.new(**options)
  if (options.keys - %i[
    compactArrays
    documentLoader
    extractAllScripts
    ordered
    processingMode
    validate
  ]).empty?
    # allow caching
    key = options.hash
    INITIAL_CONTEXTS[key] ||= begin
      context = JSON::LD::Context.allocate
      context.send(:initialize, **options)
      context.freeze
      context.term_definitions.freeze
      context
    end
  else
    # Don't try to cache
    context = JSON::LD::Context.allocate
    context.send(:initialize, **options)
    context
  end
end

.parse(local_context, base: nil, override_protected: false, propagate: true, **options) ⇒ Context

Create a new context by parsing a context.

Parameters:

  • local_context (String, #read, Array, Hash, Context)
  • base (String, #to_s) (defaults to: nil)

    (nil) The Base IRI to use when expanding the document. This overrides the value of ‘input` if it is a IRI. If not specified and `input` is not an IRI, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context.

  • override_protected (Boolean) (defaults to: false)

    (false) Protected terms may be cleared.

  • propagate (Boolean) (defaults to: true)

    (true) If false, retains any previously defined term, which can be rolled back when the descending into a new node object changes.

Returns:

Raises:

  • (JsonLdError)

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

See Also:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/json/ld/context.rb', line 117

def self.parse(local_context,
               base: nil,
               override_protected: false,
               propagate: true,
               **options)
  c = new(**options)
  if local_context.respond_to?(:empty?) && local_context.empty?
    c
  else
    c.parse(local_context,
      base: base,
      override_protected: override_protected,
      propagate: propagate)
  end
end

Instance Method Details

#as_array?(term) ⇒ Boolean

Should values be represented using an array?

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (Boolean)


1182
1183
1184
1185
1186
1187
# File 'lib/json/ld/context.rb', line 1182

def as_array?(term)
  return true if CONTEXT_CONTAINER_ARRAY_TERMS.include?(term)

  term = find_definition(term)
  term && (term.as_set? || term.container_mapping.include?('@list'))
end

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

Retrieve term coercion

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:



1168
1169
1170
1171
1172
1173
1174
1175
# File 'lib/json/ld/context.rb', line 1168

def coerce(term)
  # Map property, if it's not an RDF::Value
  # @type is always is an IRI
  return '@id' if term == RDF.type || term == '@type'

  term = find_definition(term)
  term&.type_mapping
end

#compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil) ⇒ String

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

Parameters:

  • iri (RDF::URI)
  • base (String, RDF::URI) (defaults to: nil)

    for resolving document-relative IRIs

  • value (Object) (defaults to: nil)

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

  • reverse (Boolean) (defaults to: false)

    specifies whether a reverse property is being compacted

  • vocab (Boolean) (defaults to: nil)

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

Returns:

  • (String)

    compacted form of IRI

See Also:



1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
# File 'lib/json/ld/context.rb', line 1410

def compact_iri(iri, base: nil, reverse: false, value: nil, vocab: nil)
  return if iri.nil?

  iri = iri.to_s

  if vocab && inverse_context.key?(iri)
    default_language = if default_direction
      "#{self.default_language}_#{default_direction}".downcase
    else
      (self.default_language || "@none").downcase
    end
    containers = []
    tl = "@language"
    tl_value = "@null"
    containers.concat(CONTAINERS_INDEX_SET) if index?(value) && !graph?(value)

    # If the value is a JSON Object with the key @preserve, use the value of @preserve.
    value = value['@preserve'].first if value.is_a?(Hash) && value.key?('@preserve')

    if reverse
      tl = "@type"
      tl_value = "@reverse"
      containers << '@set'
    elsif list?(value)
      # 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 = "@none"
        item_type = "@none"
        if value?(item)
          if item.key?('@direction')
            item_language = "#{item['@language']}_#{item['@direction']}".downcase
          elsif item.key?('@language')
            item_language = item['@language'].downcase
          elsif item.key?('@type')
            item_type = item['@type']
          else
            item_language = "@null"
          end
        else
          item_type = '@id'
        end
        common_language ||= item_language
        common_language = '@none' if item_language != common_language && value?(item)
        common_type ||= item_type
        common_type = '@none' if item_type != common_type
      end

      common_language ||= '@none'
      common_type ||= '@none'
      if common_type == '@none'
        tl_value = common_language
      else
        tl = '@type'
        tl_value = common_type
      end
    elsif graph?(value)
      # Prefer @index and @id containers, then @graph, then @index
      containers.concat(CONTAINERS_GRAPH_INDEX_INDEX) if index?(value)
      containers.concat(CONTAINERS_GRAPH) if value.key?('@id')

      # Prefer an @graph container next
      containers.concat(CONTAINERS_GRAPH_SET)

      # Lastly, in 1.1, any graph can be indexed on @index or @id, so add if we haven't already
      containers.concat(CONTAINERS_GRAPH_INDEX) unless index?(value)
      containers.concat(CONTAINERS_GRAPH) unless value.key?('@id')
      containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
      containers << '@set'

      tl = '@type'
      tl_value = '@id'
    else
      if value?(value)
        # In 1.1, an language map can be used to index values using @none
        if value.key?('@language') && !index?(value)
          tl_value = value['@language'].downcase
          tl_value += "_#{value['@direction']}" if value['@direction']
          containers.concat(CONTAINERS_LANGUAGE)
        elsif value.key?('@direction') && !index?(value)
          tl_value = "_#{value['@direction']}"
        elsif value.key?('@type')
          tl_value = value['@type']
          tl = '@type'
        end
      else
        # In 1.1, an id or type map can be used to index values using @none
        containers.concat(CONTAINERS_ID_TYPE)
        tl = '@type'
        tl_value = '@id'
      end
      containers << '@set'
    end

    containers << '@none'

    # In 1.1, an index map can be used to index values using @none, so add as a low priority
    containers.concat(CONTAINERS_INDEX_SET) unless index?(value)
    # Values without type or language can use @language map
    containers.concat(CONTAINERS_LANGUAGE) if value?(value) && value.keys == CONTAINERS_VALUE

    tl_value ||= '@null'
    preferred_values = []
    preferred_values << '@reverse' if tl_value == '@reverse'
    if ['@id', '@reverse'].include?(tl_value) && value.is_a?(Hash) && value.key?('@id')
      t_iri = compact_iri(value['@id'], vocab: true, base: base)
      if (r_td = term_definitions[t_iri]) && r_td.id == value['@id']
        preferred_values.concat(CONTAINERS_VOCAB_ID)
      else
        preferred_values.concat(CONTAINERS_ID_VOCAB)
      end
    else
      tl = '@any' if list?(value) && value['@list'].empty?
      preferred_values.concat([tl_value, '@none'].compact)
    end
    preferred_values << '@any'

    # if containers included `@language` and preferred_values includes something of the form language-tag_direction, add just the _direction part, to select terms that have that direction.
    if (lang_dir = preferred_values.detect { |v| v.include?('_') })
      preferred_values << ('_' + lang_dir.split('_').last)
    end

    if (p_term = select_term(iri, containers, tl, preferred_values))
      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 vocab && self.vocab && iri.start_with?(self.vocab) && iri.length > self.vocab.length
    suffix = iri[self.vocab.length..]
    return suffix unless term_definitions.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|
    # Skip term if `@prefix` is not true in term definition
    next unless td&.prefix?

    next if td&.id.nil? || td.id == iri || !td.match_iri?(iri)

    suffix = iri[td.id.length..]
    ciri = "#{term}:#{suffix}"
    candidates << ciri unless value && term_definitions.key?(ciri)
  end

  return candidates.min unless candidates.empty?

  # 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) && iri != 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

    return candidates.min unless candidates.empty?
  end

  # If iri could be confused with a compact IRI using a term in this context, signal an error
  term_definitions.each do |term, td|
    next unless td.prefix? && td.match_compact_iri?(iri)

    raise JSON::LD::JsonLdError::IRIConfusedWithPrefix, "Absolute IRI '#{iri}' confused with prefix '#{term}'"
  end

  return iri if vocab

  # transform iri to a relative IRI using the document's base IRI
  iri = remove_base(self.base || base, iri)
  # Make . relative if it has the form of a keyword.
  iri = "./#{iri}" if iri.match?(/^@[a-zA-Z]+$/)

  iri
end

#compact_value(property, value, base: nil) ⇒ 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

  • base (String, RDF::URI) (defaults to: nil)

    for resolving document-relative IRIs

Returns:

  • (Hash)

    Object representation of value

Raises:

See Also:



1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
# File 'lib/json/ld/context.rb', line 1660

def compact_value(property, value, base: nil)
  # log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}

  indexing = index?(value) && container(property).include?('@index')
  language = language(property)
  direction = direction(property)

  result = if coerce(property) == '@id' && value.key?('@id') && (value.keys - %w[@id @index]).empty?
    # Compact an @id coercion
    # log_debug("") {" (@id & coerce)"}
    compact_iri(value['@id'], base: base)
  elsif coerce(property) == '@vocab' && value.key?('@id') && (value.keys - %w[@id @index]).empty?
    # Compact an @id coercion
    # log_debug("") {" (@id & coerce & vocab)"}
    compact_iri(value['@id'], vocab: true)
  elsif value.key?('@id')
    # log_debug("") {" (@id)"}
    # return value as is
    value
  elsif value['@type'] && value['@type'] == coerce(property)
    # Compact common datatype
    # log_debug("") {" (@type & coerce) == #{coerce(property)}"}
    value['@value']
  elsif coerce(property) == '@none' || value['@type']
    # use original expanded value
    value
  elsif !value['@value'].is_a?(String)
    # log_debug("") {" (native)"}
    indexing || !index?(value) ? value['@value'] : value
  elsif value['@language'].to_s.casecmp(language.to_s).zero? && value['@direction'] == direction
    # Compact language and direction
    indexing || !index?(value) ? value['@value'] : value
  else
    value
  end

  if result.is_a?(Hash) && result.key?('@type') && value['@type'] != '@json'
    # Compact values of @type
    c_type = if result['@type'].is_a?(Array)
      result['@type'].map { |t| compact_iri(t, vocab: true) }
    else
      compact_iri(result['@type'], vocab: true)
    end
    result = result.merge('@type' => c_type)
  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 }
    # log_debug("") {" (map to key aliases)"}
    new_element = {}
    result.each do |k, v|
      new_element[self.alias(k)] = v
    end
    result = new_element
  end

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

#container(term) ⇒ Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>

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

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (Array<'@index', '@language', '@index', '@set', '@type', '@id', '@graph'>)


1156
1157
1158
1159
1160
1161
# File 'lib/json/ld/context.rb', line 1156

def container(term)
  return Set[term] if term == '@list'

  term = find_definition(term)
  term ? term.container_mapping : Set.new
end

#content(term) ⇒ Hash

Retrieve content of a term

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (Hash)


1194
1195
1196
1197
# File 'lib/json/ld/context.rb', line 1194

def content(term)
  term = find_definition(term)
  term&.content
end

#create_term_definition(local_context, term, defined, base: nil, override_protected: false, protected: nil, remote_contexts: [], validate_scoped: true) ⇒ 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)
  • base (String, RDF::URI) (defaults to: nil)

    for resolving document-relative IRIs

  • protected (Boolean) (defaults to: nil)

    if true, causes all terms to be marked protected

  • override_protected (Boolean) (defaults to: false)

    Protected terms may be cleared.

  • remote_contexts (Array<String>) (defaults to: [])
  • validate_scoped (Boolean) (defaults to: true)

    (true). Validate scoped context, loading if necessary. If false, do not load scoped contexts.

Raises:

  • (JsonLdError)

    Represents a cyclical term dependency

See Also:



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

def create_term_definition(local_context, term, defined,
                           base: nil,
                           override_protected: false,
                           protected: nil,
                           remote_contexts: [],
                           validate_scoped: true)
  # Expand a string value, unless it matches a keyword
  # log_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 JsonLdError::CyclicIRIMapping, "Cyclical term dependency found: #{term.inspect}"
  end

  # Initialize value to a the value associated with the key term in local context.
  value = local_context.fetch(term, false)
  simple_term = value.is_a?(String) || value.nil?

  # Since keywords cannot be overridden, term must not be a keyword. Otherwise, an invalid value has been detected, which is an error.
  if term == '@type' &&
     value.is_a?(Hash) &&
     !value.empty? &&
     processingMode("json-ld-1.1") &&
     (value.keys - %w[@container @protected]).empty? &&
     value.fetch('@container', '@set') == '@set'
    # thes are the only cases were redefining a keyword is allowed
  elsif KEYWORDS.include?(term) # TODO: anything that looks like a keyword
    raise JsonLdError::KeywordRedefinition, "term must not be a keyword: #{term.inspect}" if
      @options[:validate]
  elsif term.to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
    warn "Terms beginning with '@' are reserved for future use and ignored: #{term}."
    return
  elsif !term_valid?(term) && @options[:validate]
    raise JsonLdError::InvalidTermDefinition, "term is invalid: #{term.inspect}"
  end

  value = { '@id' => value } if simple_term

  # Remove any existing term definition for term in active context.
  previous_definition = term_definitions[term]
  if previous_definition&.protected? && !override_protected
    # Check later to detect identical redefinition
  elsif previous_definition
    term_definitions.delete(term)
  end

  unless value.is_a?(Hash)
    raise JsonLdError::InvalidTermDefinition,
      "Term definition for #{term.inspect} is an #{value.class} on term #{term.inspect}"
  end

  # log_debug("") {"Hash[#{term.inspect}] = #{value.inspect}"}
  definition = TermDefinition.new(term)
  definition.simple = simple_term

  expected_keys = case processingMode
  when "json-ld-1.0" then JSON_LD_10_EXPECTED_KEYS
  else JSON_LD_EXPECTED_KEYS
  end

  # Any of these keys cause us to process as json-ld-1.1, unless otherwise set
  if processingMode.nil? && value.any? { |key, _| !JSON_LD_11_EXPECTED_KEYS.include?(key) }
    processingMode('json-ld-11')
  end

  if value.any? { |key, _| !expected_keys.include?(key) }
    extra_keys = value.keys - expected_keys.to_a
    raise JsonLdError::InvalidTermDefinition,
      "Term definition for #{term.inspect} has unexpected keys: #{extra_keys.join(', ')}"
  end

  # Potentially note that the term is protected
  definition.protected = value.fetch('@protected', protected)

  if value.key?('@type')
    type = value['@type']
    # SPEC FIXME: @type may be nil
    type = case type
    when nil
      type
    when String
      begin
        expand_iri(type, vocab: true, documentRelative: false, local_context: local_context, defined: defined)
      rescue JsonLdError::InvalidIRIMapping
        raise JsonLdError::InvalidTypeMapping,
          "invalid mapping for '@type': #{type.inspect} on term #{term.inspect}"
      end
    else
      :error
    end
    if JSON_LD_11_TYPE_VALUES.include?(type) && processingMode('json-ld-1.1')
      # This is okay and used in compaction in 1.1
    elsif !JSON_LD_10_TYPE_VALUES.include?(type) && !(type.is_a?(RDF::URI) && type.absolute?)
      raise JsonLdError::InvalidTypeMapping,
        "unknown mapping for '@type': #{type.inspect} on term #{term.inspect}"
    end
    # log_debug("") {"type_mapping: #{type.inspect}"}
    definition.type_mapping = type
  end

  if value.key?('@reverse')
    raise JsonLdError::InvalidReverseProperty, "unexpected key in #{value.inspect} on term #{term.inspect}" if
      value.key?('@id') || value.key?('@nest')

    unless value['@reverse'].is_a?(String)
      raise JsonLdError::InvalidIRIMapping,
        "expected value of @reverse to be a string: #{value['@reverse'].inspect} on term #{term.inspect}"
    end

    if value['@reverse'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
      warn "Values beginning with '@' are reserved for future use and ignored: #{value['@reverse']}."
      return
    end

    # 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,
      local_context: local_context,
      defined: defined)
    unless definition.id.is_a?(RDF::Node) || (definition.id.is_a?(RDF::URI) && definition.id.absolute?)
      raise JsonLdError::InvalidIRIMapping,
        "non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}"
    end

    if term[1..].to_s.include?(':') && (term_iri = expand_iri(term)) != definition.id
      raise JsonLdError::InvalidIRIMapping, "term #{term} expands to #{definition.id}, not #{term_iri}"
    end

    if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
      warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1."
    end

    # If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, @index, @type, @id, an absolute IRI nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
    if value.key?('@container')
      container = value['@container']
      unless container.is_a?(String) && ['@set', '@index'].include?(container)
        raise JsonLdError::InvalidReverseProperty,
          "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
      end
      definition.container_mapping = check_container(container, local_context, defined, term)
    end
    definition.reverse_property = true
  elsif value.key?('@id') && value['@id'].nil?
    # Allowed to reserve a null term, which may be protected
  elsif value.key?('@id') && value['@id'] != term
    unless value['@id'].is_a?(String)
      raise JsonLdError::InvalidIRIMapping,
        "expected value of @id to be a string: #{value['@id'].inspect} on term #{term.inspect}"
    end

    if !KEYWORDS.include?(value['@id'].to_s) && value['@id'].to_s.match?(/^@[a-zA-Z]+$/) && @options[:validate]
      warn "Values beginning with '@' are reserved for future use and ignored: #{value['@id']}."
      return
    end

    definition.id = expand_iri(value['@id'],
      vocab: true,
      local_context: local_context,
      defined: defined)
    raise JsonLdError::InvalidKeywordAlias, "expected value of @id to not be @context on term #{term.inspect}" if
      definition.id == '@context'

    if term.match?(%r{(?::[^:])|/})
      term_iri = expand_iri(term,
        vocab: true,
        local_context: local_context,
        defined: defined.merge(term => true))
      if term_iri != definition.id
        raise JsonLdError::InvalidIRIMapping, "term #{term} expands to #{definition.id}, not #{term_iri}"
      end
    end

    if @options[:validate] && processingMode('json-ld-1.1') && definition.id.to_s.start_with?("_:")
      warn "[DEPRECATION] Blank Node terms deprecated in JSON-LD 1.1."
    end

    # If id ends with a gen-delim, it may be used as a prefix for simple terms
    definition.prefix = true if !term.include?(':') &&
                                simple_term &&
                                (definition.id.to_s.end_with?(':', '/', '?', '#', '[', ']',
                                  '@') || definition.id.to_s.start_with?('_:'))
  elsif term[1..].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(':', 2)
    create_term_definition(local_context, prefix, defined, protected: protected) if local_context.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
    # log_debug("") {"=> #{definition.id}"}
  elsif term.include?('/')
    # If term is a relative IRI
    definition.id = expand_iri(term, vocab: true)
    raise JsonLdError::InvalidKeywordAlias, "expected term to expand to an absolute IRI #{term.inspect}" unless
      definition.id.absolute?
  elsif KEYWORDS.include?(term)
    # This should only happen for @type when @container is @set
    definition.id = term
  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.
    unless vocab
      raise JsonLdError::InvalidIRIMapping,
        "relative term definition without vocab: #{term} on term #{term.inspect}"
    end

    definition.id = vocab + term
    # log_debug("") {"=> #{definition.id}"}
  end

  @iri_to_term[definition.id] = term if simple_term && definition.id

  if value.key?('@container')
    # log_debug("") {"container_mapping: #{value['@container'].inspect}"}
    definition.container_mapping = check_container(value['@container'], local_context, defined, term)

    # If @container includes @type
    if definition.container_mapping.include?('@type')
      # If definition does not have @type, set @type to @id
      definition.type_mapping ||= '@id'
      # If definition includes @type with a value other than @id or @vocab, an illegal type mapping error has been detected
      unless CONTEXT_TYPE_ID_VOCAB.include?(definition.type_mapping)
        raise JsonLdError::InvalidTypeMapping, "@container: @type requires @type to be @id or @vocab"
      end
    end
  end

  if value.key?('@index')
    # property-based indexing
    unless definition.container_mapping.include?('@index')
      raise JsonLdError::InvalidTermDefinition,
        "@index without @index in @container: #{value['@index']} on term #{term.inspect}"
    end
    unless value['@index'].is_a?(String) && !value['@index'].start_with?('@')
      raise JsonLdError::InvalidTermDefinition,
        "@index must expand to an IRI: #{value['@index']} on term #{term.inspect}"
    end

    definition.index = value['@index'].to_s
  end

  if value.key?('@context')
    begin
      new_ctx = parse(value['@context'],
        base: base,
        override_protected: true,
        remote_contexts: remote_contexts,
        validate_scoped: false)
      # Record null context in array form
      definition.context = case value['@context']
      when String then new_ctx.context_base
      when nil then [nil]
      else value['@context']
      end
      # log_debug("") {"context: #{definition.context.inspect}"}
    rescue JsonLdError => e
      raise JsonLdError::InvalidScopedContext,
        "Term definition for #{term.inspect} contains illegal value for @context: #{e.message}"
    end
  end

  if value.key?('@language')
    language = value['@language']
    language = case value['@language']
    when String
      # Warn on an invalid language tag, unless :validate is true, in which case it's an error
      unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(value['@language'])
        warn "@language must be valid BCP47: #{value['@language'].inspect}"
      end
      options[:lowercaseLanguage] ? value['@language'].downcase : value['@language']
    when nil
      nil
    else
      raise JsonLdError::InvalidLanguageMapping,
        "language must be null or a string, was #{value['@language'].inspect}} on term #{term.inspect}"
    end
    # log_debug("") {"language_mapping: #{language.inspect}"}
    definition.language_mapping = language || false
  end

  if value.key?('@direction')
    direction = value['@direction']
    unless direction.nil? || %w[
      ltr rtl
    ].include?(direction)
      raise JsonLdError::InvalidBaseDirection,
        "direction must be null, 'ltr', or 'rtl', was #{language.inspect}} on term #{term.inspect}"
    end

    # log_debug("") {"direction_mapping: #{direction.inspect}"}
    definition.direction_mapping = direction || false
  end

  if value.key?('@nest')
    nest = value['@nest']
    unless nest.is_a?(String)
      raise JsonLdError::InvalidNestValue,
        "nest must be a string, was #{nest.inspect}} on term #{term.inspect}"
    end
    if nest.match?(/^@[a-zA-Z]+$/) && nest != '@nest'
      raise JsonLdError::InvalidNestValue,
        "nest must not be a keyword other than @nest, was #{nest.inspect}} on term #{term.inspect}"
    end

    # log_debug("") {"nest: #{nest.inspect}"}
    definition.nest = nest
  end

  if value.key?('@prefix')
    if term.match?(%r{:|/})
      raise JsonLdError::InvalidTermDefinition,
        "@prefix used on compact or relative IRI term #{term.inspect}"
    end

    case pfx = value['@prefix']
    when TrueClass, FalseClass
      definition.prefix = pfx
    else
      raise JsonLdError::InvalidPrefixValue, "unknown value for '@prefix': #{pfx.inspect} on term #{term.inspect}"
    end

    if pfx && KEYWORDS.include?(definition.id.to_s)
      raise JsonLdError::InvalidTermDefinition,
        "keywords may not be used as prefixes"
    end
  end

  if previous_definition&.protected? && definition != previous_definition && !override_protected
    definition = previous_definition
    raise JSON::LD::JsonLdError::ProtectedTermRedefinition, "Attempt to redefine protected term #{term}"
  end

  term_definitions[term] = definition
  defined[term] = true
end

#direction(term) ⇒ String

Retrieve the text direction associated with a term, or the default direction otherwise

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (String)


1241
1242
1243
1244
1245
1246
1247
1248
1249
# File 'lib/json/ld/context.rb', line 1241

def direction(term)
  term = find_definition(term)
  dir = term&.direction_mapping
  if dir.nil?
    @default_direction
  else
    (dir == false ? nil : dir)
  end
end

#dupObject

Duplicate an active context, allowing it to be modified.



1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
# File 'lib/json/ld/context.rb', line 1761

def dup
  that = self
  ec = Context.new(unfrozen: true, **@options)
  ec.context_base = that.context_base
  ec.base = that.base unless that.base.nil?
  ec.default_direction = that.default_direction
  ec.default_language = that.default_language
  ec.previous_context = that.previous_context
  ec.processingMode = that.processingMode if that.instance_variable_get(:@processingMode)
  ec.vocab = that.vocab if that.vocab

  ec.instance_eval do
    @term_definitions = that.term_definitions.dup
    @iri_to_term = that.iri_to_term
  end
  ec
end

#empty?Boolean

Initial context, without mappings, vocab or default language

Returns:

  • (Boolean)


859
860
861
# File 'lib/json/ld/context.rb', line 859

def empty?
  @term_definitions.empty? && vocab.nil? && default_language.nil?
end

#expand_iri(value, as_string: false, base: nil, defined: nil, documentRelative: false, local_context: nil, vocab: false, **_options) ⇒ RDF::Resource, String

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

Parameters:

  • value (String)

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

  • as_string (Boolean) (defaults to: false)

    (false) transform RDF::Resource values to string

  • base (String, RDF::URI) (defaults to: nil)

    for resolving document-relative IRIs

  • defined (Hash) (defaults to: nil)

    Used during Context Processing.

  • documentRelative (Boolean) (defaults to: false)

    (false)

  • local_context (Hash) (defaults to: nil)

    Used during Context Processing.

  • vocab (Boolean) (defaults to: false)

    (false)

  • options (Hash{Symbol => Object})

Returns:

  • (RDF::Resource, String)

    IRI or String, if it’s a keyword

Raises:

See Also:



1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
# File 'lib/json/ld/context.rb', line 1299

def expand_iri(value,
               as_string: false,
               base: nil,
               defined: nil,
               documentRelative: false,
               local_context: nil,
               vocab: false,
               **_options)
  return (value && as_string ? value.to_s : value) unless value.is_a?(String)

  return value if KEYWORDS.include?(value)
  return nil if value.match?(/^@[a-zA-Z]+$/)

  defined ||= {} # if we initialized in the keyword arg we would allocate {} at each invokation, even in the 2 (common) early returns above.

  # 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.
  create_term_definition(local_context, value, defined) if local_context&.key?(value) && !defined[value]

  if (v_td = term_definitions[value]) && KEYWORDS.include?(v_td.id)
    return (as_string ? v_td.id.to_s : v_td.id)
  end

  # If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
  # If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
  if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
    iri = base && v_td.id ? base.join(v_td.id) : v_td.id # vocab might be doc relative
    return (as_string ? iri.to_s : iri)
  end

  # If value contains a colon (:), it is either an absolute IRI or a compact IRI:
  if value[1..].to_s.include?(':')
    prefix, suffix = value.split(':', 2)

    # 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.
    if prefix == '_'
      v = RDF::Node.new(namer.get_sym(suffix))
      return (as_string ? v.to_s : v)
    end
    if suffix.start_with?('//')
      v = RDF::URI(value)
      return (as_string ? v.to_s : v)
    end

    # 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.
    create_term_definition(local_context, prefix, defined) if local_context&.key?(prefix) && !defined[prefix]

    # If active context contains a term definition for prefix, return the result of concatenating the IRI mapping associated with prefix and suffix.
    if (td = term_definitions[prefix]) && !td.id.nil? && td.prefix?
      return (as_string ? td.id.to_s : td.id) + suffix
    elsif RDF::URI(value).absolute?
      # Otherwise, if the value has the form of an absolute IRI, return it
      return (as_string ? value.to_s : RDF::URI(value))
    end
  end

  iri = value.is_a?(RDF::URI) ? value : RDF::URI(value)
  result = if vocab && self.vocab
    # If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
    # Note that @vocab could still be relative to a document base
    (base && self.vocab.is_a?(RDF::URI) && self.vocab.relative? ? base.join(self.vocab) : self.vocab) + value
  elsif documentRelative
    if iri.absolute?
      iri
    elsif self.base.is_a?(RDF::URI) && self.base.absolute?
      self.base.join(iri)
    elsif self.base == false
      # No resollution of `@base: null`
      iri
    elsif base && self.base
      base.join(self.base).join(iri)
    elsif base
      base.join(iri)
    else
      # Returns a relative IRI in an odd case.
      iri
    end
  elsif local_context && iri.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::JsonLdError::InvalidIRIMapping, "not an absolute IRI: #{value}"
  else
    iri
  end
  result && as_string ? result.to_s : result
end

#expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **_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

  • useNativeTypes (Boolean) (defaults to: false)

    (false) use native representations

  • rdfDirection (Boolean) (defaults to: nil)

    (nil) decode i18n datatype if i18n-datatype

  • base (String, RDF::URI) (defaults to: nil)

    for resolving document-relative IRIs

  • options (Hash{Symbol => Object})

Returns:

  • (Hash)

    Object representation of value

Raises:

  • (RDF::ReaderError)

    if the iri cannot be expanded

See Also:



1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
# File 'lib/json/ld/context.rb', line 1610

def expand_value(property, value, useNativeTypes: false, rdfDirection: nil, base: nil, **_options)
  td = term_definitions.fetch(property, TermDefinition.new(property))

  # 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 value.is_a?(String) && td.type_mapping == '@id'
    # log_debug("") {"as relative IRI: #{value.inspect}"}
    return { '@id' => expand_iri(value, documentRelative: true, base: base).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 value.is_a?(String) && td.type_mapping == '@vocab'
    return { '@id' => expand_iri(value, vocab: true, documentRelative: true, base: base).to_s }
  end

  case value
  when RDF::URI, RDF::Node
    { '@id' => value.to_s }
  when Date, DateTime, Time
    lit = RDF::Literal.new(value)
    { '@value' => lit.to_s, '@type' => lit.datatype.to_s }
  else
    # Otherwise, initialize result to a JSON object with an @value member whose value is set to value.
    res = {}

    if td.type_mapping && !CONTAINERS_ID_VOCAB.include?(td.type_mapping.to_s)
      res['@type'] = td.type_mapping.to_s
    elsif value.is_a?(String)
      language = language(property)
      direction = direction(property)
      res['@language'] = language if language
      res['@direction'] = direction if direction
    end

    res.merge('@value' => value)
  end
end

#find_definition(term) ⇒ Term

Find a term definition

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (Term)


1147
1148
1149
# File 'lib/json/ld/context.rb', line 1147

def find_definition(term)
  term.is_a?(TermDefinition) ? term : term_definitions[term.to_s]
end

#from_vocabulary(graph) ⇒ self

Note:

requires rdf/vocab gem.

Build a context from an RDF::Vocabulary definition.

Examples:

building from an external vocabulary definition


g = RDF::Graph.load("http://schema.org/docs/schema_org_rdfa.html")

context = JSON::LD::Context.new.from_vocabulary(g,
      vocab: "http://schema.org/",
      prefixes: {schema: "http://schema.org/"},
      language: "en")

Parameters:

  • graph (RDF::Queryable)

Returns:

  • (self)


1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
# File 'lib/json/ld/context.rb', line 1060

def from_vocabulary(graph)
  require 'rdf/vocab' unless RDF.const_defined?(:Vocab)
  statements = {}
  ranges = {}

  # Add term definitions for each class and property not in schema:, and
  # for those properties having an object range
  graph.each do |statement|
    next if statement.subject.node?

    (statements[statement.subject] ||= []) << statement

    # Keep track of predicate ranges
    if [RDF::RDFS.range, RDF::Vocab::SCHEMA.rangeIncludes].include?(statement.predicate)
      (ranges[statement.subject] ||= []) << statement.object
    end
  end

  # Add term definitions for each class and property not in vocab, and
  # for those properties having an object range
  statements.each do |subject, values|
    types = values.each_with_object([]) { |v, memo| memo << v.object if v.predicate == RDF.type }
    is_property = types.any? { |t| t.to_s.include?("Property") }

    term = subject.to_s.split(%r{[/\#]}).last

    if is_property
      prop_ranges = ranges.fetch(subject, [])
      # If any range is empty or member of range includes rdfs:Literal or schema:Text
      next if (vocab && prop_ranges.empty?) ||
              prop_ranges.include?(RDF::Vocab::SCHEMA.Text) ||
              prop_ranges.include?(RDF::RDFS.Literal)

      td = term_definitions[term] = TermDefinition.new(term, id: subject.to_s)

      # Set context typing based on first element in range
      case r = prop_ranges.first
      when RDF::XSD.string
        td.language_mapping = false if default_language
        # FIXME: text direction
      when RDF::XSD.boolean, RDF::Vocab::SCHEMA.Boolean, RDF::XSD.date, RDF::Vocab::SCHEMA.Date,
        RDF::XSD.dateTime, RDF::Vocab::SCHEMA.DateTime, RDF::XSD.time, RDF::Vocab::SCHEMA.Time,
        RDF::XSD.duration, RDF::Vocab::SCHEMA.Duration, RDF::XSD.decimal, RDF::Vocab::SCHEMA.Number,
        RDF::XSD.float, RDF::Vocab::SCHEMA.Float, RDF::XSD.integer, RDF::Vocab::SCHEMA.Integer
        td.type_mapping = r
        td.simple = false
      else
        # It's an object range (includes schema:URL)
        td.type_mapping = '@id'
      end
    else
      # Ignore if there's a default voabulary and this is not a property
      next if vocab && subject.to_s.start_with?(vocab)

      # otherwise, create a term definition
      td = term_definitions[term] = TermDefinition.new(term, id: subject.to_s)
    end
  end

  self
end

#inspectObject



1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
# File 'lib/json/ld/context.rb', line 1748

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

#language(term) ⇒ String

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

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (String)


1227
1228
1229
1230
1231
1232
1233
1234
1235
# File 'lib/json/ld/context.rb', line 1227

def language(term)
  term = find_definition(term)
  lang = term&.language_mapping
  if lang.nil?
    @default_language
  else
    (lang == false ? nil : lang)
  end
end

#merge(context, override_protected: false) ⇒ Context

Merge in a context, creating a new context with updates from ‘context`

Parameters:

  • context (Context)
  • override_protected (Boolean) (defaults to: false)

    Allow or disallow protected terms to be changed

Returns:



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/json/ld/context.rb', line 459

def merge(context, override_protected: false)
  ctx = Context.new(term_definitions: term_definitions, standard_prefixes: options[:standard_prefixes])
  ctx.context_base = context.context_base || context_base
  ctx.default_language = context.default_language || default_language
  ctx.default_direction = context.default_direction || default_direction
  ctx.vocab = context.vocab || vocab
  ctx.base = base unless base.nil?
  unless override_protected
    ctx.term_definitions.each do |term, definition|
      next unless definition.protected? && (other = context.term_definitions[term])
      unless definition == other
        raise JSON::LD::JsonLdError::ProtectedTermRedefinition, "Attempt to redefine protected term #{term}"
      end
    end
  end

  # Add term definitions
  context.term_definitions.each do |term, definition|
    ctx.term_definitions[term] = definition
  end
  ctx
end

#nest(term) ⇒ String

Retrieve nest of a term. value of nest must be @nest or a term that resolves to @nest

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (String)

    Nesting term

Raises:

  • JsonLdError::InvalidNestValue if nesting term exists and is not a term resolving to ‘@nest` in the current context.



1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
# File 'lib/json/ld/context.rb', line 1206

def nest(term)
  term = find_definition(term)
  return unless term

  case term.nest
  when '@nest', nil
  else
    nest_term = find_definition(term.nest)
    unless nest_term && nest_term.id == '@nest'
      raise JsonLdError::InvalidNestValue,
        "nest must a term resolving to @nest, was #{nest_term.inspect}"
    end

  end
  term.nest
end

#parse(local_context, base: nil, override_protected: false, propagate: true, remote_contexts: [], validate_scoped: true) ⇒ Context

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:

  • local_context (String, #read, Array, Hash, Context)
  • base (String, #to_s) (defaults to: nil)

    The Base IRI to use when expanding the document. This overrides the value of ‘input` if it is a IRI. If not specified and `input` is not an IRI, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context.

  • override_protected (Boolean) (defaults to: false)

    Protected terms may be cleared.

  • propagate (Boolean) (defaults to: true)

    (true) If false, retains any previously defined term, which can be rolled back when the descending into a new node object changes.

  • remote_contexts (Array<String>) (defaults to: [])

    ([])

  • validate_scoped (Boolean) (defaults to: true)

    (true). Validate scoped context, loading if necessary. If false, do not load scoped contexts.

Returns:

Raises:

  • (JsonLdError)

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

See Also:



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
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
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
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/json/ld/context.rb', line 243

def parse(local_context,
          base: nil,
          override_protected: false,
          propagate: true,
          remote_contexts: [],
          validate_scoped: true)
  result = dup
  # Early check for @propagate, which can only appear in a local context
  propagate = local_context.is_a?(Hash) ? local_context.fetch('@propagate', propagate) : propagate
  result.previous_context ||= result.dup unless propagate

  local_context = as_array(local_context)

  log_depth do
    local_context.each do |context|
      case context
      when nil, false
        # 3.1 If the `override_protected` is  false, and the active context contains protected terms, an error is raised.
        if override_protected || result.term_definitions.values.none?(&:protected?)
          null_context = Context.new(**options)
          null_context.previous_context = result unless propagate
          result = null_context
        else
          raise JSON::LD::JsonLdError::InvalidContextNullification,
            "Attempt to clear a context with protected terms"
        end
      when Context
        # log_debug("parse") {"context: #{context.inspect}"}
        result = result.merge(context)
      when IO, StringIO
        # log_debug("parse") {"io: #{context}"}
        # Load context document, if it is an open file
        begin
          ctx = load_context(context, **@options)
          if @options[:validate] && ctx['@context'].nil?
            raise JSON::LD::JsonLdError::InvalidRemoteContext,
              "Context missing @context key"
          end

          result = result.parse(ctx["@context"] || {})
        rescue JSON::ParserError => e
          log_info("parse") { "Failed to parse @context from remote document at #{context}: #{e.message}" }
          if @options[:validate]
            raise JSON::LD::JsonLdError::InvalidRemoteContext,
              "Failed to parse remote context at #{context}: #{e.message}"
          end

          self
        end
      when String, RDF::URI
        # log_debug("parse") {"remote: #{context}, base: #{result.context_base || result.base}"}

        # 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 || base).join(context)
        context_canon = context.canonicalize
        context_canon.scheme = 'http' if context_canon.scheme == 'https'

        # If validating a scoped context which has already been loaded, skip to the next one
        next if !validate_scoped && remote_contexts.include?(context.to_s)

        remote_contexts << context.to_s
        raise JsonLdError::ContextOverflow, context.to_s if remote_contexts.length >= MAX_CONTEXTS_LOADED

        cached_context = if PRELOADED[context_canon.to_s]
          # If we have a cached context, merge it into the current context (result) and use as the new context
          # log_debug("parse") {"=> cached_context: #{context_canon.to_s.inspect}"}

          # If this is a Proc, then replace the entry with the result of running the Proc
          if PRELOADED[context_canon.to_s].respond_to?(:call)
            # log_debug("parse") {"=> (call)"}
            PRELOADED[context_canon.to_s] = PRELOADED[context_canon.to_s].call
          end
          PRELOADED[context_canon.to_s].context_base ||= context_canon.to_s
          PRELOADED[context_canon.to_s]
        else
          # Load context document, if it is a string
          Context.cache[context_canon.to_s] ||= begin
            context_opts = @options.merge(
              profile: 'http://www.w3.org/ns/json-ld#context',
              requestProfile: 'http://www.w3.org/ns/json-ld#context',
              base: nil
            )
            # context_opts.delete(:headers)
            JSON::LD::API.loadRemoteDocument(context.to_s, **context_opts) do |remote_doc|
              # 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.
              unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
                raise JsonLdError::InvalidRemoteContext,
                  context.to_s
              end

              # Parse stand-alone
              ctx = Context.new(unfrozen: true, **options).dup
              ctx.context_base = context.to_s
              ctx = ctx.parse(remote_doc.document['@context'], remote_contexts: remote_contexts.dup)
              ctx.context_base = context.to_s # In case it was altered
              ctx.instance_variable_set(:@base, nil)
              ctx
            end
          rescue JsonLdError::LoadingDocumentFailed => e
            log_info("parse") do
              "Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"
            end
            raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
          rescue JsonLdError
            raise
          rescue StandardError => e
            log_info("parse") do
              "Failed to retrieve @context from remote document at #{context_canon.inspect}: #{e.message}"
            end
            raise JsonLdError::LoadingRemoteContextFailed, "#{context}: #{e.message}", e.backtrace
          end
        end

        # Merge loaded context noting protected term overriding
        context = result.merge(cached_context, override_protected: override_protected)

        context.previous_context = self unless propagate
        result = context
      when Hash
        context = context.dup # keep from modifying a hash passed as a param

        # This counts on hash elements being processed in order
        {
          '@version' => :processingMode=,
          '@import' => nil,
          '@base' => :base=,
          '@direction' => :default_direction=,
          '@language' => :default_language=,
          '@propagate' => :propagate=,
          '@vocab' => :vocab=
        }.each do |key, setter|
          next unless context.key?(key)

          if key == '@import'
            # Retrieve remote context and merge the remaining context object into the result.
            if result.processingMode("json-ld-1.0")
              raise JsonLdError::InvalidContextEntry,
                "@import may only be used in 1.1 mode}"
            end
            unless context['@import'].is_a?(String)
              raise JsonLdError::InvalidImportValue,
                "@import must be a string: #{context['@import'].inspect}"
            end

            import_loc = RDF::URI(result.context_base || base).join(context['@import'])
            begin
              context_opts = @options.merge(
                profile: 'http://www.w3.org/ns/json-ld#context',
                requestProfile: 'http://www.w3.org/ns/json-ld#context',
                base: nil
              )
              context_opts.delete(:headers)
              # FIXME: should cache this, but ContextCache is for parsed contexts
              JSON::LD::API.loadRemoteDocument(import_loc, **context_opts) do |remote_doc|
                # Dereference import_loc. 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.
                unless remote_doc.document.is_a?(Hash) && remote_doc.document.key?('@context')
                  raise JsonLdError::InvalidRemoteContext,
                    import_loc.to_s
                end

                import_context = remote_doc.document['@context']
                import_context.delete('@base')
                unless import_context.is_a?(Hash)
                  raise JsonLdError::InvalidRemoteContext,
                    "#{import_context.to_json} must be an object"
                end
                if import_context.key?('@import')
                  raise JsonLdError::InvalidContextEntry,
                    "#{import_context.to_json} must not include @import entry"
                end

                context.delete(key)
                context = import_context.merge(context)
              end
            rescue JsonLdError::LoadingDocumentFailed => e
              raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
            rescue JsonLdError
              raise
            rescue StandardError => e
              raise JsonLdError::LoadingRemoteContextFailed, "#{import_loc}: #{e.message}", e.backtrace
            end
          else
            result.send(setter, context[key], remote_contexts: remote_contexts)
          end
          context.delete(key)
        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
        context.each_key do |key|
          # ... where key is not @base, @vocab, @language, or @version
          next if NON_TERMDEF_KEYS.include?(key)

          result.create_term_definition(context, key, defined,
            base: base,
            override_protected: override_protected,
            protected: context['@protected'],
            remote_contexts: remote_contexts.dup,
            validate_scoped: validate_scoped)
        end
      else
        # 3.3) If context is not a JSON object, an invalid local context error has been detected and processing is aborted.
        raise JsonLdError::InvalidLocalContext, "must be a URL, JSON object or array of same: #{context.inspect}"
      end
    end
  end
  result
end

#processingMode(expected = nil) ⇒ String

Retrieve, or check processing mode.

  • With no arguments, retrieves the current set processingMode.

  • With an argument, verifies that the processingMode is at least that provided, either as an integer, or a string of the form “json-ld-1.x”

  • If expecting 1.1, and not set, it has the side-effect of setting mode to json-ld-1.1.

Parameters:

  • expected (String, Number) (defaults to: nil)

    (nil)

Returns:

  • (String)


919
920
921
922
923
924
925
926
927
928
929
930
# File 'lib/json/ld/context.rb', line 919

def processingMode(expected = nil)
  case expected
  when 1.0, 'json-ld-1.0'
    @processingMode == 'json-ld-1.0'
  when 1.1, 'json-ld-1.1'
    @processingMode.nil? || @processingMode == 'json-ld-1.1'
  when nil
    @processingMode || 'json-ld-1.1'
  else
    false
  end
end

#processingMode=(value = nil, **_options) ⇒ String

Set processing mode.

  • With an argument, verifies that the processingMode is at least that provided, either as an integer, or a string of the form “json-ld-1.x”

If contex has a @version member, it’s value MUST be 1.1, otherwise an “invalid @version value” has been detected, and processing is aborted. If processingMode has been set, and it is not “json-ld-1.1”, a “processing mode conflict” has been detecting, and processing is aborted.

Parameters:

  • value (String, Number) (defaults to: nil)

Returns:

  • (String)

Raises:



943
944
945
946
947
948
949
950
951
952
953
954
955
# File 'lib/json/ld/context.rb', line 943

def processingMode=(value = nil, **_options)
  value = "json-ld-1.1" if value == 1.1
  case value
  when "json-ld-1.0", "json-ld-1.1"
    if @processingMode && @processingMode != value
      raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{@processingMode}"
    end

    @processingMode = value
  else
    raise JsonLdError::InvalidVersionValue, value.inspect
  end
end

#propagate=(value, **_options) ⇒ Object

Set propagation @note: by the time this is called, the work has already been done.

Parameters:

  • value (Boolean)


984
985
986
987
988
989
990
991
992
993
994
995
996
# File 'lib/json/ld/context.rb', line 984

def propagate=(value, **_options)
  if processingMode("json-ld-1.0")
    raise JsonLdError::InvalidContextEntry,
      "@propagate may only be set in 1.1 mode"
  end

  unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
    raise JsonLdError::InvalidPropagateValue,
      "@propagate must be boolean valued: #{value.inspect}"
  end

  value
end

#reverse?(term) ⇒ Boolean

Is this a reverse term

Parameters:

  • term (Term, #to_s)

    in unexpanded form

Returns:

  • (Boolean)


1255
1256
1257
1258
# File 'lib/json/ld/context.rb', line 1255

def reverse?(term)
  term = find_definition(term)
  term&.reverse_property
end

#reverse_term(term) ⇒ Term

Given a term or IRI, find a reverse term definition matching that term. If the term is already reversed, find a non-reversed version.

Parameters:

  • term (Term, #to_s)

Returns:

  • (Term)

    related term definition



1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
# File 'lib/json/ld/context.rb', line 1265

def reverse_term(term)
  # Direct lookup of term
  term = term_definitions[term.to_s] if term_definitions.key?(term.to_s) && !term.is_a?(TermDefinition)

  # Lookup term, assuming term is an IRI
  unless term.is_a?(TermDefinition)
    td = term_definitions.values.detect { |t| t.id == term.to_s }

    # Otherwise create a temporary term definition
    term = td || TermDefinition.new(term.to_s, id: expand_iri(term, vocab: true))
  end

  # Now, return a term, which reverses this term
  term_definitions.values.detect { |t| t.id == term.id && t.reverse_property != term.reverse_property }
end

#serialize(provided_context: nil, **_options) ⇒ Hash

Generate @context

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

Parameters:

  • provided_context (Array, Hash, Context, IO, StringIO) (defaults to: nil)

    (nil) Original context to use, if available

  • options (Hash{Symbol => Object})

    ({})

Returns:

  • (Hash)


1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
# File 'lib/json/ld/context.rb', line 1008

def serialize(provided_context: nil, **_options)
  # log_debug("serlialize: generate context")
  # log_debug("") {"=> context: #{inspect}"}
  use_context = case provided_context
  when String, RDF::URI
    # log_debug "serlialize: reuse context: #{provided_context.inspect}"
    provided_context.to_s
  when Hash
    # log_debug "serlialize: reuse context: #{provided_context.inspect}"
    # If it has an @context entry use it, otherwise it is assumed to be the body of a context
    provided_context.fetch('@context', provided_context)
  when Array
    # log_debug "serlialize: reuse context: #{provided_context.inspect}"
    provided_context
  when IO, StringIO
    load_context(provided_context, **@options).fetch('@context', {})
  else
    ctx = {}
    ctx['@version'] = 1.1 if @processingMode == 'json-ld-1.1'
    ctx['@base'] = base.to_s if base
    ctx['@direction'] = default_direction.to_s if default_direction
    ctx['@language'] = default_language.to_s if default_language
    ctx['@vocab'] = vocab.to_s if vocab

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

  # Return hash with @context, or empty
  use_context.nil? || use_context.empty? ? {} : { '@context' => use_context }
end

#set_mapping(term, value) ⇒ TermDefinition

Set term mapping

Parameters:

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

Returns:



1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
# File 'lib/json/ld/context.rb', line 1128

def set_mapping(term, value)
  # log_debug("") {"map #{term.inspect} to #{value.inspect}"}
  term = term.to_s
  term_definitions[term] =
    TermDefinition.new(term, id: value, simple: true, prefix: value.to_s.end_with?(*PREFIX_URI_ENDINGS))
  term_definitions[term].simple = true

  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.key?(:prefixes)
  iri_to_term[value.to_s] = term
  term_definitions[term]
end

#to_rb(*aliases) ⇒ String

Turn this into a source for a new instantiation

Parameters:

  • aliases (Array<String>)

    Other URLs to alias when preloading

Returns:

  • (String)


1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
# File 'lib/json/ld/context.rb', line 1725

def to_rb(*aliases)
  canon_base = RDF::URI(context_base).canonicalize
  defn = []

  defn << "base: #{base.to_s.inspect}" if base
  defn << "language: #{default_language.inspect}" if default_language
  defn << "vocab: #{vocab.to_s.inspect}" if vocab
  defn << "processingMode: #{processingMode.inspect}" if processingMode
  term_defs = term_definitions.map do |term, td|
    "      " + term.inspect + " => " + td.to_rb
  end.sort
  defn << "term_definitions: {\n#{term_defs.join(",\n")}\n    }" unless term_defs.empty?
  %(# -*- encoding: utf-8 -*-
# frozen_string_literal: true
# This file generated automatically from #{context_base}
require 'json/ld'
class JSON::LD::Context
).gsub(/^      /, '') +
    %[  add_preloaded("#{canon_base}") do\n    new(] + defn.join(", ") + ")\n  end\n" +
    aliases.map { |a| %[  alias_preloaded("#{a}", "#{canon_base}")\n] }.join +
    "end\n"
end