Class: Webgen::NodeFinder

Inherits:
Object
  • Object
show all
Defined in:
lib/webgen/node_finder.rb

Overview

Used for finding nodes that match certain criterias.

About

This extension class is used for finding nodes that match certain criterias (all nodes are used if no filter options are specified) when calling the #find method. There are some built-in filters but one can also provide custom filters via #add_filter_module.

The found nodes are either returned in a flat list or hierarchical in nested lists (if a node has no child nodes, only the node itself is used; otherwise a two element array containing the node and child nodes is used). Sorting, limiting the number of returned nodes and using an offset are also possible.

Note that results are cached in the volatile cache of the Cache instance!

Finder options

A complete list of the supported finder options can be found in the user documentation! Note that there may also be other 3rd party node filters available if you are using extension bundles!

Implementing a filter module

Implementing a filter module is very easy. Just create a module that contains your filter methods and tell the NodeFinder object about it using the #add_filter_module method. A filter method needs to take three arguments: the Result stucture, the reference node and the filter value.

The result.nodes accessor contains the array of nodes that should be manipulated in-place.

If a filter uses the reference node, it has to tell the node finder about it to allow proper caching:

  • If the node’s language property is used, result.lang_used has to be set to true.

  • If the node’s hierarchy level is used, result.level_used has to be set to true.

  • If the node’s parent is used, result.parent_node_used has to be set to true.

  • In all other cases, result.ref_node_used has to be set to true

Here is a sample filter module which provides the ability to filter nodes based on the meta information key category. The category key contains an array with one or more categories. The value for this category filter is one or more strings and the filter returns those nodes that contain at least one specified category.

module CategoryFilter

  def filter_on_category(result, ref_node, categories)
    categories = [categories].flatten # needed in case categories is a string
    result.nodes.select! {|n| categories.any? {|c| n['category'].include?(c)}}
  end

end

website.ext.node_finder.add_filter_module(CategoryFilter, category: 'filter_on_category')

Defined Under Namespace

Classes: Result

Instance Method Summary collapse

Constructor Details

#initialize(website) ⇒ NodeFinder

Create a new NodeFinder object for the given website.



76
77
78
79
80
81
82
83
84
85
# File 'lib/webgen/node_finder.rb', line 76

def initialize(website)
  @website = website
  @mapping = {
    :alcn => :filter_alcn, :absolute_levels => :filter_absolute_levels, :lang => :filter_lang,
    :and => :filter_and, :or => :filter_or, :not => :filter_not,
    :ancestors => :filter_ancestors, :descendants => :filter_descendants,
    :siblings => :filter_siblings,
    :mi => :filter_meta_info
  }
end

Instance Method Details

#add_filter_module(mod, mapping) ⇒ Object

Add a module with filter methods.

The parameter mapping needs to be a hash associating unique names with the methods of the given module that can be used as finder methods.

Examples:

node_finder.add_filter_module(MyModule, blog: 'filter_on_blog')


96
97
98
99
100
101
102
103
104
105
# File 'lib/webgen/node_finder.rb', line 96

def add_filter_module(mod, mapping)
  public_methods = mod.public_instance_methods.map {|c| c.to_s}
  mapping.each do |name, method|
    if !public_methods.include?(method.to_s)
      raise ArgumentError, "Finder method '#{method}' not found in module #{mod}"
    end
    @mapping[name.intern] = method
  end
  extend(mod)
end

#find(opts_or_name, ref_node) ⇒ Object

Return all nodes that match certain criterias.

The parameter opts_or_name can either be a hash with finder options or the name of a finder option set defined using the configuration option ‘node_finder.options_sets’. The node ref_node is used as reference node.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/webgen/node_finder.rb', line 112

def find(opts_or_name, ref_node)
  if result = cached_result(opts_or_name, ref_node)
    return result.nodes
  end
  opts = prepare_options_hash(opts_or_name)

  limit, offset, flatten, sort, levels, reverse = remove_non_filter_options(opts)
  flatten = true if limit || offset
  levels = [levels || [1, 1_000_000]].flatten.map {|i| i.to_i}

  result = filter_nodes(opts, ref_node)
  nodes = result.nodes

  if flatten
    sort_nodes(nodes, sort, reverse)
    nodes = nodes[(offset.to_s.to_i)..(limit ? offset.to_s.to_i + limit.to_s.to_i - 1 : -1)] if limit || offset
  else
    temp = {}
    min_level = 1_000_000
    nodes.each {|n| min_level = n.level if n.level < min_level}

    nodes.each do |n|
      hierarchy_nodes = []
      (hierarchy_nodes.unshift(n); n = n.parent) while n.level >= min_level
      hierarchy_nodes.inject(temp) {|memo, hn| memo[hn] ||= {}}
    end

    reducer = lambda do |h, level|
      if level < levels.first
        temp = h.map {|k,v| v.empty? ? nil : reducer.call(v, level + 1)}.compact
        temp.length == 1 && temp.first.kind_of?(Array) ? temp.first : temp
      elsif level < levels.last
        h.map {|k,v| v.empty? ? k : [k, reducer.call(v, level + 1)]}
      else
        h.map {|k,v| k}
      end
    end
    nodes = reducer.call(temp, 1)
    sort_nodes(nodes, sort, reverse, false)
  end

  result.nodes = nodes
  cache_result(opts_or_name, ref_node, result)
  result.nodes
end