class Decontaminate::Decontaminator

{Decontaminate::Decontaminator} is the base class for creating XML extraction parsers. A DSL is exposed via class methods to allow specifying how the XML should be parsed.

Attributes

decoders[R]

@private

root[RW]

An attribute that controls what the base element should be for all XPath queries against the provided XML node.

xml_node[R]

Public Class Methods

add_decoder(key, decoder) click to toggle source

Registers an XML decoder under the given key. This method is called by all of the DSL directives when registering a decoder into the internal list of decoders. It can be overridden by subclasses to customize how adding decoders works (this is done anonymously by {with}).

@param key [String] The key the decoder will produce in the resulting

JSON

@param decoder [Decontaminate::Decoder] A decoder instance used to

produce the result value
# File lib/decontaminate/decontaminator.rb, line 232
def add_decoder(key, decoder)
  fail "Decoder already registered for key #{key}" if decoders.key? key
  decoders[key] = decoder
end
hash(xpath = '.', key: infer_key(xpath), &body) click to toggle source

Produces a hash in the resulting JSON with values determined by the body of the provided block. The {hash} directive allows for logical grouping of values by nesting them within separate hash structures. The body of the provided block works just like the surrounding context—directives such as {scalar}, {tuple}, and even {hash} may be further nested within the hash body.

The xpath argument is optional if key is provided. If xpath is provided, it will scope the block to the element referenced by xpath like {with}. In fact, providing xpath works exactly the same as wrapping the body of {hash} with a {with} directive.

The key argument is optional if xpath is provided. If key is not provided, its value will be inferred from xpath using {infer_key}.

If the xpath does not point to an element, the result will be nil.

@param xpath [String] A relative XPath string that points to the parent

node for all values within the hash

@param key [String] The key for the value in the resulting JSON @param body A block that includes directives that specify the contents

of the hash
# File lib/decontaminate/decontaminator.rb, line 158
def hash(xpath = '.', key: infer_key(xpath), &body)
  decontaminator = Class.new(class_to_inherit_from, &body)
  add_decoder key, Decontaminate::Decoder::Hash.new(xpath, decontaminator)
end
hashes(xpath = nil, path: nil, key: nil, &body) click to toggle source

The plural version of {hash}. Produces an array of hashes for the given key in the resulting JSON. If the element path is specified using the xpath argument, then the individual elements are inferred using {infer_plural_path}. Otherwise, if the elements are specified using the path argument, no inference is performed, and the path must return a set of multiple elements.

If xpath is provided, but the parent element does not exist, the result will be an empty array, not nil.

@param xpath [String] A relative XPath string that points to a parent

element containing singular values (mutually exclusive with +path+)

@param path [String] A relative XPath string that points to the singular

values (mutually exclusive with +xpath+)

@param key [String] The key for the value in the resulting JSON @param body A block that includes directives that specify the contents

of the hash
# File lib/decontaminate/decontaminator.rb, line 180
def hashes(xpath = nil, path: nil, key: nil, &body)
  resolved_path = path || infer_plural_path(xpath)
  key ||= infer_key(path || xpath)

  decontaminator = Class.new(class_to_inherit_from, &body)
  singular = Decontaminate::Decoder::Hash.new('.', decontaminator)
  decoder = Decontaminate::Decoder::Array.new(resolved_path, singular)

  add_decoder key, decoder
end
infer_key(xpath) click to toggle source

Infers the name of a key for use in a JSON object from an XPath query string. This is used by the DSL directives to infer a key when one is not provided. By default, it strips a leading +@+ sign, removes all leading or trailing underscores, then converts the string to an underscore-separated string using {ActiveSupport::Inflector#underscore}.

@param xpath [String] An XPath query string @return [String] A key for use in a JSON object

# File lib/decontaminate/decontaminator.rb, line 245
def infer_key(xpath)
  xpath.delete('@').gsub(/^_+|_+$/, '').underscore
end
infer_plural_path(xpath) click to toggle source

Infers that path for singular elements from an XPath string referring to a plural parent element. This is used by {scalars} and {hashes} to infer their singular elements. By default, it finds the last path element, splitting the string on forward-slash characters, converts it to a singular by using {ActiveSupport::Inflector#singularize}, and appends the result to the whole path.

@param xpath [String] An XPath query string @return [String] An inferred path to the singular elements

# File lib/decontaminate/decontaminator.rb, line 258
def infer_plural_path(xpath)
  xpath + '/' + xpath.split('/').last.singularize
end
inherited(subclass) click to toggle source

@private

# File lib/decontaminate/decontaminator.rb, line 23
def inherited(subclass)
  subclass.instance_eval do
    @root = '.'
    @decoders = {}
  end
end
new(xml_node, instance: nil) click to toggle source

Instantiates a decontaminator with a Nokogiri XML node or document.

@param xml_node [Nokogiri::XML::Node] The XML node or document to

decontaminate
# File lib/decontaminate/decontaminator.rb, line 279
def initialize(xml_node, instance: nil)
  @xml_node = xml_node
  @instance = instance
end
scalar(xpath, type: :string, key: infer_key(xpath), transformer: nil, &block) click to toggle source

Produces a singular scalar value in the resulting JSON, which can be a string, an integer, a float, or a boolean, depending on the value provided for the type argument. The default is :string. If the node pointed to by xpath is plain text or an attribute value, then that value will be used directly. Otherwise, the node's text will be used when parsing the scalar value.

The key argument is optional. If it is not provided, its value will be inferred from xpath using {infer_key}.

If the xpath does not point to an element, the result will be nil.

The result may be customized by either passing a block or providing a value for the transformer argument (passing both is an error). If transformer is provided, then the instance method with the same name is called with the resulting value, and the method's return value is what is included in the resulting JSON. If a block is provided, it is called with the resulting value in the context of the instance (so self points to an instance of the decontaminator), and its return value is what is included in the resulting JSON.

@param xpath [String] A relative XPath string that points to the scalar

value

@param type [Symbol] One of either :string, :integer, :float, or

+:boolean+

@param key [String] The key for the value in the resulting JSON @param transformer [Symbol] A symbol with the name of an instance method

used to transform the result

@param block A block used to transform the result

# File lib/decontaminate/decontaminator.rb, line 61
def scalar(xpath,
           type: :string,
           key: infer_key(xpath),
           transformer: nil,
           &block)
  block ||= self_proc_for_method transformer
  add_decoder key, Decontaminate::Decoder::Scalar.new(xpath, type, block)
end
scalars(xpath = nil, path: nil, type: :string, key: nil, transformer: nil, &block) click to toggle source

The plural version of {scalar}. Produces an array of scalars for the given key in the resulting JSON. If the element path is specified using the xpath argument, then the individual elements are inferred using {infer_plural_path}. Otherwise, if the elements are specified using the path argument, no inference is performed, and the path must return a set of multiple elements.

If xpath is provided, but the parent element does not exist, the result will be an empty array, not nil.

@param xpath [String] A relative XPath string that points to a parent

element containing singular values (mutually exclusive with +path+)

@param path [String] A relative XPath string that points to the singular

values (mutually exclusive with +xpath+)

@param type [Symbol] One of either :string, :integer, :float, or

+:boolean+

@param key [String] The key for the value in the resulting JSON @param transformer [Symbol] A symbol with the name of an instance method

used to transform the result

@param block A block used to transform the result

# File lib/decontaminate/decontaminator.rb, line 90
def scalars(xpath = nil,
            path: nil,
            type: :string,
            key: nil,
            transformer: nil,
            &block)
  resolved_path = path || infer_plural_path(xpath)
  key ||= infer_key(path || xpath)
  block ||= self_proc_for_method transformer

  singular = Decontaminate::Decoder::Scalar.new('.', type, block)
  decoder = Decontaminate::Decoder::Array.new(resolved_path, singular)

  add_decoder key, decoder
end
tuple(paths, key:, type: :string, transformer: nil, &block) click to toggle source

Like {scalar} but using multiple paths. By default, this will produce a fixed-length array in the resulting JSON containing an element for each path.

This is especially useful when combined with a block or transformer, since the result can be a value computed from the individual element values. When the block or transformer method is called, it is provided a separate argument for each value in the tuple.

@param paths [Array<String>] An array of relative XPath strings that

each point to a scalar value

@param key [String] The key for the value in the resulting JSON @param type [Symbol] One of either :string, :integer, :float, or

+:boolean+

@param transformer [Symbol] A symbol with the name of an instance method

used to transform the result

@param block A block used to transform the result

# File lib/decontaminate/decontaminator.rb, line 123
def tuple(paths,
          key:,
          type: :string,
          transformer: nil,
          &block)
  block ||= self_proc_for_method transformer

  scalar = Decontaminate::Decoder::Scalar.new('.', type, nil)
  decoder = Decontaminate::Decoder::Tuple.new(paths, scalar, block)

  add_decoder key, decoder
end
with(xpath, &body) click to toggle source

Scopes all nested directives to the node referenced by the xpath argument. Unlike other directives, {with} does not produce any value in the resulting JSON. Instead, it just adjusts all nested paths so that they are relative to the node referenced by xpath.

@param xpath [String] A relative XPath string that points to a single

node

@param body A block that includes directives that are scoped to the

referenced node
# File lib/decontaminate/decontaminator.rb, line 200
def with(xpath, &body)
  this = self
  decontaminator = Class.new(class_to_inherit_from)

  decontaminator.instance_eval do
    # proxy add_decoder to the parent class
    define_singleton_method :add_decoder do |key, decoder|
      proxy = Decontaminate::Decoder::ChildNodeProxy.new(xpath, decoder)
      this.add_decoder key, proxy
    end

    # also, don't let subclasses inherit from this; they would also pick
    # up the overridden add_decoder method
    define_singleton_method :class_to_inherit_from do
      this
    end
  end

  decontaminator.class_eval(&body)
end

Private Class Methods

class_to_inherit_from() click to toggle source
# File lib/decontaminate/decontaminator.rb, line 264
def class_to_inherit_from
  self
end
self_proc_for_method(sym) click to toggle source
# File lib/decontaminate/decontaminator.rb, line 268
def self_proc_for_method(sym)
  sym && proc { |*args| send sym, *args }
end

Public Instance Methods

as_json() click to toggle source

Retrieves the decontaminated JSON representation of the given XML node.

@return [Hash] The decontaminated JSON object

# File lib/decontaminate/decontaminator.rb, line 287
def as_json
  acc = {}

  root_node = xml_node && xml_node.at_xpath(root)
  decoders.each do |key, decoder|
    acc[key] = decoder.decode instance, root_node
  end

  acc
end

Private Instance Methods

decoders() click to toggle source
# File lib/decontaminate/decontaminator.rb, line 306
def decoders
  self.class.decoders
end
instance() click to toggle source

The canonical instance that all blocks should run within. Child (anonymous) decontaminators should delegate to the parent.

# File lib/decontaminate/decontaminator.rb, line 302
def instance
  @instance || self
end
root() click to toggle source
# File lib/decontaminate/decontaminator.rb, line 310
def root
  self.class.root
end