class Capybara::Selector::RegexpDisassembler
@api private
def alternated_substrings
def alternated_substrings @alternated_substrings ||= begin or_strings = process(alternation: true) remove_or_covered(or_strings) or_strings.any?(&:empty?) ? [] : or_strings end end
def collapse(strs)
def collapse(strs) strs.map do |substrings| substrings.slice_before(&:nil?).map(&:join).reject(&:empty?).uniq end end
def combine(strs)
def combine(strs) suffixes = [[]] strs.reverse_each do |str| if str.is_a? Set prefixes = str.flat_map { |s| combine(s) } suffixes = prefixes.product(suffixes).map { |pair| pair.flatten(1) } else suffixes.each { |arr| arr.unshift str } end end suffixes end
def extract_strings(expression, alternation: false)
def extract_strings(expression, alternation: false) Expression.new(expression).extract_strings(alternation) end
def initialize(regexp)
def initialize(regexp) @regexp = regexp end
def process(alternation:)
def process(alternation:) strs = extract_strings(Regexp::Parser.parse(@regexp), alternation: alternation) strs = collapse(combine(strs).map(&:flatten)) strs.each { |str| str.map!(&:upcase) } if @regexp.casefold? strs end
def remove_and_covered(strings)
def remove_and_covered(strings) # delete_if is documented to modify the array after every block iteration - this doesn't appear to be true # uniq the strings to prevent identical strings from removing each other strings.uniq! # If we have "ab" and "abcd" required - only need to check for "abcd" strings.delete_if do |sub_string| strings.any? do |cover_string| next if sub_string.equal? cover_string cover_string.include?(sub_string) end end end
def remove_or_covered(or_series)
def remove_or_covered(or_series) # If we are going to match `("a" and "b") or ("ade" and "bce")` it only makes sense to match ("a" and "b") # Ensure minimum sets of strings are being or'd or_series.each { |strs| remove_and_covered(strs) } # Remove any of the alternated string series that fully contain any other string series or_series.delete_if do |and_strs| or_series.any? do |and_strs2| next if and_strs.equal? and_strs2 remove_and_covered(and_strs + and_strs2) == and_strs end end end
def substrings
def substrings @substrings ||= begin strs = process(alternation: false).first remove_and_covered(strs) end end