Class: RDF::KV

Inherits:
Object
  • Object
show all
Defined in:
lib/rdf/kv/version.rb,
lib/rdf/kv.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
"0.1.8"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject: nil, graph: nil, prefixes: {}, callback: nil) ⇒ KV

Initialize the processor.

Parameters:

  • subject (RDF::URI) (defaults to: nil)

    The default subject. Required.

  • graph (RDF::URI) (defaults to: nil)

    The default context. Optional.

  • prefixes (Hash) (defaults to: {})

    Namespace/prefix mappings. Optional.

  • callback (#call) (defaults to: nil)

    A callback that expects and returns a term. Optional.

Raises:

  • (ArgumentError)


277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/rdf/kv.rb', line 277

def initialize subject: nil, graph: nil, prefixes: {}, callback: nil
  # look at all of our pretty assertions
  raise ArgumentError, 'subject must be an RDF::Resource' unless
    subject.is_a? RDF::Resource
  raise ArgumentError, 'graph must be an RDF::Resource' unless
    graph.nil? or graph.is_a? RDF::Resource
  raise ArgumentError, 'prefixes must be hashable' unless
    prefixes.respond_to? :to_h
  raise ArgumentError, 'callback must be callable' unless
    callback.nil? or callback.respond_to? :call

  @subject  = subject
  @graph    = graph
  @callback = callback
  @prefixes = DEFAULT_NS.merge(prefixes.to_h.map do |k, v|
    k = k.to_s.to_sym    unless k.is_a? Symbol
    # coerce to uri
    v = RDF::URI(v.to_s) unless v.is_a? RDF::Resource
    # now coerce to vocabulary
    v = RDF::Vocabulary.new v unless v.is_a? RDF::Vocabulary
    [k, v]
  end.to_h)
end

Instance Attribute Details

#callbackObject (readonly)

Returns the value of attribute callback.



265
266
267
# File 'lib/rdf/kv.rb', line 265

def callback
  @callback
end

#graphObject (readonly)

Returns the value of attribute graph.



265
266
267
# File 'lib/rdf/kv.rb', line 265

def graph
  @graph
end

#prefixesObject (readonly) Also known as: namespaces

Returns the value of attribute prefixes.



265
266
267
# File 'lib/rdf/kv.rb', line 265

def prefixes
  @prefixes
end

#subjectObject (readonly)

Returns the value of attribute subject.



265
266
267
# File 'lib/rdf/kv.rb', line 265

def subject
  @subject
end

Instance Method Details

#process(data) ⇒ RDF::Changeset

Note:

This operation may change the state of the processor, so while this object can be reused for multiple hashes, it is unwise to reuse it across requests.

Returns A changeset containing the results.

Parameters:

  • data (Hash)

    The data coming, e.g., from the Web form.

Returns:

  • (RDF::Changeset)

    A changeset containing the results.

Raises:

  • (ArgumentError)


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
# File 'lib/rdf/kv.rb', line 310

def process data
  raise ArgumentError, 'data must be a hash' unless data.is_a? Hash
  macros  = GENERATED.dup
  maybe   = {} # candidates
  neither = {} # discard pile

  data.each do |k, *v|
    # step 0: get the values to a homogeneous list
    k = k.to_s
    v = v.flatten.map(&:to_s)
    # step 1: pull out all the macro declarations
    if (m = /#{DECLARATION}/o.match k)
      name  = m[1].to_sym
      sigil = !!(m[2] && !m[2].empty?)
      # skip over generated macros
      next if GENERATED.key? name
      # step 1.0.1: create [content, deref flag] pairs
      (macros[name] ||= []).concat v.map { |x| [x, sigil] }
    elsif (m = /(?:^\s*\S+\s+\S+.*?$|[:\$])/.match k)
      (maybe[k] ||= []).concat v
    else
      (neither[k] ||= []).concat v
    end
  end

  # step 2: dereference all the macros (that asked to be dereferenced)
  begin
    macros = massage_macros macros
  rescue Exception => e
    # XXX we should do something more here
    raise Error.new e
  end

  # step 3: apply special control macros (which modify self)
  begin
    SPECIALS.each do |k, macro|
      instance_exec macros[k], &macro if macros[k]
    end
  rescue Exception => e
    # again this should be nicer
    raise Error.new e
  end

  # this will be our output
  patch = RDF::Changeset.new

  maybe.each do |k, v|
    # this will return an array now
    k = deref_content(k, macros).compact
    v = v.compact.map(&:strip).uniq

    # this is only this way because of macros
    k.each do |template|
      tokens = GRAMMAR.match(template) or next
      tokens = tokens.captures

      raise 'INTERNAL ERROR: Regexp captures do not match template' unless
        tokens.length == MAP.length

      # i had something much cleverer here but of course it didn't DWIW
      contents = {}
      MAP.each_index { |i| contents[MAP[i]] ||= tokens[i] }
      contents.compact!

      contents[:modifier] = (contents[:modifier] || '').chars.map do |c|
        [c, true]
      end.to_h

      if contents[:designator]
        sigil, symbol = contents[:designator].split '', 2
        symbol = resolve_term symbol if sigil == ?^
        contents[:designator] = symbol.to_s.empty? ? [sigil] : [sigil, symbol]
      else
        contents[:designator] = [contents[:modifier][?!] ? ?: : ?']
      end

      %i[term1 term2 graph].filter { |t| contents[t] }.each do |which|
        tmp = resolve_term contents[which]
        tmp = callback.call(tmp) if callback
        contents[which] = tmp
      end

      # these are the values we actually use; ensure they are duplicated
      values = (contents[:deref] ? deref_content(v, macros) : v).dup

      g = coerce_term(contents[:graph]) || graph
      # initialize the triple
      s, p, o = nil

      # shorthand for reverse
      if reverse = !!contents[:modifier][?!]
        # literals make no sense on reverse statements
        # (XXX this is a candidate for diagnostics)
        next unless [?_, ?:].include? contents[:designator].first
        # these terms have already been resolved/coerced
        p = contents[:term1]
        o = contents[:term2] || (callback ? callback.call(subject) : subject)
      else
        s, p = (contents[:term2] ? contents.values_at(:term1, :term2) :
                [subject, contents[:term1]]).map do |t|
            # XXX DO WE NEED THIS ANYMORE??
            t = resolve_term t
            callback ? callback.call(t) : t
        end
      end

      # the operation depends on whether the `-` modifier is present
      op = contents[:modifier][?-] ? :delete : :insert

      # if we're deleting triples and the values contain an empty
      # string then we're deleting a wildcard, same if we `=` overwrite
      if !reverse and op == :delete && values.include?('') ||
          contents[:modifier][?=]

        # create a random variable name so we don't pass in a nil
        var = RDF::Query::Variable.new random_uuid_ncname

        # i can't remember why we don't do this in reverse, probably
        # because it is too easy to shoot yourself in the foot
        patch.delete RDF::Statement(s, p, var, graph_name: g)

        # nuke these since it will be pointless to evaluate further
        values.clear if op == :delete
      end

      # otherwise the code is basically the same
      values.each do |x|
        # get what should be guaranteed to be an RDF term or nil
        x = coerce_term(x, *contents[:designator]) or next

        # now we assign the appropriate direction
        reverse ? s = x : o = x

        # this will be either insert or delete
        patch.send op, RDF::Statement(s, p, o, graph_name: g)
      end
    end
  end

  patch
end