Class: Marta::XPath::XPathFactory

Inherits:
Object
  • Object
show all
Includes:
UserValuePrework
Defined in:
lib/marta/x_path.rb

Overview

Note:

It is believed that no user will use it

Here we are creating xpath including arrays of xpaths with one-two-…-x parts that are not known

All xpaths for Marta are constructed out of three parts: granny, pappy, and self. Where self is a xpath for DOM element itself, pappy is for father element, granny is for grandfather. For example //DIV/SPAN/INPUT: //DIV = granny, /SPAN = pappy, /INPUT = self.

We are generating special arrays of hashes at first for each part. And then we are constructing final xpaths

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(meth, requestor) ⇒ XPathFactory

Returns a new instance of XPathFactory.



28
29
30
31
32
# File 'lib/marta/x_path.rb', line 28

def initialize(meth, requestor)
  @meth = meth
  @granny = @pappy = true
  @requestor = requestor
end

Instance Attribute Details

#grannyObject

Returns the value of attribute granny.



27
28
29
# File 'lib/marta/x_path.rb', line 27

def granny
  @granny
end

#pappyObject

Returns the value of attribute pappy.



27
28
29
# File 'lib/marta/x_path.rb', line 27

def pappy
  @pappy
end

Instance Method Details

#analyze(depth, limit) ⇒ Object

We are trying to understand here how much work should we do in order to generate all possible xpaths variants.

depth is suggested amount of unstable xpath parts limit is the maximum amount of xpaths that we want to generate If we can try all the combinations of xpaths with considering depth elements unstable withou reaching the limit of tries, method will return that depth and precreated array_of_hashes If limit will be reached on creation of all xpaths, method is returning last acceptable depth and array_of_hashes



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/marta/x_path.rb', line 44

def analyze(depth, limit)
  hashes = array_of_hashes
  count = 1
  real_depth = 0
  variativity = 0
  hashes.each do |hash|
    variativity += (hash[:empty] - hash[:full]).count
  end
  depth.times do
    count = count * variativity
    if count < limit
      real_depth += 1
    end
  end
  return real_depth, hashes
end

#array_of_hashesObject

We are parsing stored element data (tag, text and attributes) into the array of hashes

Output looks like: ["//", "H1", ”],empty[“”, “[@*[contains(.,‘x’)]]”]]



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/marta/x_path.rb', line 200

def array_of_hashes
  result = Array.new
  result.push make_hash('//', '//')
  if @granny
    result = result +
             positive_part_of_array_of_hashes('granny') +
             negative_part_of_array_of_hashes('granny')
    result.push make_hash('/', '//')
  end
  if @pappy
    result = result +
             positive_part_of_array_of_hashes('pappy') +
             negative_part_of_array_of_hashes('pappy')
    result.push make_hash('/', '//')
  end
  result = result +
           positive_part_of_array_of_hashes('self') +
           negative_part_of_array_of_hashes('self')
  result
end

#generate_xpathObject

We can generate straight xpath by all known data



148
149
150
151
152
153
154
# File 'lib/marta/x_path.rb', line 148

def generate_xpath
  result = ''
  array_of_hashes.each do |hash|
    result = result + hash[:full][0]
  end
  process_string result, @requestor
end

#generate_xpaths(depth, limit = 100000) ⇒ Object

Full logic of xpath generating We are understanding can we find all the possible xpath variations We are creating all possible masks (arrays of switches) one by one If we know that we cannot find all variants we are generating some by more random algorithm



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/marta/x_path.rb', line 134

def generate_xpaths(depth, limit = 100000)
  xpaths = Array.new
  real_depth, hashes = analyze(depth, limit)
  masks = get_masks([Array.new(hashes.count, :full)], real_depth)
  masks.each do |mask|
    xpaths = xpaths + xpaths_by_mask(mask, hashes)
  end
  if real_depth != depth
    xpaths = xpaths + monte_carlo(hashes, depth, limit)
  end
  xpaths.uniq.map {|xpath| process_string(xpath, @requestor)}
end

#get_masks(masks, depth) ⇒ Object

We are generating masks like [:empty,:full,:full,:empty] They are used for more understandable logic of looping xpaths variants In fact they are lists with all the combinations of switches :full:empty. Where the length of switches is the same as length of xpath parts. And amount of :empty switches is depth



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/marta/x_path.rb', line 85

def get_masks(masks, depth)
  result = Array.new
  masks.each do |mask|
    result.push mask
    for i in 0..mask.count-1
      result.push(mask.map { |e| e.dup })
      result.last[i] = :empty
    end
  end
  if depth-1 == 0
    result
  else
    get_masks(result, depth-1)
  end
end

#make_hash(full, empty = '') ⇒ Object

Creating the smallest possible part of array hash



222
223
224
# File 'lib/marta/x_path.rb', line 222

def make_hash(full, empty = '')
  {full: [full], empty: empty.class != Array ? [empty] : empty}
end

#monte_carlo(hashes, depth, limit) ⇒ Object

Generating not more than limit random xpaths variants considering that depth parts of xpath are unstable



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/marta/x_path.rb', line 63

def monte_carlo(hashes, depth, limit)
  xpaths = Array.new
  while xpaths.count < limit do
    mask = Array.new hashes.count, :full
    depth.times do
      mask[rand(mask.count)] = :empty
    end
    final_array = Array.new
    hashes.each_with_index do |hash, index|
      final_array.push(hash[mask[index]].sample)
    end
    xpaths.push final_array.join
    xpaths
  end
  xpaths
end

#negative_part_of_array_of_hashes(what) ⇒ Object

We are parsing negative part of element data to array of hashes



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/marta/x_path.rb', line 175

def negative_part_of_array_of_hashes(what)
  result = Array.new
  @meth['negative'][what]['tag'].each do |not_tag|
    result.push make_hash("[not(self::#{not_tag})]", '')
  end
  @meth['negative'][what]['text'].each do |not_text|
    result.push make_hash("[not(contains(text(),'#{not_text}'))]", '')
  end
  @meth['negative'][what]['attributes'].each_pair do |attribute, values|
    if (values != []) and (values != ['']) and !values.nil?
      values.each do |value|
        result.push make_hash("[not(contains(@#{attribute},'#{value}'))]")
      end
    end
  end
  result
end

#positive_part_of_array_of_hashes(what) ⇒ Object

We are parsing positive part of element data to array of hashes



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/marta/x_path.rb', line 157

def positive_part_of_array_of_hashes(what)
  result = Array.new
  result.push make_hash(@meth['positive'][what]['tag'] != [] ? @meth['positive'][what]['tag'][0] : '*', '*')
  if (@meth['positive'][what]['text'] != []) and (@meth['positive'][what]['text'] != [''])
    result.push make_hash("[contains(text(),'#{@meth['positive'][what]['text'][0]}')]")
  end
  @meth['positive'][what]['attributes'].each_pair do |attribute, values|
    if (values != []) and (values != ['']) and !values.nil?
      values.each do |value|
        result.push make_hash("[contains(@#{attribute},'#{value}')]",
                              ["[@*[contains(.,'#{value}')]]", ""])
      end
    end
  end
  result
end

#xpaths_by_mask(mask, hashes) ⇒ Object

We are forming xpath strings by masks and hashes with data



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/marta/x_path.rb', line 102

def xpaths_by_mask(mask, hashes)
  xpaths = Array.new
  final_array = [[]]
  hashes.each_with_index do |hash, index|
    hash[mask[index]].each_with_index do |hash_value, empty_index|
      if empty_index == 0
        final_array.each do |final_array_item|
          final_array_item.push(hash_value)
        end
      else
        alternative_final_array = []
        final_array.each do |final_array_item|
          alternative_final_array.push final_array_item.dup
        end
        alternative_final_array.each do |a_final_array_item|
          a_final_array_item[-1] = hash_value
        end
        final_array = final_array + alternative_final_array
      end
    end
  end
  final_array.each do |final_array_item|
    xpaths.push final_array_item.join
  end
  xpaths
end