<commit_before>module Capybara
  # this is a class for generating XPath queries, use it like this:
  #     Xpath.text_field('foo').link('blah').to_s
  # this will generate an XPath that matches either a text field or a link
  class XPath

    class << self
      def escape(string)
        if string.include?("'")
          string = string.split("'", -1).map do |substr|
            "'#{substr}'"
          end.join(%q{,"'",})
          "concat(#{string})"
        else
          "'#{string}'"
        end
      end
      
      def wrap(path)
        if path.is_a?(self)
          path
        else
          new(path.to_s)
        end
      end

      def respond_to?(method)
        new.respond_to?(method)
      end

      def method_missing(*args)
        new.send(*args)
      end
    end

    attr_reader :paths

    def initialize(*paths)
      @paths = paths
    end

    def to_s
      @paths.join(' | ')
    end

    def append(path)
      XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
    end

    def prepend(path)
      XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
    end

    def from_css(css)
      XPath.new(*[@paths, Nokogiri::CSS.xpath_for(css).map { |selector| '.' + selector }].flatten)
    end
    alias_method :for_css, :from_css

    def field(locator, options={})
      if options[:with]
        fillable_field(locator, options)
      else
        xpath = fillable_field(locator)
        xpath = xpath.input_field(:file, locator, options)
        xpath = xpath.checkbox(locator, options)
        xpath = xpath.radio_button(locator, options)
        xpath.select(locator, options)
      end
    end

    def fillable_field(locator, options={})
      text_area(locator, options).text_field(locator, options)
    end

    def content(locator)
      append("./descendant-or-self::*[contains(normalize-space(.),#{s(locator)})]")
    end

    def table(locator, options={})
      conditions = ""
      if options[:rows]
        row_conditions = options[:rows].map do |row|
          row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
          "tr[./#{row}]"
        end.join(sibling)
        conditions << "[.//#{row_conditions}]"
      end
      append(".//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
    end

    def fieldset(locator)
      append(".//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
    end

    def link(locator)
      xpath = append(".//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
      xpath.prepend(".//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
    end

    def button(locator)
      xpath = append(".//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
      xpath = xpath.append(".//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
      xpath = xpath.prepend(".//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
      xpath = xpath.prepend(".//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
      xpath = xpath.prepend(".//button[@value=#{s(locator)} or text()=#{s(locator)}]")
    end

    def text_field(locator, options={})
      options = options.merge(:value => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//input[not(@type) or (@type!='radio' and @type!='checkbox' and @type!='hidden')]", options)
    end

    def text_area(locator, options={})
      options = options.merge(:text => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//textarea", options)
    end

    def select(locator, options={})
      add_field(locator, ".//select", options)
    end

    def checkbox(locator, options={})
      input_field(:checkbox, locator, options)
    end

    def radio_button(locator, options={})
      input_field(:radio, locator, options)
    end

    def file_field(locator, options={})
      input_field(:file, locator, options)
    end

  protected

    def input_field(type, locator, options={})
      options = options.merge(:value => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//input[@type='#{type}']", options)
    end

    # place this between to nodes to indicate that they should be siblings
    def sibling
      '/following-sibling::*[1]/self::'
    end

    def add_field(locator, field, options={})
      postfix = extract_postfix(options)
      xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
      xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
      xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
      # FIXME: Label should not be scoped to node, temporary workaround!!!
      xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}")
      xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
    end

    def extract_postfix(options)
      options.inject("") do |postfix, (key, value)|
        case key
          when :value     then postfix += "[@value=#{s(value)}]"
          when :text      then postfix += "[text()=#{s(value)}]"
          when :checked   then postfix += "[@checked]"
          when :unchecked then postfix += "[not(@checked)]"
          when :options   then postfix += value.map { |o| "[.//option/text()=#{s(o)}]" }.join
          when :selected  then postfix += [value].flatten.map { |o| "[.//option[@selected]/text()=#{s(o)}]" }.join
        end
        postfix
      end
    end

    # Sanitize a String for putting it into an xpath query
    def s(string)
      XPath.escape(string)
    end

  end
end
<commit_msg>Fixup documentation for XPath class<commit_after>module Capybara

  ##
  #
  # This is a class for generating XPath queries, use it like this:
  #
  #     Xpath.text_field('foo').link('blah').to_s
  #
  # This will generate an XPath that matches either a text field or a link.
  #
  class XPath

    class << self
      def escape(string)
        if string.include?("'")
          string = string.split("'", -1).map do |substr|
            "'#{substr}'"
          end.join(%q{,"'",})
          "concat(#{string})"
        else
          "'#{string}'"
        end
      end
      
      def wrap(path)
        if path.is_a?(self)
          path
        else
          new(path.to_s)
        end
      end

      def respond_to?(method)
        new.respond_to?(method)
      end

      def method_missing(*args)
        new.send(*args)
      end
    end

    attr_reader :paths

    def initialize(*paths)
      @paths = paths
    end

    def to_s
      @paths.join(' | ')
    end

    def append(path)
      XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
    end

    def prepend(path)
      XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
    end

    def from_css(css)
      XPath.new(*[@paths, Nokogiri::CSS.xpath_for(css).map { |selector| '.' + selector }].flatten)
    end
    alias_method :for_css, :from_css

    def field(locator, options={})
      if options[:with]
        fillable_field(locator, options)
      else
        xpath = fillable_field(locator)
        xpath = xpath.input_field(:file, locator, options)
        xpath = xpath.checkbox(locator, options)
        xpath = xpath.radio_button(locator, options)
        xpath.select(locator, options)
      end
    end

    def fillable_field(locator, options={})
      text_area(locator, options).text_field(locator, options)
    end

    def content(locator)
      append("./descendant-or-self::*[contains(normalize-space(.),#{s(locator)})]")
    end

    def table(locator, options={})
      conditions = ""
      if options[:rows]
        row_conditions = options[:rows].map do |row|
          row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
          "tr[./#{row}]"
        end.join(sibling)
        conditions << "[.//#{row_conditions}]"
      end
      append(".//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
    end

    def fieldset(locator)
      append(".//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
    end

    def link(locator)
      xpath = append(".//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
      xpath.prepend(".//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
    end

    def button(locator)
      xpath = append(".//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
      xpath = xpath.append(".//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
      xpath = xpath.prepend(".//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
      xpath = xpath.prepend(".//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
      xpath = xpath.prepend(".//button[@value=#{s(locator)} or text()=#{s(locator)}]")
    end

    def text_field(locator, options={})
      options = options.merge(:value => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//input[not(@type) or (@type!='radio' and @type!='checkbox' and @type!='hidden')]", options)
    end

    def text_area(locator, options={})
      options = options.merge(:text => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//textarea", options)
    end

    def select(locator, options={})
      add_field(locator, ".//select", options)
    end

    def checkbox(locator, options={})
      input_field(:checkbox, locator, options)
    end

    def radio_button(locator, options={})
      input_field(:radio, locator, options)
    end

    def file_field(locator, options={})
      input_field(:file, locator, options)
    end

  protected

    def input_field(type, locator, options={})
      options = options.merge(:value => options[:with]) if options.has_key?(:with)
      add_field(locator, ".//input[@type='#{type}']", options)
    end

    # place this between to nodes to indicate that they should be siblings
    def sibling
      '/following-sibling::*[1]/self::'
    end

    def add_field(locator, field, options={})
      postfix = extract_postfix(options)
      xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
      xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
      xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
      # FIXME: Label should not be scoped to node, temporary workaround!!!
      xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}")
      xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
    end

    def extract_postfix(options)
      options.inject("") do |postfix, (key, value)|
        case key
          when :value     then postfix += "[@value=#{s(value)}]"
          when :text      then postfix += "[text()=#{s(value)}]"
          when :checked   then postfix += "[@checked]"
          when :unchecked then postfix += "[not(@checked)]"
          when :options   then postfix += value.map { |o| "[.//option/text()=#{s(o)}]" }.join
          when :selected  then postfix += [value].flatten.map { |o| "[.//option[@selected]/text()=#{s(o)}]" }.join
        end
        postfix
      end
    end

    # Sanitize a String for putting it into an xpath query
    def s(string)
      XPath.escape(string)
    end

  end
end

{'fraction_non_alphanumeric': 0.1855359448988377, 'fraction_numerical': 0.00034438226431338786, 'mean_word_length': 2.934959349593496, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7352322', 'n_tokens_mistral': 3934, 'n_tokens_neox': 3890, 'n_words': 788}