class Krikri::Parser::ValueArray

A specialized Array object for containing Parser::Values. Provides methods
for accessing and filtering values that can be chained.

@example chaining methods to select values

    my_value_array.field('dc:creator', 'foaf:name')
      .match_attribute('first_name').values

Methods defined on this class should return another ValueArray, an Array
of literal values (retrieved from Parser::Value#value), or a single
literal value.

Uses `#bindings` to track variables for recovery via `#else`, `#from`, and

`#back`. Methods that return a `ValueArray` pass `#bindings` down to the

new instance.

@example

  my_value_array.field('dc:creator').bind(:creator)

@example `#if` sets `bindings[:top]`, `#else` recoveres if empty

  my_value_array.field('dc:creator', 'foaf:name').if.field('empty:field')
    .else { |vs| vs.field('some:otherField') }

Attributes

bindings[R]

@!attribute [r] bindings

A hash containing bindings of variables to (Symbols) to ValueArrays

Public Class Methods

build(record) click to toggle source

Wraps the root node of the given record in this class.

@param record [Krikri::Parser] a parsed record to wrap in a ValueArray @return [ValueArray]

# File lib/krikri/parser.rb, line 504
def self.build(record)
  new([record.root])
end
new(array = [], bindings = {}) click to toggle source

@param array [Array] an array of values to delegate array operations to @param bindings [Hash<Symbol, ValueArray] a set of variable bindings

This is overloaded to accept an instance of this class to use as a 
`:top` recovery node in, e.g. `#else`. `:top` to `self` if none is
passed.
# File lib/krikri/parser.rb, line 190
def initialize(array = [], bindings = {})
  @array = array

  if bindings.is_a?(ValueArray)
    # this way is deprected!
    # how should we handle deprecations?
    @bindings = {}
    @bindings[:top] ||= bindings
  else
    @bindings = bindings
    @bindings[:top] ||= self
  end
end

Public Instance Methods

<<(value) click to toggle source

@see Array#<< @raise [InvalidParserValueError] when the value is not a Parser::Value

# File lib/krikri/parser.rb, line 216
def <<(value)
  raise InvalidParserValueError unless value.is_a? Value
  @array << value
  value
end
[]=(index, value) click to toggle source

@see Array#[]= @raise [InvalidParserValueError] when the value is not a Parser::Value

# File lib/krikri/parser.rb, line 207
def []=(index, value)
  raise InvalidParserValueError unless value.is_a? Value
  @array[index] = value
  self
end
and(from: :top) click to toggle source

Returns to a bound variable unless the current value is empty. If no variable is given, returns to the top of the call chain.

This allows checking complex conditionals, where mappings depend on data from deep within multiple branches of the tree.

@example use with `#bind`

value_array.field(:subject).bind(:subj)
  .field('some', 'subfield')
  .select { |a| condition(a) }
  .and(:subj).field(:label)

@param from [#to_sym] a symbol respresenting a bound variable name;

default: :top

@return [ValueArray] self

@raise [ArgumentError] when an unbound variable is given as `var`

# File lib/krikri/parser.rb, line 240
def and(from: :top)
  return self if self.empty?
  bindings[from.to_sym] or
    raise ArgumentError, "Tried to return to unbound variable: #{from}"
end
at(idx) click to toggle source

@param idx [#to_i, Range] @return [ValueArray] an array containing the node(s) in the

specified index posiition(s).
# File lib/krikri/parser.rb, line 274
def at(idx)
  self.class.new(Array(@array[idx]))
end
bind(var) click to toggle source

Binds the current array to the variable name given

@param var [#to_sym] a symbol respresenting the variable name @return [ValueArray] self

# File lib/krikri/parser.rb, line 251
def bind(var)
  bindings[var.to_sym] = self
  self
end
compact() click to toggle source

@see Array#compact @return [ValueArray]

# File lib/krikri/parser.rb, line 392
def compact
  self.class.new(@array.compact, bindings)
end
concat(*args, &block) click to toggle source

@see Array#concat @return [ValueArray]

# File lib/krikri/parser.rb, line 259
def concat(*args, &block)
  self.class.new(@array.concat(*args, &block), bindings)
end
else() { |bindings| ... } click to toggle source

Short circuits if `self` is not empty, else passes the top of the call chain (`@bindings`) to the given block.

@example usage with `#if`

value_array.if { |arry| arry.field(:a_field) }
  .else { |arry|  arry.field(:alternate_field) }

# use other filters at will
value_array.if.field(:a_field).reject { |v| v == 'SKIP ME' }
  .else { |arry|  arry.field(:alternate_field) }

@example standalone use; resetting to record root

value_array.field(:a_field).else { |arry| arry.field(:alternate_field) }

@yield gives `@bindings` if self is empty @yieldparam arry [ValueArray] the value of `@bindings`

@return [ValueArray] `self` unless empty; otherwise the result of the

block
# File lib/krikri/parser.rb, line 361
def else(&block)
  raise ArgumentError, 'No block given for `#else`' unless block_given?
  return self unless self.empty?
  yield bindings[:top]
end
field(*args) click to toggle source

Accesses a given field. Use multiple arguments to travel down the node hierarchy.

@return [ValueArray] an array containing the nodes available in a

particular field.
# File lib/krikri/parser.rb, line 284
def field(*args)
  result = self
  args.each { |name| result = result.get_field(name) }

  result
end
field_names() click to toggle source

Lists field names on this node.

This can be useful in cases where the desired value is a key or child node label, rather than a value.

@return [ValueArray] an array containing the field names.

@see select_child, reject_child for methods that filter on these names

# File lib/krikri/parser.rb, line 312
def field_names
  self.class.new(flat_map(&:children).uniq, bindings)
end
fields(*args) click to toggle source

Accesses the union of multiple specified fields.

@return [ValueArray] an array containing the nodes available in the given fields.

# File lib/krikri/parser.rb, line 296
def fields(*args)
  results = args.map do |f|
    field(*Array(f))
  end
  self.class.new(results.flatten, bindings)
end
first_value(*args) click to toggle source

Retrieves the first element of a ValueArray. Uses an optional argument to specify how many items to return. By design, it behaves similarly to Array#first, but it intentionally doesn't override it.

@return [ValueArray] a Krikri::Parser::ValueArray for first n elements

# File lib/krikri/parser.rb, line 373
def first_value(*args)
  return self.class.new(@array.first(*args)) unless args.empty?
  self.class.new([@array.first].compact, bindings)
end
flatten(*args, &block) click to toggle source

@see Array#concat @return [ValueArray]

# File lib/krikri/parser.rb, line 399
def flatten(*args, &block)
  self.class.new(@array.flatten(*args, &block), bindings)
end
if() { |self| ... } click to toggle source

Sets the top of the call chain to self and returns or yields self.

This is syntactic sugar for `#bind(:top)`, with the addition of block syntax.

@example with method chain syntax

value_array.if.field(:a_field).else do |arry|
   arry.field(:alternate_field)
end

@example with block syntax

value_array.if { |arry| arry.field(:a_field) }
  .else { |arry|  arry.field(:alternate_field) }

@yield gives self @yieldparam arry [ValueArray] self

@return [ValueArray] the result of the block, if given; or self with :top set

# File lib/krikri/parser.rb, line 335
def if(&block)
  bind(:top)
  return yield self if block_given?
  self
end
last_value(*args) click to toggle source

Retrieves the last element of a ValueArray. Uses an optional argument to specify how many items to return. By design, it behaves similarly to Array#last, but it intentionally doesn't override it.

@return [ValueArray] a Krikri::Parser::ValueArray for last n elements

# File lib/krikri/parser.rb, line 384
def last_value(*args)
  return self.class.new(@array.last(*args)) unless args.empty?
  self.class.new([@array.last].compact, bindings)
end
map(*args, &block) click to toggle source

Wraps the result of Array#map in a ValueArray

@see Array#map @return [ValueArray]

# File lib/krikri/parser.rb, line 408
def map(*args, &block)
  self.class.new(@array.map(*args, &block), bindings)
end
match_attribute(name, other = nil, &block) click to toggle source

@example selecting by presence of an attribute; returns all nodes where

`#attribute?(:type)` is true

match_attribute(:type)

@example selecting by the value of an attribute; returns all nodes with

`#attribute(:type) == other`

match_attribute(:type, other)

@example selecting by block against an attribute; returns all nodes with

`block.call(attribute(:type))` is true

match_attribute(:type) { |value| value.starts_with? 'blah' }

@example selecting by block against an attribute; returns all nodes with

`block.call(attribute(:type)) == other` is true

match_attribute(:type, 'moomin') { |value| value.downcase }

@param name [#to_sym] an attribute name @param other [Object] an object to check for equality with the

values from the given attribute.

@yield [value] yields each value with the attribute in name to the block

@return [ValueArray] an array containing nodes for which the specified

attribute has a value matching the given attribute name, object, and
block.
# File lib/krikri/parser.rb, line 460
def match_attribute(name, other = nil, &block)
  select(&compare_to_attribute(name, other, &block))
end
match_child(*children) click to toggle source

@param *children [Array<String>]

@return [ValueArray] an array containing nodes for which the specified

attribute does not have a child node matching the given child name
# File lib/krikri/parser.rb, line 469
def match_child(*children)
  select { |v| !(v.children & children).empty? }
end
reject(*args, &block) click to toggle source

Wraps the result of Array#reject in a ValueArray

@see Array#reject @return [ValueArray]

# File lib/krikri/parser.rb, line 426
def reject(*args, &block)
  self.class.new(@array.reject(*args, &block), bindings)
end
reject_attribute(name, other = nil, &block) click to toggle source

@param name [#to_sym] an attribute name @param other [Object] an object to check for equality with the

values from the given attribute.

@yield [value] yields each value with the attribute in name to the block

@return [ValueArray] an array containing nodes for which the specified

attribute does not have a value matching the given attribute name,
object, and block.

@see match_attribute for examples; this calls reject, where it calls

#select.
# File lib/krikri/parser.rb, line 486
def reject_attribute(name, other = nil, &block)
  reject(&compare_to_attribute(name, other, &block))
end
reject_child(*children) click to toggle source

@param *children [Array<String>]

@return [ValueArray] an array containing nodes for which the specified

attribute does not have a childnode matching the given child name
# File lib/krikri/parser.rb, line 495
def reject_child(*children)
  reject { |v| !(v.children & children).empty? }
end
select(*args, &block) click to toggle source

Wraps the result of Array#select in a ValueArray

@see Array#select @return [ValueArray]

# File lib/krikri/parser.rb, line 417
def select(*args, &block)
  self.class.new(@array.select(*args, &block), bindings)
end
values() click to toggle source

@return [Array] literal values from the objects in this array. @see Parser::Value#value

# File lib/krikri/parser.rb, line 266
def values
  @array.map { |v| v.respond_to?(:value) ? v.value : v }
end

Protected Instance Methods

get_field(name) click to toggle source
# File lib/krikri/parser.rb, line 510
def get_field(name)
  self.class.new(flat_map { |val| val[name] }, bindings)
end

Private Instance Methods

compare_to_attribute(name, other) { |result| ... } click to toggle source

@see match_attribute, reject_attribute

# File lib/krikri/parser.rb, line 518
def compare_to_attribute(name, other, &block)
  lambda do |v|
    next unless v.attribute?(name.to_sym)
    result = v.send(name)
    result = yield(result) if block_given?
    return result == other if other
    result
  end
end