brakeman 4.10.1 → 5.0.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +9 -7
  3. data/README.md +1 -1
  4. data/bundle/load.rb +8 -9
  5. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/CHANGELOG.md +1 -8
  6. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/FAQ.md +0 -0
  7. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/Gemfile +0 -0
  8. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/MIT-LICENSE +0 -0
  9. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/README.md +0 -0
  10. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/REFERENCE.md +5 -9
  11. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/TODO +0 -0
  12. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/haml.gemspec +1 -1
  13. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml.rb +0 -0
  14. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/attribute_builder.rb +0 -0
  15. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/attribute_compiler.rb +0 -0
  16. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/attribute_parser.rb +0 -0
  17. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/buffer.rb +0 -0
  18. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/compiler.rb +0 -0
  19. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/engine.rb +0 -0
  20. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/error.rb +0 -0
  21. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/escapable.rb +0 -0
  22. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/exec.rb +0 -0
  23. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/filters.rb +0 -0
  24. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/generator.rb +0 -0
  25. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers.rb +0 -0
  26. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/action_view_extensions.rb +0 -0
  27. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/action_view_mods.rb +0 -0
  28. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/action_view_xss_mods.rb +0 -0
  29. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/safe_erubi_template.rb +0 -0
  30. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/safe_erubis_template.rb +0 -0
  31. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/helpers/xss_mods.rb +0 -0
  32. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/options.rb +0 -0
  33. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/parser.rb +3 -31
  34. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/plugin.rb +0 -0
  35. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/railtie.rb +0 -0
  36. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/sass_rails_filter.rb +0 -0
  37. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/template.rb +0 -0
  38. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/template/options.rb +0 -0
  39. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/temple_engine.rb +0 -0
  40. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/temple_line_counter.rb +0 -0
  41. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/util.rb +1 -1
  42. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/lib/haml/version.rb +1 -1
  43. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/yard/default/fulldoc/html/css/common.sass +0 -0
  44. data/bundle/ruby/2.7.0/gems/{haml-5.2.1 → haml-5.2.0}/yard/default/layout/html/footer.erb +0 -0
  45. data/lib/brakeman.rb +6 -0
  46. data/lib/brakeman/app_tree.rb +36 -3
  47. data/lib/brakeman/checks/check_execute.rb +1 -1
  48. data/lib/brakeman/checks/check_regex_dos.rb +1 -1
  49. data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
  50. data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
  51. data/lib/brakeman/file_parser.rb +19 -23
  52. data/lib/brakeman/options.rb +5 -1
  53. data/lib/brakeman/parsers/template_parser.rb +2 -3
  54. data/lib/brakeman/processors/alias_processor.rb +2 -2
  55. data/lib/brakeman/processors/controller_processor.rb +1 -1
  56. data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
  57. data/lib/brakeman/processors/output_processor.rb +1 -1
  58. data/lib/brakeman/processors/template_alias_processor.rb +0 -5
  59. data/lib/brakeman/report.rb +8 -0
  60. data/lib/brakeman/report/report_sonar.rb +38 -0
  61. data/lib/brakeman/rescanner.rb +7 -5
  62. data/lib/brakeman/scanner.rb +42 -18
  63. data/lib/brakeman/tracker.rb +6 -0
  64. data/lib/brakeman/tracker/controller.rb +1 -1
  65. data/lib/brakeman/util.rb +9 -4
  66. data/lib/brakeman/version.rb +1 -1
  67. data/lib/brakeman/warning_codes.rb +2 -0
  68. data/lib/ruby_parser/bm_sexp.rb +9 -9
  69. metadata +49 -99
  70. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/Gemfile +0 -6
  71. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/LICENSE.txt +0 -22
  72. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/NEWS.md +0 -141
  73. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/README.md +0 -60
  74. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/attlistdecl.rb +0 -63
  75. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/attribute.rb +0 -205
  76. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/cdata.rb +0 -68
  77. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/child.rb +0 -97
  78. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/comment.rb +0 -80
  79. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/doctype.rb +0 -287
  80. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/document.rb +0 -291
  81. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/attlistdecl.rb +0 -11
  82. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/dtd.rb +0 -47
  83. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/elementdecl.rb +0 -18
  84. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/entitydecl.rb +0 -57
  85. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/notationdecl.rb +0 -40
  86. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/element.rb +0 -1269
  87. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/encoding.rb +0 -51
  88. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/entity.rb +0 -171
  89. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/default.rb +0 -116
  90. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/pretty.rb +0 -142
  91. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/transitive.rb +0 -58
  92. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/functions.rb +0 -447
  93. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/instruction.rb +0 -79
  94. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/light/node.rb +0 -196
  95. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/namespace.rb +0 -59
  96. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/node.rb +0 -76
  97. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/output.rb +0 -30
  98. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parent.rb +0 -166
  99. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parseexception.rb +0 -52
  100. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/baseparser.rb +0 -594
  101. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/lightparser.rb +0 -59
  102. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/pullparser.rb +0 -197
  103. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/sax2parser.rb +0 -273
  104. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/streamparser.rb +0 -61
  105. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/treeparser.rb +0 -101
  106. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/ultralightparser.rb +0 -57
  107. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/xpathparser.rb +0 -675
  108. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/quickpath.rb +0 -266
  109. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/rexml.rb +0 -32
  110. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/sax2listener.rb +0 -98
  111. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/security.rb +0 -28
  112. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/source.rb +0 -298
  113. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/streamlistener.rb +0 -93
  114. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/text.rb +0 -424
  115. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/undefinednamespaceexception.rb +0 -9
  116. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/relaxng.rb +0 -539
  117. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/validation.rb +0 -144
  118. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/validationexception.rb +0 -10
  119. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xmldecl.rb +0 -130
  120. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xmltokens.rb +0 -85
  121. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xpath.rb +0 -81
  122. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xpath_parser.rb +0 -968
  123. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/rexml.gemspec +0 -84
@@ -1,968 +0,0 @@
1
- # frozen_string_literal: false
2
-
3
- require "pp"
4
-
5
- require_relative 'namespace'
6
- require_relative 'xmltokens'
7
- require_relative 'attribute'
8
- require_relative 'parsers/xpathparser'
9
-
10
- class Object
11
- # provides a unified +clone+ operation, for REXML::XPathParser
12
- # to use across multiple Object types
13
- def dclone
14
- clone
15
- end
16
- end
17
- class Symbol
18
- # provides a unified +clone+ operation, for REXML::XPathParser
19
- # to use across multiple Object types
20
- def dclone ; self ; end
21
- end
22
- class Integer
23
- # provides a unified +clone+ operation, for REXML::XPathParser
24
- # to use across multiple Object types
25
- def dclone ; self ; end
26
- end
27
- class Float
28
- # provides a unified +clone+ operation, for REXML::XPathParser
29
- # to use across multiple Object types
30
- def dclone ; self ; end
31
- end
32
- class Array
33
- # provides a unified +clone+ operation, for REXML::XPathParser
34
- # to use across multiple Object+ types
35
- def dclone
36
- klone = self.clone
37
- klone.clear
38
- self.each{|v| klone << v.dclone}
39
- klone
40
- end
41
- end
42
-
43
- module REXML
44
- # You don't want to use this class. Really. Use XPath, which is a wrapper
45
- # for this class. Believe me. You don't want to poke around in here.
46
- # There is strange, dark magic at work in this code. Beware. Go back! Go
47
- # back while you still can!
48
- class XPathParser
49
- include XMLTokens
50
- LITERAL = /^'([^']*)'|^"([^"]*)"/u
51
-
52
- DEBUG = (ENV["REXML_XPATH_PARSER_DEBUG"] == "true")
53
-
54
- def initialize(strict: false)
55
- @debug = DEBUG
56
- @parser = REXML::Parsers::XPathParser.new
57
- @namespaces = nil
58
- @variables = {}
59
- @nest = 0
60
- @strict = strict
61
- end
62
-
63
- def namespaces=( namespaces={} )
64
- Functions::namespace_context = namespaces
65
- @namespaces = namespaces
66
- end
67
-
68
- def variables=( vars={} )
69
- Functions::variables = vars
70
- @variables = vars
71
- end
72
-
73
- def parse path, nodeset
74
- path_stack = @parser.parse( path )
75
- match( path_stack, nodeset )
76
- end
77
-
78
- def get_first path, nodeset
79
- path_stack = @parser.parse( path )
80
- first( path_stack, nodeset )
81
- end
82
-
83
- def predicate path, nodeset
84
- path_stack = @parser.parse( path )
85
- match( path_stack, nodeset )
86
- end
87
-
88
- def []=( variable_name, value )
89
- @variables[ variable_name ] = value
90
- end
91
-
92
-
93
- # Performs a depth-first (document order) XPath search, and returns the
94
- # first match. This is the fastest, lightest way to return a single result.
95
- #
96
- # FIXME: This method is incomplete!
97
- def first( path_stack, node )
98
- return nil if path.size == 0
99
-
100
- case path[0]
101
- when :document
102
- # do nothing
103
- return first( path[1..-1], node )
104
- when :child
105
- for c in node.children
106
- r = first( path[1..-1], c )
107
- return r if r
108
- end
109
- when :qname
110
- name = path[2]
111
- if node.name == name
112
- return node if path.size == 3
113
- return first( path[3..-1], node )
114
- else
115
- return nil
116
- end
117
- when :descendant_or_self
118
- r = first( path[1..-1], node )
119
- return r if r
120
- for c in node.children
121
- r = first( path, c )
122
- return r if r
123
- end
124
- when :node
125
- return first( path[1..-1], node )
126
- when :any
127
- return first( path[1..-1], node )
128
- end
129
- return nil
130
- end
131
-
132
-
133
- def match(path_stack, nodeset)
134
- nodeset = nodeset.collect.with_index do |node, i|
135
- position = i + 1
136
- XPathNode.new(node, position: position)
137
- end
138
- result = expr(path_stack, nodeset)
139
- case result
140
- when Array # nodeset
141
- unnode(result)
142
- else
143
- [result]
144
- end
145
- end
146
-
147
- private
148
- def strict?
149
- @strict
150
- end
151
-
152
- # Returns a String namespace for a node, given a prefix
153
- # The rules are:
154
- #
155
- # 1. Use the supplied namespace mapping first.
156
- # 2. If no mapping was supplied, use the context node to look up the namespace
157
- def get_namespace( node, prefix )
158
- if @namespaces
159
- return @namespaces[prefix] || ''
160
- else
161
- return node.namespace( prefix ) if node.node_type == :element
162
- return ''
163
- end
164
- end
165
-
166
-
167
- # Expr takes a stack of path elements and a set of nodes (either a Parent
168
- # or an Array and returns an Array of matching nodes
169
- def expr( path_stack, nodeset, context=nil )
170
- enter(:expr, path_stack, nodeset) if @debug
171
- return nodeset if path_stack.length == 0 || nodeset.length == 0
172
- while path_stack.length > 0
173
- trace(:while, path_stack, nodeset) if @debug
174
- if nodeset.length == 0
175
- path_stack.clear
176
- return []
177
- end
178
- op = path_stack.shift
179
- case op
180
- when :document
181
- first_raw_node = nodeset.first.raw_node
182
- nodeset = [XPathNode.new(first_raw_node.root_node, position: 1)]
183
- when :self
184
- nodeset = step(path_stack) do
185
- [nodeset]
186
- end
187
- when :child
188
- nodeset = step(path_stack) do
189
- child(nodeset)
190
- end
191
- when :literal
192
- trace(:literal, path_stack, nodeset) if @debug
193
- return path_stack.shift
194
- when :attribute
195
- nodeset = step(path_stack, any_type: :attribute) do
196
- nodesets = []
197
- nodeset.each do |node|
198
- raw_node = node.raw_node
199
- next unless raw_node.node_type == :element
200
- attributes = raw_node.attributes
201
- next if attributes.empty?
202
- nodesets << attributes.each_attribute.collect.with_index do |attribute, i|
203
- XPathNode.new(attribute, position: i + 1)
204
- end
205
- end
206
- nodesets
207
- end
208
- when :namespace
209
- pre_defined_namespaces = {
210
- "xml" => "http://www.w3.org/XML/1998/namespace",
211
- }
212
- nodeset = step(path_stack, any_type: :namespace) do
213
- nodesets = []
214
- nodeset.each do |node|
215
- raw_node = node.raw_node
216
- case raw_node.node_type
217
- when :element
218
- if @namespaces
219
- nodesets << pre_defined_namespaces.merge(@namespaces)
220
- else
221
- nodesets << pre_defined_namespaces.merge(raw_node.namespaces)
222
- end
223
- when :attribute
224
- if @namespaces
225
- nodesets << pre_defined_namespaces.merge(@namespaces)
226
- else
227
- nodesets << pre_defined_namespaces.merge(raw_node.element.namespaces)
228
- end
229
- end
230
- end
231
- nodesets
232
- end
233
- when :parent
234
- nodeset = step(path_stack) do
235
- nodesets = []
236
- nodeset.each do |node|
237
- raw_node = node.raw_node
238
- if raw_node.node_type == :attribute
239
- parent = raw_node.element
240
- else
241
- parent = raw_node.parent
242
- end
243
- nodesets << [XPathNode.new(parent, position: 1)] if parent
244
- end
245
- nodesets
246
- end
247
- when :ancestor
248
- nodeset = step(path_stack) do
249
- nodesets = []
250
- # new_nodes = {}
251
- nodeset.each do |node|
252
- raw_node = node.raw_node
253
- new_nodeset = []
254
- while raw_node.parent
255
- raw_node = raw_node.parent
256
- # next if new_nodes.key?(node)
257
- new_nodeset << XPathNode.new(raw_node,
258
- position: new_nodeset.size + 1)
259
- # new_nodes[node] = true
260
- end
261
- nodesets << new_nodeset unless new_nodeset.empty?
262
- end
263
- nodesets
264
- end
265
- when :ancestor_or_self
266
- nodeset = step(path_stack) do
267
- nodesets = []
268
- # new_nodes = {}
269
- nodeset.each do |node|
270
- raw_node = node.raw_node
271
- next unless raw_node.node_type == :element
272
- new_nodeset = [XPathNode.new(raw_node, position: 1)]
273
- # new_nodes[node] = true
274
- while raw_node.parent
275
- raw_node = raw_node.parent
276
- # next if new_nodes.key?(node)
277
- new_nodeset << XPathNode.new(raw_node,
278
- position: new_nodeset.size + 1)
279
- # new_nodes[node] = true
280
- end
281
- nodesets << new_nodeset unless new_nodeset.empty?
282
- end
283
- nodesets
284
- end
285
- when :descendant_or_self
286
- nodeset = step(path_stack) do
287
- descendant(nodeset, true)
288
- end
289
- when :descendant
290
- nodeset = step(path_stack) do
291
- descendant(nodeset, false)
292
- end
293
- when :following_sibling
294
- nodeset = step(path_stack) do
295
- nodesets = []
296
- nodeset.each do |node|
297
- raw_node = node.raw_node
298
- next unless raw_node.respond_to?(:parent)
299
- next if raw_node.parent.nil?
300
- all_siblings = raw_node.parent.children
301
- current_index = all_siblings.index(raw_node)
302
- following_siblings = all_siblings[(current_index + 1)..-1]
303
- next if following_siblings.empty?
304
- nodesets << following_siblings.collect.with_index do |sibling, i|
305
- XPathNode.new(sibling, position: i + 1)
306
- end
307
- end
308
- nodesets
309
- end
310
- when :preceding_sibling
311
- nodeset = step(path_stack, order: :reverse) do
312
- nodesets = []
313
- nodeset.each do |node|
314
- raw_node = node.raw_node
315
- next unless raw_node.respond_to?(:parent)
316
- next if raw_node.parent.nil?
317
- all_siblings = raw_node.parent.children
318
- current_index = all_siblings.index(raw_node)
319
- preceding_siblings = all_siblings[0, current_index].reverse
320
- next if preceding_siblings.empty?
321
- nodesets << preceding_siblings.collect.with_index do |sibling, i|
322
- XPathNode.new(sibling, position: i + 1)
323
- end
324
- end
325
- nodesets
326
- end
327
- when :preceding
328
- nodeset = step(path_stack, order: :reverse) do
329
- unnode(nodeset) do |node|
330
- preceding(node)
331
- end
332
- end
333
- when :following
334
- nodeset = step(path_stack) do
335
- unnode(nodeset) do |node|
336
- following(node)
337
- end
338
- end
339
- when :variable
340
- var_name = path_stack.shift
341
- return [@variables[var_name]]
342
-
343
- when :eq, :neq, :lt, :lteq, :gt, :gteq
344
- left = expr( path_stack.shift, nodeset.dup, context )
345
- right = expr( path_stack.shift, nodeset.dup, context )
346
- res = equality_relational_compare( left, op, right )
347
- trace(op, left, right, res) if @debug
348
- return res
349
-
350
- when :or
351
- left = expr(path_stack.shift, nodeset.dup, context)
352
- return true if Functions.boolean(left)
353
- right = expr(path_stack.shift, nodeset.dup, context)
354
- return Functions.boolean(right)
355
-
356
- when :and
357
- left = expr(path_stack.shift, nodeset.dup, context)
358
- return false unless Functions.boolean(left)
359
- right = expr(path_stack.shift, nodeset.dup, context)
360
- return Functions.boolean(right)
361
-
362
- when :div, :mod, :mult, :plus, :minus
363
- left = expr(path_stack.shift, nodeset, context)
364
- right = expr(path_stack.shift, nodeset, context)
365
- left = unnode(left) if left.is_a?(Array)
366
- right = unnode(right) if right.is_a?(Array)
367
- left = Functions::number(left)
368
- right = Functions::number(right)
369
- case op
370
- when :div
371
- return left / right
372
- when :mod
373
- return left % right
374
- when :mult
375
- return left * right
376
- when :plus
377
- return left + right
378
- when :minus
379
- return left - right
380
- else
381
- raise "[BUG] Unexpected operator: <#{op.inspect}>"
382
- end
383
- when :union
384
- left = expr( path_stack.shift, nodeset, context )
385
- right = expr( path_stack.shift, nodeset, context )
386
- left = unnode(left) if left.is_a?(Array)
387
- right = unnode(right) if right.is_a?(Array)
388
- return (left | right)
389
- when :neg
390
- res = expr( path_stack, nodeset, context )
391
- res = unnode(res) if res.is_a?(Array)
392
- return -Functions.number(res)
393
- when :not
394
- when :function
395
- func_name = path_stack.shift.tr('-','_')
396
- arguments = path_stack.shift
397
-
398
- if nodeset.size != 1
399
- message = "[BUG] Node set size must be 1 for function call: "
400
- message += "<#{func_name}>: <#{nodeset.inspect}>: "
401
- message += "<#{arguments.inspect}>"
402
- raise message
403
- end
404
-
405
- node = nodeset.first
406
- if context
407
- target_context = context
408
- else
409
- target_context = {:size => nodeset.size}
410
- if node.is_a?(XPathNode)
411
- target_context[:node] = node.raw_node
412
- target_context[:index] = node.position
413
- else
414
- target_context[:node] = node
415
- target_context[:index] = 1
416
- end
417
- end
418
- args = arguments.dclone.collect do |arg|
419
- result = expr(arg, nodeset, target_context)
420
- result = unnode(result) if result.is_a?(Array)
421
- result
422
- end
423
- Functions.context = target_context
424
- return Functions.send(func_name, *args)
425
-
426
- else
427
- raise "[BUG] Unexpected path: <#{op.inspect}>: <#{path_stack.inspect}>"
428
- end
429
- end # while
430
- return nodeset
431
- ensure
432
- leave(:expr, path_stack, nodeset) if @debug
433
- end
434
-
435
- def step(path_stack, any_type: :element, order: :forward)
436
- nodesets = yield
437
- begin
438
- enter(:step, path_stack, nodesets) if @debug
439
- nodesets = node_test(path_stack, nodesets, any_type: any_type)
440
- while path_stack[0] == :predicate
441
- path_stack.shift # :predicate
442
- predicate_expression = path_stack.shift.dclone
443
- nodesets = evaluate_predicate(predicate_expression, nodesets)
444
- end
445
- if nodesets.size == 1
446
- ordered_nodeset = nodesets[0]
447
- else
448
- raw_nodes = []
449
- nodesets.each do |nodeset|
450
- nodeset.each do |node|
451
- if node.respond_to?(:raw_node)
452
- raw_nodes << node.raw_node
453
- else
454
- raw_nodes << node
455
- end
456
- end
457
- end
458
- ordered_nodeset = sort(raw_nodes, order)
459
- end
460
- new_nodeset = []
461
- ordered_nodeset.each do |node|
462
- # TODO: Remove duplicated
463
- new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
464
- end
465
- new_nodeset
466
- ensure
467
- leave(:step, path_stack, new_nodeset) if @debug
468
- end
469
- end
470
-
471
- def node_test(path_stack, nodesets, any_type: :element)
472
- enter(:node_test, path_stack, nodesets) if @debug
473
- operator = path_stack.shift
474
- case operator
475
- when :qname
476
- prefix = path_stack.shift
477
- name = path_stack.shift
478
- new_nodesets = nodesets.collect do |nodeset|
479
- filter_nodeset(nodeset) do |node|
480
- raw_node = node.raw_node
481
- case raw_node.node_type
482
- when :element
483
- if prefix.nil?
484
- raw_node.name == name
485
- elsif prefix.empty?
486
- if strict?
487
- raw_node.name == name and raw_node.namespace == ""
488
- else
489
- # FIXME: This DOUBLES the time XPath searches take
490
- ns = get_namespace(raw_node, prefix)
491
- raw_node.name == name and raw_node.namespace == ns
492
- end
493
- else
494
- # FIXME: This DOUBLES the time XPath searches take
495
- ns = get_namespace(raw_node, prefix)
496
- raw_node.name == name and raw_node.namespace == ns
497
- end
498
- when :attribute
499
- if prefix.nil?
500
- raw_node.name == name
501
- elsif prefix.empty?
502
- raw_node.name == name and raw_node.namespace == ""
503
- else
504
- # FIXME: This DOUBLES the time XPath searches take
505
- ns = get_namespace(raw_node.element, prefix)
506
- raw_node.name == name and raw_node.namespace == ns
507
- end
508
- else
509
- false
510
- end
511
- end
512
- end
513
- when :namespace
514
- prefix = path_stack.shift
515
- new_nodesets = nodesets.collect do |nodeset|
516
- filter_nodeset(nodeset) do |node|
517
- raw_node = node.raw_node
518
- case raw_node.node_type
519
- when :element
520
- namespaces = @namespaces || raw_node.namespaces
521
- raw_node.namespace == namespaces[prefix]
522
- when :attribute
523
- namespaces = @namespaces || raw_node.element.namespaces
524
- raw_node.namespace == namespaces[prefix]
525
- else
526
- false
527
- end
528
- end
529
- end
530
- when :any
531
- new_nodesets = nodesets.collect do |nodeset|
532
- filter_nodeset(nodeset) do |node|
533
- raw_node = node.raw_node
534
- raw_node.node_type == any_type
535
- end
536
- end
537
- when :comment
538
- new_nodesets = nodesets.collect do |nodeset|
539
- filter_nodeset(nodeset) do |node|
540
- raw_node = node.raw_node
541
- raw_node.node_type == :comment
542
- end
543
- end
544
- when :text
545
- new_nodesets = nodesets.collect do |nodeset|
546
- filter_nodeset(nodeset) do |node|
547
- raw_node = node.raw_node
548
- raw_node.node_type == :text
549
- end
550
- end
551
- when :processing_instruction
552
- target = path_stack.shift
553
- new_nodesets = nodesets.collect do |nodeset|
554
- filter_nodeset(nodeset) do |node|
555
- raw_node = node.raw_node
556
- (raw_node.node_type == :processing_instruction) and
557
- (target.empty? or (raw_node.target == target))
558
- end
559
- end
560
- when :node
561
- new_nodesets = nodesets.collect do |nodeset|
562
- filter_nodeset(nodeset) do |node|
563
- true
564
- end
565
- end
566
- else
567
- message = "[BUG] Unexpected node test: " +
568
- "<#{operator.inspect}>: <#{path_stack.inspect}>"
569
- raise message
570
- end
571
- new_nodesets
572
- ensure
573
- leave(:node_test, path_stack, new_nodesets) if @debug
574
- end
575
-
576
- def filter_nodeset(nodeset)
577
- new_nodeset = []
578
- nodeset.each do |node|
579
- next unless yield(node)
580
- new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
581
- end
582
- new_nodeset
583
- end
584
-
585
- def evaluate_predicate(expression, nodesets)
586
- enter(:predicate, expression, nodesets) if @debug
587
- new_nodesets = nodesets.collect do |nodeset|
588
- new_nodeset = []
589
- subcontext = { :size => nodeset.size }
590
- nodeset.each_with_index do |node, index|
591
- if node.is_a?(XPathNode)
592
- subcontext[:node] = node.raw_node
593
- subcontext[:index] = node.position
594
- else
595
- subcontext[:node] = node
596
- subcontext[:index] = index + 1
597
- end
598
- result = expr(expression.dclone, [node], subcontext)
599
- trace(:predicate_evaluate, expression, node, subcontext, result) if @debug
600
- result = result[0] if result.kind_of? Array and result.length == 1
601
- if result.kind_of? Numeric
602
- if result == node.position
603
- new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
604
- end
605
- elsif result.instance_of? Array
606
- if result.size > 0 and result.inject(false) {|k,s| s or k}
607
- if result.size > 0
608
- new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
609
- end
610
- end
611
- else
612
- if result
613
- new_nodeset << XPathNode.new(node, position: new_nodeset.size + 1)
614
- end
615
- end
616
- end
617
- new_nodeset
618
- end
619
- new_nodesets
620
- ensure
621
- leave(:predicate, new_nodesets) if @debug
622
- end
623
-
624
- def trace(*args)
625
- indent = " " * @nest
626
- PP.pp(args, "").each_line do |line|
627
- puts("#{indent}#{line}")
628
- end
629
- end
630
-
631
- def enter(tag, *args)
632
- trace(:enter, tag, *args)
633
- @nest += 1
634
- end
635
-
636
- def leave(tag, *args)
637
- @nest -= 1
638
- trace(:leave, tag, *args)
639
- end
640
-
641
- # Reorders an array of nodes so that they are in document order
642
- # It tries to do this efficiently.
643
- #
644
- # FIXME: I need to get rid of this, but the issue is that most of the XPath
645
- # interpreter functions as a filter, which means that we lose context going
646
- # in and out of function calls. If I knew what the index of the nodes was,
647
- # I wouldn't have to do this. Maybe add a document IDX for each node?
648
- # Problems with mutable documents. Or, rewrite everything.
649
- def sort(array_of_nodes, order)
650
- new_arry = []
651
- array_of_nodes.each { |node|
652
- node_idx = []
653
- np = node.node_type == :attribute ? node.element : node
654
- while np.parent and np.parent.node_type == :element
655
- node_idx << np.parent.index( np )
656
- np = np.parent
657
- end
658
- new_arry << [ node_idx.reverse, node ]
659
- }
660
- ordered = new_arry.sort_by do |index, node|
661
- if order == :forward
662
- index
663
- else
664
- -index
665
- end
666
- end
667
- ordered.collect do |_index, node|
668
- node
669
- end
670
- end
671
-
672
- def descendant(nodeset, include_self)
673
- nodesets = []
674
- nodeset.each do |node|
675
- new_nodeset = []
676
- new_nodes = {}
677
- descendant_recursive(node.raw_node, new_nodeset, new_nodes, include_self)
678
- nodesets << new_nodeset unless new_nodeset.empty?
679
- end
680
- nodesets
681
- end
682
-
683
- def descendant_recursive(raw_node, new_nodeset, new_nodes, include_self)
684
- if include_self
685
- return if new_nodes.key?(raw_node)
686
- new_nodeset << XPathNode.new(raw_node, position: new_nodeset.size + 1)
687
- new_nodes[raw_node] = true
688
- end
689
-
690
- node_type = raw_node.node_type
691
- if node_type == :element or node_type == :document
692
- raw_node.children.each do |child|
693
- descendant_recursive(child, new_nodeset, new_nodes, true)
694
- end
695
- end
696
- end
697
-
698
- # Builds a nodeset of all of the preceding nodes of the supplied node,
699
- # in reverse document order
700
- # preceding:: includes every element in the document that precedes this node,
701
- # except for ancestors
702
- def preceding(node)
703
- ancestors = []
704
- parent = node.parent
705
- while parent
706
- ancestors << parent
707
- parent = parent.parent
708
- end
709
-
710
- precedings = []
711
- preceding_node = preceding_node_of(node)
712
- while preceding_node
713
- if ancestors.include?(preceding_node)
714
- ancestors.delete(preceding_node)
715
- else
716
- precedings << XPathNode.new(preceding_node,
717
- position: precedings.size + 1)
718
- end
719
- preceding_node = preceding_node_of(preceding_node)
720
- end
721
- precedings
722
- end
723
-
724
- def preceding_node_of( node )
725
- psn = node.previous_sibling_node
726
- if psn.nil?
727
- if node.parent.nil? or node.parent.class == Document
728
- return nil
729
- end
730
- return node.parent
731
- #psn = preceding_node_of( node.parent )
732
- end
733
- while psn and psn.kind_of? Element and psn.children.size > 0
734
- psn = psn.children[-1]
735
- end
736
- psn
737
- end
738
-
739
- def following(node)
740
- followings = []
741
- following_node = next_sibling_node(node)
742
- while following_node
743
- followings << XPathNode.new(following_node,
744
- position: followings.size + 1)
745
- following_node = following_node_of(following_node)
746
- end
747
- followings
748
- end
749
-
750
- def following_node_of( node )
751
- if node.kind_of? Element and node.children.size > 0
752
- return node.children[0]
753
- end
754
- return next_sibling_node(node)
755
- end
756
-
757
- def next_sibling_node(node)
758
- psn = node.next_sibling_node
759
- while psn.nil?
760
- if node.parent.nil? or node.parent.class == Document
761
- return nil
762
- end
763
- node = node.parent
764
- psn = node.next_sibling_node
765
- end
766
- return psn
767
- end
768
-
769
- def child(nodeset)
770
- nodesets = []
771
- nodeset.each do |node|
772
- raw_node = node.raw_node
773
- node_type = raw_node.node_type
774
- # trace(:child, node_type, node)
775
- case node_type
776
- when :element
777
- nodesets << raw_node.children.collect.with_index do |child_node, i|
778
- XPathNode.new(child_node, position: i + 1)
779
- end
780
- when :document
781
- new_nodeset = []
782
- raw_node.children.each do |child|
783
- case child
784
- when XMLDecl, Text
785
- # Ignore
786
- else
787
- new_nodeset << XPathNode.new(child, position: new_nodeset.size + 1)
788
- end
789
- end
790
- nodesets << new_nodeset unless new_nodeset.empty?
791
- end
792
- end
793
- nodesets
794
- end
795
-
796
- def norm b
797
- case b
798
- when true, false
799
- return b
800
- when 'true', 'false'
801
- return Functions::boolean( b )
802
- when /^\d+(\.\d+)?$/, Numeric
803
- return Functions::number( b )
804
- else
805
- return Functions::string( b )
806
- end
807
- end
808
-
809
- def equality_relational_compare(set1, op, set2)
810
- set1 = unnode(set1) if set1.is_a?(Array)
811
- set2 = unnode(set2) if set2.is_a?(Array)
812
-
813
- if set1.kind_of? Array and set2.kind_of? Array
814
- # If both objects to be compared are node-sets, then the
815
- # comparison will be true if and only if there is a node in the
816
- # first node-set and a node in the second node-set such that the
817
- # result of performing the comparison on the string-values of
818
- # the two nodes is true.
819
- set1.product(set2).any? do |node1, node2|
820
- node_string1 = Functions.string(node1)
821
- node_string2 = Functions.string(node2)
822
- compare(node_string1, op, node_string2)
823
- end
824
- elsif set1.kind_of? Array or set2.kind_of? Array
825
- # If one is nodeset and other is number, compare number to each item
826
- # in nodeset s.t. number op number(string(item))
827
- # If one is nodeset and other is string, compare string to each item
828
- # in nodeset s.t. string op string(item)
829
- # If one is nodeset and other is boolean, compare boolean to each item
830
- # in nodeset s.t. boolean op boolean(item)
831
- if set1.kind_of? Array
832
- a = set1
833
- b = set2
834
- else
835
- a = set2
836
- b = set1
837
- end
838
-
839
- case b
840
- when true, false
841
- each_unnode(a).any? do |unnoded|
842
- compare(Functions.boolean(unnoded), op, b)
843
- end
844
- when Numeric
845
- each_unnode(a).any? do |unnoded|
846
- compare(Functions.number(unnoded), op, b)
847
- end
848
- when /\A\d+(\.\d+)?\z/
849
- b = Functions.number(b)
850
- each_unnode(a).any? do |unnoded|
851
- compare(Functions.number(unnoded), op, b)
852
- end
853
- else
854
- b = Functions::string(b)
855
- each_unnode(a).any? do |unnoded|
856
- compare(Functions::string(unnoded), op, b)
857
- end
858
- end
859
- else
860
- # If neither is nodeset,
861
- # If op is = or !=
862
- # If either boolean, convert to boolean
863
- # If either number, convert to number
864
- # Else, convert to string
865
- # Else
866
- # Convert both to numbers and compare
867
- compare(set1, op, set2)
868
- end
869
- end
870
-
871
- def value_type(value)
872
- case value
873
- when true, false
874
- :boolean
875
- when Numeric
876
- :number
877
- when String
878
- :string
879
- else
880
- raise "[BUG] Unexpected value type: <#{value.inspect}>"
881
- end
882
- end
883
-
884
- def normalize_compare_values(a, operator, b)
885
- a_type = value_type(a)
886
- b_type = value_type(b)
887
- case operator
888
- when :eq, :neq
889
- if a_type == :boolean or b_type == :boolean
890
- a = Functions.boolean(a) unless a_type == :boolean
891
- b = Functions.boolean(b) unless b_type == :boolean
892
- elsif a_type == :number or b_type == :number
893
- a = Functions.number(a) unless a_type == :number
894
- b = Functions.number(b) unless b_type == :number
895
- else
896
- a = Functions.string(a) unless a_type == :string
897
- b = Functions.string(b) unless b_type == :string
898
- end
899
- when :lt, :lteq, :gt, :gteq
900
- a = Functions.number(a) unless a_type == :number
901
- b = Functions.number(b) unless b_type == :number
902
- else
903
- message = "[BUG] Unexpected compare operator: " +
904
- "<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
905
- raise message
906
- end
907
- [a, b]
908
- end
909
-
910
- def compare(a, operator, b)
911
- a, b = normalize_compare_values(a, operator, b)
912
- case operator
913
- when :eq
914
- a == b
915
- when :neq
916
- a != b
917
- when :lt
918
- a < b
919
- when :lteq
920
- a <= b
921
- when :gt
922
- a > b
923
- when :gteq
924
- a >= b
925
- else
926
- message = "[BUG] Unexpected compare operator: " +
927
- "<#{operator.inspect}>: <#{a.inspect}>: <#{b.inspect}>"
928
- raise message
929
- end
930
- end
931
-
932
- def each_unnode(nodeset)
933
- return to_enum(__method__, nodeset) unless block_given?
934
- nodeset.each do |node|
935
- if node.is_a?(XPathNode)
936
- unnoded = node.raw_node
937
- else
938
- unnoded = node
939
- end
940
- yield(unnoded)
941
- end
942
- end
943
-
944
- def unnode(nodeset)
945
- each_unnode(nodeset).collect do |unnoded|
946
- unnoded = yield(unnoded) if block_given?
947
- unnoded
948
- end
949
- end
950
- end
951
-
952
- # @private
953
- class XPathNode
954
- attr_reader :raw_node, :context
955
- def initialize(node, context=nil)
956
- if node.is_a?(XPathNode)
957
- @raw_node = node.raw_node
958
- else
959
- @raw_node = node
960
- end
961
- @context = context || {}
962
- end
963
-
964
- def position
965
- @context[:position]
966
- end
967
- end
968
- end