brakeman 5.0.0.pre1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +23 -0
  3. data/bundle/load.rb +9 -8
  4. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/CHANGELOG.md +8 -1
  5. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/FAQ.md +0 -0
  6. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/Gemfile +0 -0
  7. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/MIT-LICENSE +0 -0
  8. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/README.md +0 -0
  9. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/REFERENCE.md +9 -5
  10. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/TODO +0 -0
  11. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/haml.gemspec +1 -1
  12. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml.rb +0 -0
  13. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/attribute_builder.rb +0 -0
  14. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/attribute_compiler.rb +0 -0
  15. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/attribute_parser.rb +0 -0
  16. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/buffer.rb +0 -0
  17. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/compiler.rb +0 -0
  18. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/engine.rb +0 -0
  19. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/error.rb +0 -0
  20. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/escapable.rb +0 -0
  21. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/exec.rb +0 -0
  22. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/filters.rb +0 -0
  23. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/generator.rb +0 -0
  24. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers.rb +0 -0
  25. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/action_view_extensions.rb +0 -0
  26. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/action_view_mods.rb +0 -0
  27. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/action_view_xss_mods.rb +0 -0
  28. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/safe_erubi_template.rb +0 -0
  29. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/safe_erubis_template.rb +0 -0
  30. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/helpers/xss_mods.rb +0 -0
  31. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/options.rb +0 -0
  32. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/parser.rb +31 -3
  33. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/plugin.rb +0 -0
  34. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/railtie.rb +0 -0
  35. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/sass_rails_filter.rb +0 -0
  36. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/template.rb +0 -0
  37. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/template/options.rb +0 -0
  38. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/temple_engine.rb +0 -0
  39. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/temple_line_counter.rb +0 -0
  40. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/util.rb +1 -1
  41. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/lib/haml/version.rb +1 -1
  42. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/yard/default/fulldoc/html/css/common.sass +0 -0
  43. data/bundle/ruby/2.7.0/gems/{haml-5.2.0 → haml-5.2.1}/yard/default/layout/html/footer.erb +0 -0
  44. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/Gemfile +6 -0
  45. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/LICENSE.txt +22 -0
  46. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/NEWS.md +141 -0
  47. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/README.md +60 -0
  48. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/attlistdecl.rb +63 -0
  49. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/attribute.rb +205 -0
  50. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/cdata.rb +68 -0
  51. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/child.rb +97 -0
  52. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/comment.rb +80 -0
  53. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/doctype.rb +287 -0
  54. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/document.rb +291 -0
  55. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/attlistdecl.rb +11 -0
  56. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/dtd.rb +47 -0
  57. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/elementdecl.rb +18 -0
  58. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/entitydecl.rb +57 -0
  59. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/dtd/notationdecl.rb +40 -0
  60. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/element.rb +1269 -0
  61. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/encoding.rb +51 -0
  62. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/entity.rb +171 -0
  63. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/default.rb +116 -0
  64. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/pretty.rb +142 -0
  65. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/formatters/transitive.rb +58 -0
  66. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/functions.rb +447 -0
  67. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/instruction.rb +79 -0
  68. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/light/node.rb +196 -0
  69. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/namespace.rb +59 -0
  70. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/node.rb +76 -0
  71. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/output.rb +30 -0
  72. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parent.rb +166 -0
  73. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parseexception.rb +52 -0
  74. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/baseparser.rb +594 -0
  75. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/lightparser.rb +59 -0
  76. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/pullparser.rb +197 -0
  77. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/sax2parser.rb +273 -0
  78. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/streamparser.rb +61 -0
  79. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/treeparser.rb +101 -0
  80. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/ultralightparser.rb +57 -0
  81. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/parsers/xpathparser.rb +675 -0
  82. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/quickpath.rb +266 -0
  83. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/rexml.rb +32 -0
  84. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/sax2listener.rb +98 -0
  85. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/security.rb +28 -0
  86. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/source.rb +298 -0
  87. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/streamlistener.rb +93 -0
  88. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/text.rb +424 -0
  89. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/undefinednamespaceexception.rb +9 -0
  90. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/relaxng.rb +539 -0
  91. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/validation.rb +144 -0
  92. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/validation/validationexception.rb +10 -0
  93. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xmldecl.rb +130 -0
  94. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xmltokens.rb +85 -0
  95. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xpath.rb +81 -0
  96. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/lib/rexml/xpath_parser.rb +968 -0
  97. data/bundle/ruby/2.7.0/gems/rexml-3.2.4/rexml.gemspec +84 -0
  98. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/History.rdoc +6 -0
  99. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/Manifest.txt +0 -0
  100. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/README.rdoc +0 -0
  101. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/compare/normalize.rb +0 -0
  102. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/debugging.md +0 -0
  103. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/rp_extensions.rb +0 -0
  104. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/rp_stringscanner.rb +0 -0
  105. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby20_parser.rb +0 -0
  106. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby20_parser.y +0 -0
  107. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby21_parser.rb +0 -0
  108. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby21_parser.y +0 -0
  109. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby22_parser.rb +0 -0
  110. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby22_parser.y +0 -0
  111. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby23_parser.rb +0 -0
  112. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby23_parser.y +0 -0
  113. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby24_parser.rb +0 -0
  114. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby24_parser.y +0 -0
  115. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby25_parser.rb +0 -0
  116. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby25_parser.y +0 -0
  117. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby26_parser.rb +0 -0
  118. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby26_parser.y +0 -0
  119. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby27_parser.rb +0 -0
  120. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby27_parser.y +0 -0
  121. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_lexer.rb +0 -0
  122. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_lexer.rex +0 -0
  123. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_lexer.rex.rb +0 -0
  124. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_parser.rb +0 -0
  125. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_parser.yy +0 -0
  126. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/lib/ruby_parser_extras.rb +1 -1
  127. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/tools/munge.rb +0 -0
  128. data/bundle/ruby/2.7.0/gems/{ruby_parser-3.15.0 → ruby_parser-3.15.1}/tools/ripper.rb +0 -0
  129. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/History.rdoc +6 -0
  130. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/Manifest.txt +0 -0
  131. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/README.rdoc +0 -0
  132. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/composite_sexp_processor.rb +0 -0
  133. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/pt_testcase.rb +0 -0
  134. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/sexp.rb +0 -0
  135. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/sexp_matcher.rb +0 -0
  136. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/sexp_processor.rb +1 -1
  137. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/strict_sexp.rb +0 -0
  138. data/bundle/ruby/2.7.0/gems/{sexp_processor-4.15.1 → sexp_processor-4.15.2}/lib/unique.rb +0 -0
  139. data/lib/brakeman/checks/base_check.rb +7 -1
  140. data/lib/brakeman/checks/check_execute.rb +2 -1
  141. data/lib/brakeman/checks/check_regex_dos.rb +1 -1
  142. data/lib/brakeman/checks/check_sql.rb +1 -1
  143. data/lib/brakeman/file_parser.rb +5 -0
  144. data/lib/brakeman/processors/alias_processor.rb +20 -4
  145. data/lib/brakeman/processors/controller_processor.rb +1 -1
  146. data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
  147. data/lib/brakeman/processors/output_processor.rb +1 -1
  148. data/lib/brakeman/processors/template_alias_processor.rb +5 -0
  149. data/lib/brakeman/report/report_base.rb +0 -2
  150. data/lib/brakeman/report/report_csv.rb +37 -60
  151. data/lib/brakeman/report/report_junit.rb +2 -2
  152. data/lib/brakeman/report/report_sarif.rb +1 -1
  153. data/lib/brakeman/report/report_tabs.rb +1 -1
  154. data/lib/brakeman/report/report_text.rb +1 -1
  155. data/lib/brakeman/scanner.rb +3 -1
  156. data/lib/brakeman/tracker/config.rb +73 -0
  157. data/lib/brakeman/tracker/controller.rb +1 -1
  158. data/lib/brakeman/util.rb +2 -2
  159. data/lib/brakeman/version.rb +1 -1
  160. data/lib/brakeman/warning.rb +10 -2
  161. data/lib/ruby_parser/bm_sexp.rb +9 -9
  162. metadata +139 -85
@@ -0,0 +1,968 @@
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