2018-09-26 14:55:56 -07:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-10-15 18:48:15 -07:00
|
|
|
require 'regexp_parser'
|
|
|
|
|
2018-09-26 14:55:56 -07:00
|
|
|
module Capybara
|
|
|
|
class Selector
|
2018-10-01 18:02:26 -07:00
|
|
|
# @api private
|
2018-09-26 14:55:56 -07:00
|
|
|
class RegexpDisassembler
|
|
|
|
def initialize(regexp)
|
|
|
|
@regexp = regexp
|
|
|
|
end
|
|
|
|
|
2018-10-30 12:04:40 -07:00
|
|
|
def alternated_substrings
|
2018-11-04 14:37:35 -08:00
|
|
|
@alternated_substrings ||= begin
|
2018-10-30 12:04:40 -07:00
|
|
|
process(alternation: true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-26 14:55:56 -07:00
|
|
|
def substrings
|
|
|
|
@substrings ||= begin
|
2018-10-30 12:04:40 -07:00
|
|
|
process(alternation: false).first
|
2018-09-26 14:55:56 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-10-30 12:04:40 -07:00
|
|
|
def process(alternation:)
|
|
|
|
strs = extract_strings(Regexp::Parser.parse(@regexp), [''], alternation: alternation)
|
2018-11-04 14:37:35 -08:00
|
|
|
strs = collapse(combine(strs).map(&:flatten))
|
2018-10-30 12:04:40 -07:00
|
|
|
strs.each { |str| str.map!(&:upcase) } if @regexp.casefold?
|
|
|
|
strs
|
|
|
|
end
|
|
|
|
|
2018-10-15 18:48:15 -07:00
|
|
|
def min_repeat(exp)
|
|
|
|
exp.quantifier&.min || 1
|
|
|
|
end
|
|
|
|
|
|
|
|
def fixed_repeat?(exp)
|
|
|
|
min_repeat(exp) == (exp.quantifier&.max || 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
def optional?(exp)
|
|
|
|
min_repeat(exp).zero?
|
2018-09-26 14:55:56 -07:00
|
|
|
end
|
|
|
|
|
2018-10-30 12:04:40 -07:00
|
|
|
def combine(strs)
|
|
|
|
suffixes = [[]]
|
|
|
|
strs.reverse_each do |str|
|
|
|
|
if str.is_a? Set
|
|
|
|
prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }
|
|
|
|
|
|
|
|
result = []
|
|
|
|
prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
|
|
|
|
suffixes = result
|
|
|
|
else
|
|
|
|
suffixes.each do |arr|
|
|
|
|
arr.unshift str
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
suffixes
|
|
|
|
end
|
|
|
|
|
|
|
|
def collapse(strs)
|
|
|
|
strs.map do |substrings|
|
2018-11-04 14:37:35 -08:00
|
|
|
substrings.slice_before(&:empty?).map(&:join).reject(&:empty?).uniq
|
2018-10-30 12:04:40 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def extract_strings(expression, strings, alternation: false)
|
2018-10-15 18:48:15 -07:00
|
|
|
expression.each do |exp|
|
|
|
|
if optional?(exp)
|
2018-10-30 12:04:40 -07:00
|
|
|
strings.push('')
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if %i[meta].include?(exp.type) && !exp.terminal? && alternation
|
2018-11-04 14:37:35 -08:00
|
|
|
strings.push(alternative_strings(exp))
|
2018-10-15 18:48:15 -07:00
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if %i[meta set].include?(exp.type)
|
2018-10-30 12:04:40 -07:00
|
|
|
strings.push('')
|
2018-10-15 18:48:15 -07:00
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if exp.terminal?
|
|
|
|
case exp.type
|
|
|
|
when :literal
|
2018-11-04 14:37:35 -08:00
|
|
|
strings.push(exp.text * min_repeat(exp))
|
2018-10-15 18:48:15 -07:00
|
|
|
when :escape
|
2018-11-04 14:37:35 -08:00
|
|
|
strings.push(exp.char * min_repeat(exp))
|
2018-10-15 18:48:15 -07:00
|
|
|
else
|
2018-10-30 12:04:40 -07:00
|
|
|
strings.push('')
|
2018-10-15 18:48:15 -07:00
|
|
|
end
|
|
|
|
else
|
2018-10-30 12:04:40 -07:00
|
|
|
min_repeat(exp).times { extract_strings(exp, strings, alternation: alternation) }
|
2018-10-15 18:48:15 -07:00
|
|
|
end
|
2018-10-30 12:04:40 -07:00
|
|
|
strings.push('') unless fixed_repeat?(exp)
|
2018-10-15 18:48:15 -07:00
|
|
|
end
|
|
|
|
strings
|
|
|
|
end
|
2018-11-04 14:37:35 -08:00
|
|
|
|
|
|
|
def alternative_strings(expression)
|
|
|
|
alternatives = expression.alternatives.map { |sub_exp| extract_strings(sub_exp, [], alternation: true) }
|
|
|
|
if alternatives.all? { |alt| alt.any? { |a| !a.empty? } }
|
|
|
|
Set.new(alternatives)
|
|
|
|
else
|
|
|
|
strings.push('')
|
|
|
|
end
|
|
|
|
end
|
2018-09-26 14:55:56 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|