class Kitchen::ElementEnumeratorFactory

Builds specific subclasses of ElementEnumeratorBase

Attributes

default_css_or_xpath[R]
detect_sub_element_class[R]
enumerator_class[R]
sub_element_class[R]

Public Class Methods

new(enumerator_class:, default_css_or_xpath: nil, sub_element_class: nil, detect_sub_element_class: false) click to toggle source

Creates a new instance

@param default_css_or_xpath [String, Proc, Symbol] The selectors to substitute for the “$” character

when this factory is used to build an enumerator.  A string argument is used literally.  A proc
is eventually called given the document's Config object (for accessing selectors).  A symbol
is interpreted as the name of a selector and is called on the document's Config object's
selectors object.  The easiest way to get a Proc is to pass `Selector.named(:name_of_selector)`

@param sub_element_class [ElementBase] The element class to use for what the enumerator finds. @param enumerator_class [ElementEnumeratorBase] The enumerator class to return @param detect_sub_element_class [Boolean] If true, infers the sub_element_class from the node

# File lib/kitchen/element_enumerator_factory.rb, line 24
def initialize(enumerator_class:, default_css_or_xpath: nil, sub_element_class: nil,
               detect_sub_element_class: false)
  @default_css_or_xpath = default_css_or_xpath
  @sub_element_class = sub_element_class
  @enumerator_class = enumerator_class
  @detect_sub_element_class = detect_sub_element_class
end

Public Instance Methods

build_within(enumerator_or_element, search_query: SearchQuery.new, reload: false) click to toggle source

Builds a new enumerator within the scope of the provided argument (either an enumerator or a class). Accepts optional selectors to further limit the scope of results found.

@param enumerator_or_element [ElementEnumeratorBase, ElementBase] the object

within which to iterate

@param search_query [SearchQuery] search directives to limit iteration results @return [ElementEnumeratorBase] actually returns the concrete enumerator class

given to the factory in its constructor.
# File lib/kitchen/element_enumerator_factory.rb, line 42
def build_within(enumerator_or_element, search_query: SearchQuery.new, reload: false)
  case enumerator_or_element
  when ElementBase
    build_within_element(enumerator_or_element,
                         search_query: search_query,
                         reload: reload)
  when ElementEnumeratorBase
    if enumerator_class != ElementEnumerator && !search_query.expects_substitution?
      raise "Query #{search_query} is missing the substitution character ('$') but " \
            "is run with an enumerator #{enumerator_class.name} that has its own " \
            "selectors for substitution."
    end

    build_within_other_enumerator(enumerator_or_element,
                                  search_query: search_query,
                                  reload: reload)
  end
end
or_with(other_factory) click to toggle source

Builds a new enumerator that finds elements matching either this factory's or the provided factory's selectors.

@param other_factory [ElementEnumeratorFactory] @return [ElementEnumeratorFactory]

# File lib/kitchen/element_enumerator_factory.rb, line 67
def or_with(other_factory)
  self.class.new(
    default_css_or_xpath: [default_css_or_xpath, other_factory.default_css_or_xpath],
    enumerator_class: TypeCastingElementEnumerator,
    detect_sub_element_class: true
  )
end

Protected Instance Methods

build_within_element(element, search_query:, reload:) click to toggle source
# File lib/kitchen/element_enumerator_factory.rb, line 77
def build_within_element(element, search_query:, reload:)
  search_query.apply_default_css_or_xpath_and_normalize(default_css_or_xpath,
                                                        config: element.config)

  enumerator_class.new(search_query: search_query) do |block|
    grand_ancestors = element.ancestors
    parent_ancestor = Ancestor.new(element)

    # If the provided `search_query` has already been iterated through on this element,
    # we need to undo any counting on the ancestors so that when they are counted again
    # below, the counts are correct.
    element.uncount(search_query)

    element.raw_search(*search_query.css_or_xpath, reload: reload).each do |sub_node|
      sub_element = ElementFactory.build_from_node(
        node: sub_node,
        document: element.document,
        element_class: sub_element_class,
        default_short_type: search_query.as_type,
        detect_element_class: detect_sub_element_class
      )

      next unless search_query.conditions_match?(sub_element)

      # Record this sub element's ancestors and increment their descendant counts
      sub_element.add_ancestors(grand_ancestors, parent_ancestor)

      # Remember that we counted this sub element in case we need to later reset the counts
      element.remember_that_a_sub_element_was_counted(search_query, sub_element.short_type)

      # Remember how this sub element was found so can trace search history given any element.
      sub_element.search_query_that_found_me = search_query

      # Mark the location so that if there's an error we can show the developer where.
      sub_element.mark_as_current_location!

      block.yield(sub_element)
    end
  end
end
build_within_other_enumerator(other_enumerator, search_query:, reload:) click to toggle source
# File lib/kitchen/element_enumerator_factory.rb, line 118
def build_within_other_enumerator(other_enumerator, search_query:, reload:)
  # Return a new enumerator instance that internally iterates over `other_enumerator`
  # running a new enumerator for each element returned by that other enumerator.
  enumerator_class.new(search_query: search_query,
                       upstream_enumerator: other_enumerator) do |block|
    other_enumerator.each do |element|
      build_within_element(element,
                           search_query: search_query,
                           reload: reload).each do |sub_element|
        block.yield(sub_element)
      end
    end
  end
end