rubysl-rexml 1.0.0

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 (179) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/README.md +29 -0
  7. data/Rakefile +1 -0
  8. data/lib/rexml/attlistdecl.rb +62 -0
  9. data/lib/rexml/attribute.rb +185 -0
  10. data/lib/rexml/cdata.rb +67 -0
  11. data/lib/rexml/child.rb +96 -0
  12. data/lib/rexml/comment.rb +80 -0
  13. data/lib/rexml/doctype.rb +271 -0
  14. data/lib/rexml/document.rb +230 -0
  15. data/lib/rexml/dtd/attlistdecl.rb +10 -0
  16. data/lib/rexml/dtd/dtd.rb +51 -0
  17. data/lib/rexml/dtd/elementdecl.rb +17 -0
  18. data/lib/rexml/dtd/entitydecl.rb +56 -0
  19. data/lib/rexml/dtd/notationdecl.rb +39 -0
  20. data/lib/rexml/element.rb +1227 -0
  21. data/lib/rexml/encoding.rb +71 -0
  22. data/lib/rexml/encodings/CP-1252.rb +103 -0
  23. data/lib/rexml/encodings/EUC-JP.rb +35 -0
  24. data/lib/rexml/encodings/ICONV.rb +22 -0
  25. data/lib/rexml/encodings/ISO-8859-1.rb +7 -0
  26. data/lib/rexml/encodings/ISO-8859-15.rb +72 -0
  27. data/lib/rexml/encodings/SHIFT-JIS.rb +37 -0
  28. data/lib/rexml/encodings/SHIFT_JIS.rb +1 -0
  29. data/lib/rexml/encodings/UNILE.rb +34 -0
  30. data/lib/rexml/encodings/US-ASCII.rb +30 -0
  31. data/lib/rexml/encodings/UTF-16.rb +35 -0
  32. data/lib/rexml/encodings/UTF-8.rb +18 -0
  33. data/lib/rexml/entity.rb +166 -0
  34. data/lib/rexml/formatters/default.rb +109 -0
  35. data/lib/rexml/formatters/pretty.rb +138 -0
  36. data/lib/rexml/formatters/transitive.rb +56 -0
  37. data/lib/rexml/functions.rb +382 -0
  38. data/lib/rexml/instruction.rb +70 -0
  39. data/lib/rexml/light/node.rb +196 -0
  40. data/lib/rexml/namespace.rb +47 -0
  41. data/lib/rexml/node.rb +75 -0
  42. data/lib/rexml/output.rb +24 -0
  43. data/lib/rexml/parent.rb +166 -0
  44. data/lib/rexml/parseexception.rb +51 -0
  45. data/lib/rexml/parsers/baseparser.rb +503 -0
  46. data/lib/rexml/parsers/lightparser.rb +60 -0
  47. data/lib/rexml/parsers/pullparser.rb +196 -0
  48. data/lib/rexml/parsers/sax2parser.rb +238 -0
  49. data/lib/rexml/parsers/streamparser.rb +46 -0
  50. data/lib/rexml/parsers/treeparser.rb +97 -0
  51. data/lib/rexml/parsers/ultralightparser.rb +56 -0
  52. data/lib/rexml/parsers/xpathparser.rb +698 -0
  53. data/lib/rexml/quickpath.rb +266 -0
  54. data/lib/rexml/rexml.rb +32 -0
  55. data/lib/rexml/sax2listener.rb +97 -0
  56. data/lib/rexml/source.rb +251 -0
  57. data/lib/rexml/streamlistener.rb +92 -0
  58. data/lib/rexml/syncenumerator.rb +33 -0
  59. data/lib/rexml/text.rb +344 -0
  60. data/lib/rexml/undefinednamespaceexception.rb +8 -0
  61. data/lib/rexml/validation/relaxng.rb +559 -0
  62. data/lib/rexml/validation/validation.rb +155 -0
  63. data/lib/rexml/validation/validationexception.rb +9 -0
  64. data/lib/rexml/xmldecl.rb +119 -0
  65. data/lib/rexml/xmltokens.rb +18 -0
  66. data/lib/rexml/xpath.rb +66 -0
  67. data/lib/rexml/xpath_parser.rb +792 -0
  68. data/lib/rubysl/rexml.rb +1 -0
  69. data/lib/rubysl/rexml/version.rb +5 -0
  70. data/rubysl-rexml.gemspec +23 -0
  71. data/spec/attribute/clone_spec.rb +10 -0
  72. data/spec/attribute/element_spec.rb +22 -0
  73. data/spec/attribute/equal_value_spec.rb +17 -0
  74. data/spec/attribute/hash_spec.rb +12 -0
  75. data/spec/attribute/initialize_spec.rb +28 -0
  76. data/spec/attribute/inspect_spec.rb +19 -0
  77. data/spec/attribute/namespace_spec.rb +23 -0
  78. data/spec/attribute/node_type_spec.rb +9 -0
  79. data/spec/attribute/prefix_spec.rb +17 -0
  80. data/spec/attribute/remove_spec.rb +19 -0
  81. data/spec/attribute/to_s_spec.rb +13 -0
  82. data/spec/attribute/to_string_spec.rb +14 -0
  83. data/spec/attribute/value_spec.rb +14 -0
  84. data/spec/attribute/write_spec.rb +22 -0
  85. data/spec/attribute/xpath_spec.rb +19 -0
  86. data/spec/attributes/add_spec.rb +6 -0
  87. data/spec/attributes/append_spec.rb +6 -0
  88. data/spec/attributes/delete_all_spec.rb +30 -0
  89. data/spec/attributes/delete_spec.rb +26 -0
  90. data/spec/attributes/each_attribute_spec.rb +24 -0
  91. data/spec/attributes/each_spec.rb +24 -0
  92. data/spec/attributes/element_reference_spec.rb +18 -0
  93. data/spec/attributes/element_set_spec.rb +25 -0
  94. data/spec/attributes/get_attribute_ns_spec.rb +13 -0
  95. data/spec/attributes/get_attribute_spec.rb +28 -0
  96. data/spec/attributes/initialize_spec.rb +18 -0
  97. data/spec/attributes/length_spec.rb +6 -0
  98. data/spec/attributes/namespaces_spec.rb +5 -0
  99. data/spec/attributes/prefixes_spec.rb +23 -0
  100. data/spec/attributes/shared/add.rb +17 -0
  101. data/spec/attributes/shared/length.rb +12 -0
  102. data/spec/attributes/size_spec.rb +6 -0
  103. data/spec/attributes/to_a_spec.rb +20 -0
  104. data/spec/cdata/clone_spec.rb +9 -0
  105. data/spec/cdata/initialize_spec.rb +24 -0
  106. data/spec/cdata/shared/to_s.rb +11 -0
  107. data/spec/cdata/to_s_spec.rb +6 -0
  108. data/spec/cdata/value_spec.rb +6 -0
  109. data/spec/document/add_element_spec.rb +30 -0
  110. data/spec/document/add_spec.rb +60 -0
  111. data/spec/document/clone_spec.rb +19 -0
  112. data/spec/document/doctype_spec.rb +14 -0
  113. data/spec/document/encoding_spec.rb +21 -0
  114. data/spec/document/expanded_name_spec.rb +15 -0
  115. data/spec/document/new_spec.rb +37 -0
  116. data/spec/document/node_type_spec.rb +7 -0
  117. data/spec/document/root_spec.rb +11 -0
  118. data/spec/document/stand_alone_spec.rb +18 -0
  119. data/spec/document/version_spec.rb +13 -0
  120. data/spec/document/write_spec.rb +38 -0
  121. data/spec/document/xml_decl_spec.rb +14 -0
  122. data/spec/element/add_attribute_spec.rb +40 -0
  123. data/spec/element/add_attributes_spec.rb +21 -0
  124. data/spec/element/add_element_spec.rb +38 -0
  125. data/spec/element/add_namespace_spec.rb +23 -0
  126. data/spec/element/add_text_spec.rb +23 -0
  127. data/spec/element/attribute_spec.rb +16 -0
  128. data/spec/element/attributes_spec.rb +18 -0
  129. data/spec/element/cdatas_spec.rb +23 -0
  130. data/spec/element/clone_spec.rb +28 -0
  131. data/spec/element/comments_spec.rb +20 -0
  132. data/spec/element/delete_attribute_spec.rb +38 -0
  133. data/spec/element/delete_element_spec.rb +50 -0
  134. data/spec/element/delete_namespace_spec.rb +24 -0
  135. data/spec/element/document_spec.rb +17 -0
  136. data/spec/element/each_element_with_attribute_spec.rb +34 -0
  137. data/spec/element/each_element_with_text_spec.rb +30 -0
  138. data/spec/element/get_text_spec.rb +17 -0
  139. data/spec/element/has_attributes_spec.rb +16 -0
  140. data/spec/element/has_elements_spec.rb +17 -0
  141. data/spec/element/has_text_spec.rb +15 -0
  142. data/spec/element/inspect_spec.rb +26 -0
  143. data/spec/element/instructions_spec.rb +20 -0
  144. data/spec/element/namespace_spec.rb +26 -0
  145. data/spec/element/namespaces_spec.rb +31 -0
  146. data/spec/element/new_spec.rb +34 -0
  147. data/spec/element/next_element_spec.rb +18 -0
  148. data/spec/element/node_type_spec.rb +7 -0
  149. data/spec/element/prefixes_spec.rb +22 -0
  150. data/spec/element/previous_element_spec.rb +19 -0
  151. data/spec/element/raw_spec.rb +23 -0
  152. data/spec/element/root_spec.rb +27 -0
  153. data/spec/element/text_spec.rb +45 -0
  154. data/spec/element/texts_spec.rb +15 -0
  155. data/spec/element/whitespace_spec.rb +22 -0
  156. data/spec/node/each_recursive_spec.rb +20 -0
  157. data/spec/node/find_first_recursive_spec.rb +24 -0
  158. data/spec/node/index_in_parent_spec.rb +14 -0
  159. data/spec/node/next_sibling_node_spec.rb +20 -0
  160. data/spec/node/parent_spec.rb +20 -0
  161. data/spec/node/previous_sibling_node_spec.rb +20 -0
  162. data/spec/shared/each_element.rb +35 -0
  163. data/spec/shared/elements_to_a.rb +35 -0
  164. data/spec/text/append_spec.rb +9 -0
  165. data/spec/text/clone_spec.rb +9 -0
  166. data/spec/text/comparison_spec.rb +24 -0
  167. data/spec/text/empty_spec.rb +11 -0
  168. data/spec/text/indent_text_spec.rb +23 -0
  169. data/spec/text/inspect_spec.rb +7 -0
  170. data/spec/text/new_spec.rb +48 -0
  171. data/spec/text/node_type_spec.rb +7 -0
  172. data/spec/text/normalize_spec.rb +7 -0
  173. data/spec/text/read_with_substitution_spec.rb +12 -0
  174. data/spec/text/to_s_spec.rb +17 -0
  175. data/spec/text/unnormalize_spec.rb +7 -0
  176. data/spec/text/value_spec.rb +36 -0
  177. data/spec/text/wrap_spec.rb +20 -0
  178. data/spec/text/write_with_substitution_spec.rb +32 -0
  179. metadata +385 -0
@@ -0,0 +1,155 @@
1
+ require 'rexml/validation/validationexception'
2
+
3
+ module REXML
4
+ module Validation
5
+ module Validator
6
+ NILEVENT = [ nil ]
7
+ def reset
8
+ @current = @root
9
+ @root.reset
10
+ @root.previous = true
11
+ @attr_stack = []
12
+ self
13
+ end
14
+ def dump
15
+ puts @root.inspect
16
+ end
17
+ def validate( event )
18
+ #puts "Current: #@current"
19
+ #puts "Event: #{event.inspect}"
20
+ @attr_stack = [] unless defined? @attr_stack
21
+ match = @current.next(event)
22
+ raise ValidationException.new( "Validation error. Expected: "+
23
+ @current.expected.join( " or " )+" from #{@current.inspect} "+
24
+ " but got #{Event.new( event[0], event[1] ).inspect}" ) unless match
25
+ @current = match
26
+
27
+ # Check for attributes
28
+ case event[0]
29
+ when :start_element
30
+ #puts "Checking attributes"
31
+ @attr_stack << event[2]
32
+ begin
33
+ sattr = [:start_attribute, nil]
34
+ eattr = [:end_attribute]
35
+ text = [:text, nil]
36
+ k,v = event[2].find { |k,v|
37
+ sattr[1] = k
38
+ #puts "Looking for #{sattr.inspect}"
39
+ m = @current.next( sattr )
40
+ #puts "Got #{m.inspect}"
41
+ if m
42
+ # If the state has text children...
43
+ #puts "Looking for #{eattr.inspect}"
44
+ #puts "Expect #{m.expected}"
45
+ if m.matches?( eattr )
46
+ #puts "Got end"
47
+ @current = m
48
+ else
49
+ #puts "Didn't get end"
50
+ text[1] = v
51
+ #puts "Looking for #{text.inspect}"
52
+ m = m.next( text )
53
+ #puts "Got #{m.inspect}"
54
+ text[1] = nil
55
+ return false unless m
56
+ @current = m if m
57
+ end
58
+ m = @current.next( eattr )
59
+ if m
60
+ @current = m
61
+ true
62
+ else
63
+ false
64
+ end
65
+ else
66
+ false
67
+ end
68
+ }
69
+ event[2].delete(k) if k
70
+ end while k
71
+ when :end_element
72
+ attrs = @attr_stack.pop
73
+ raise ValidationException.new( "Validation error. Illegal "+
74
+ " attributes: #{attrs.inspect}") if attrs.length > 0
75
+ end
76
+ end
77
+ end
78
+
79
+ class Event
80
+ def initialize(event_type, event_arg=nil )
81
+ @event_type = event_type
82
+ @event_arg = event_arg
83
+ end
84
+
85
+ attr_reader :event_type
86
+ attr_accessor :event_arg
87
+
88
+ def done?
89
+ @done
90
+ end
91
+
92
+ def single?
93
+ return (@event_type != :start_element and @event_type != :start_attribute)
94
+ end
95
+
96
+ def matches?( event )
97
+ #puts "#@event_type =? #{event[0]} && #@event_arg =? #{event[1]} "
98
+ return false unless event[0] == @event_type
99
+ case event[0]
100
+ when nil
101
+ return true
102
+ when :start_element
103
+ return true if event[1] == @event_arg
104
+ when :end_element
105
+ return true
106
+ when :start_attribute
107
+ return true if event[1] == @event_arg
108
+ when :end_attribute
109
+ return true
110
+ when :end_document
111
+ return true
112
+ when :text
113
+ return (@event_arg.nil? or @event_arg == event[1])
114
+ =begin
115
+ when :processing_instruction
116
+ false
117
+ when :xmldecl
118
+ false
119
+ when :start_doctype
120
+ false
121
+ when :end_doctype
122
+ false
123
+ when :externalentity
124
+ false
125
+ when :elementdecl
126
+ false
127
+ when :entity
128
+ false
129
+ when :attlistdecl
130
+ false
131
+ when :notationdecl
132
+ false
133
+ when :end_doctype
134
+ false
135
+ =end
136
+ else
137
+ false
138
+ end
139
+ end
140
+
141
+ def ==( other )
142
+ return false unless other.kind_of? Event
143
+ @event_type == other.event_type and @event_arg == other.event_arg
144
+ end
145
+
146
+ def to_s
147
+ inspect
148
+ end
149
+
150
+ def inspect
151
+ "#{@event_type.inspect}( #@event_arg )"
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,9 @@
1
+ module REXML
2
+ module Validation
3
+ class ValidationException < RuntimeError
4
+ def initialize msg
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ require 'rexml/encoding'
2
+ require 'rexml/source'
3
+
4
+ module REXML
5
+ # NEEDS DOCUMENTATION
6
+ class XMLDecl < Child
7
+ include Encoding
8
+
9
+ DEFAULT_VERSION = "1.0";
10
+ DEFAULT_ENCODING = "UTF-8";
11
+ DEFAULT_STANDALONE = "no";
12
+ START = '<\?xml';
13
+ STOP = '\?>';
14
+
15
+ attr_accessor :version, :standalone
16
+ attr_reader :writeencoding, :writethis
17
+
18
+ def initialize(version=DEFAULT_VERSION, encoding=nil, standalone=nil)
19
+ @writethis = true
20
+ @writeencoding = !encoding.nil?
21
+ if version.kind_of? XMLDecl
22
+ super()
23
+ @version = version.version
24
+ self.encoding = version.encoding
25
+ @writeencoding = version.writeencoding
26
+ @standalone = version.standalone
27
+ else
28
+ super()
29
+ @version = version
30
+ self.encoding = encoding
31
+ @standalone = standalone
32
+ end
33
+ @version = DEFAULT_VERSION if @version.nil?
34
+ end
35
+
36
+ def clone
37
+ XMLDecl.new(self)
38
+ end
39
+
40
+ # indent::
41
+ # Ignored. There must be no whitespace before an XML declaration
42
+ # transitive::
43
+ # Ignored
44
+ # ie_hack::
45
+ # Ignored
46
+ def write(writer, indent=-1, transitive=false, ie_hack=false)
47
+ return nil unless @writethis or writer.kind_of? Output
48
+ writer << START.sub(/\\/u, '')
49
+ if writer.kind_of? Output
50
+ writer << " #{content writer.encoding}"
51
+ else
52
+ writer << " #{content encoding}"
53
+ end
54
+ writer << STOP.sub(/\\/u, '')
55
+ end
56
+
57
+ def ==( other )
58
+ other.kind_of?(XMLDecl) and
59
+ other.version == @version and
60
+ other.encoding == self.encoding and
61
+ other.standalone == @standalone
62
+ end
63
+
64
+ def xmldecl version, encoding, standalone
65
+ @version = version
66
+ self.encoding = encoding
67
+ @standalone = standalone
68
+ end
69
+
70
+ def node_type
71
+ :xmldecl
72
+ end
73
+
74
+ alias :stand_alone? :standalone
75
+ alias :old_enc= :encoding=
76
+
77
+ def encoding=( enc )
78
+ if enc.nil?
79
+ self.old_enc = "UTF-8"
80
+ @writeencoding = false
81
+ else
82
+ self.old_enc = enc
83
+ @writeencoding = true
84
+ end
85
+ self.dowrite
86
+ end
87
+
88
+ # Only use this if you do not want the XML declaration to be written;
89
+ # this object is ignored by the XML writer. Otherwise, instantiate your
90
+ # own XMLDecl and add it to the document.
91
+ #
92
+ # Note that XML 1.1 documents *must* include an XML declaration
93
+ def XMLDecl.default
94
+ rv = XMLDecl.new( "1.0" )
95
+ rv.nowrite
96
+ rv
97
+ end
98
+
99
+ def nowrite
100
+ @writethis = false
101
+ end
102
+
103
+ def dowrite
104
+ @writethis = true
105
+ end
106
+
107
+ def inspect
108
+ START.sub(/\\/u, '') + " ... " + STOP.sub(/\\/u, '')
109
+ end
110
+
111
+ private
112
+ def content(enc)
113
+ rv = "version='#@version'"
114
+ rv << " encoding='#{enc}'" if @writeencoding || enc !~ /utf-8/i
115
+ rv << " standalone='#@standalone'" if @standalone
116
+ rv
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,18 @@
1
+ module REXML
2
+ # Defines a number of tokens used for parsing XML. Not for general
3
+ # consumption.
4
+ module XMLTokens
5
+ NCNAME_STR= '[\w:][\-\w\d.]*'
6
+ NAME_STR= "(?:#{NCNAME_STR}:)?#{NCNAME_STR}"
7
+
8
+ NAMECHAR = '[\-\w\d\.:]'
9
+ NAME = "([\\w:]#{NAMECHAR}*)"
10
+ NMTOKEN = "(?:#{NAMECHAR})+"
11
+ NMTOKENS = "#{NMTOKEN}(\\s+#{NMTOKEN})*"
12
+ REFERENCE = "(?:&#{NAME};|&#\\d+;|&#x[0-9a-fA-F]+;)"
13
+
14
+ #REFERENCE = "(?:#{ENTITYREF}|#{CHARREF})"
15
+ #ENTITYREF = "&#{NAME};"
16
+ #CHARREF = "&#\\d+;|&#x[0-9a-fA-F]+;"
17
+ end
18
+ end
@@ -0,0 +1,66 @@
1
+ require 'rexml/functions'
2
+ require 'rexml/xpath_parser'
3
+
4
+ module REXML
5
+ # Wrapper class. Use this class to access the XPath functions.
6
+ class XPath
7
+ include Functions
8
+ EMPTY_HASH = {}
9
+
10
+ # Finds and returns the first node that matches the supplied xpath.
11
+ # element::
12
+ # The context element
13
+ # path::
14
+ # The xpath to search for. If not supplied or nil, returns the first
15
+ # node matching '*'.
16
+ # namespaces::
17
+ # If supplied, a Hash which defines a namespace mapping.
18
+ #
19
+ # XPath.first( node )
20
+ # XPath.first( doc, "//b"} )
21
+ # XPath.first( node, "a/x:b", { "x"=>"http://doofus" } )
22
+ def XPath::first element, path=nil, namespaces=nil, variables={}
23
+ raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
24
+ raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
25
+ parser = XPathParser.new
26
+ parser.namespaces = namespaces
27
+ parser.variables = variables
28
+ path = "*" unless path
29
+ element = [element] unless element.kind_of? Array
30
+ parser.parse(path, element).flatten[0]
31
+ end
32
+
33
+ # Iterates over nodes that match the given path, calling the supplied
34
+ # block with the match.
35
+ # element::
36
+ # The context element
37
+ # path::
38
+ # The xpath to search for. If not supplied or nil, defaults to '*'
39
+ # namespaces::
40
+ # If supplied, a Hash which defines a namespace mapping
41
+ #
42
+ # XPath.each( node ) { |el| ... }
43
+ # XPath.each( node, '/*[@attr='v']' ) { |el| ... }
44
+ # XPath.each( node, 'ancestor::x' ) { |el| ... }
45
+ def XPath::each element, path=nil, namespaces=nil, variables={}, &block
46
+ raise "The namespaces argument, if supplied, must be a hash object." unless namespaces.nil? or namespaces.kind_of?(Hash)
47
+ raise "The variables argument, if supplied, must be a hash object." unless variables.kind_of?(Hash)
48
+ parser = XPathParser.new
49
+ parser.namespaces = namespaces
50
+ parser.variables = variables
51
+ path = "*" unless path
52
+ element = [element] unless element.kind_of? Array
53
+ parser.parse(path, element).each( &block )
54
+ end
55
+
56
+ # Returns an array of nodes matching a given XPath.
57
+ def XPath::match element, path=nil, namespaces=nil, variables={}
58
+ parser = XPathParser.new
59
+ parser.namespaces = namespaces
60
+ parser.variables = variables
61
+ path = "*" unless path
62
+ element = [element] unless element.kind_of? Array
63
+ parser.parse(path,element)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,792 @@
1
+ require 'rexml/namespace'
2
+ require 'rexml/xmltokens'
3
+ require 'rexml/attribute'
4
+ require 'rexml/syncenumerator'
5
+ require 'rexml/parsers/xpathparser'
6
+
7
+ class Object
8
+ def dclone
9
+ clone
10
+ end
11
+ end
12
+ class Symbol
13
+ def dclone ; self ; end
14
+ end
15
+ class Fixnum
16
+ def dclone ; self ; end
17
+ end
18
+ class Float
19
+ def dclone ; self ; end
20
+ end
21
+ class Array
22
+ def dclone
23
+ klone = self.clone
24
+ klone.clear
25
+ self.each{|v| klone << v.dclone}
26
+ klone
27
+ end
28
+ end
29
+
30
+ module REXML
31
+ # You don't want to use this class. Really. Use XPath, which is a wrapper
32
+ # for this class. Believe me. You don't want to poke around in here.
33
+ # There is strange, dark magic at work in this code. Beware. Go back! Go
34
+ # back while you still can!
35
+ class XPathParser
36
+ include XMLTokens
37
+ LITERAL = /^'([^']*)'|^"([^"]*)"/u
38
+
39
+ def initialize( )
40
+ @parser = REXML::Parsers::XPathParser.new
41
+ @namespaces = nil
42
+ @variables = {}
43
+ end
44
+
45
+ def namespaces=( namespaces={} )
46
+ Functions::namespace_context = namespaces
47
+ @namespaces = namespaces
48
+ end
49
+
50
+ def variables=( vars={} )
51
+ Functions::variables = vars
52
+ @variables = vars
53
+ end
54
+
55
+ def parse path, nodeset
56
+ #puts "#"*40
57
+ path_stack = @parser.parse( path )
58
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
59
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
60
+ match( path_stack, nodeset )
61
+ end
62
+
63
+ def get_first path, nodeset
64
+ #puts "#"*40
65
+ path_stack = @parser.parse( path )
66
+ #puts "PARSE: #{path} => #{path_stack.inspect}"
67
+ #puts "PARSE: nodeset = #{nodeset.inspect}"
68
+ first( path_stack, nodeset )
69
+ end
70
+
71
+ def predicate path, nodeset
72
+ path_stack = @parser.parse( path )
73
+ expr( path_stack, nodeset )
74
+ end
75
+
76
+ def []=( variable_name, value )
77
+ @variables[ variable_name ] = value
78
+ end
79
+
80
+
81
+ # Performs a depth-first (document order) XPath search, and returns the
82
+ # first match. This is the fastest, lightest way to return a single result.
83
+ #
84
+ # FIXME: This method is incomplete!
85
+ def first( path_stack, node )
86
+ #puts "#{depth}) Entering match( #{path.inspect}, #{tree.inspect} )"
87
+ return nil if path.size == 0
88
+
89
+ case path[0]
90
+ when :document
91
+ # do nothing
92
+ return first( path[1..-1], node )
93
+ when :child
94
+ for c in node.children
95
+ #puts "#{depth}) CHILD checking #{name(c)}"
96
+ r = first( path[1..-1], c )
97
+ #puts "#{depth}) RETURNING #{r.inspect}" if r
98
+ return r if r
99
+ end
100
+ when :qname
101
+ name = path[2]
102
+ #puts "#{depth}) QNAME #{name(tree)} == #{name} (path => #{path.size})"
103
+ if node.name == name
104
+ #puts "#{depth}) RETURNING #{tree.inspect}" if path.size == 3
105
+ return node if path.size == 3
106
+ return first( path[3..-1], node )
107
+ else
108
+ return nil
109
+ end
110
+ when :descendant_or_self
111
+ r = first( path[1..-1], node )
112
+ return r if r
113
+ for c in node.children
114
+ r = first( path, c )
115
+ return r if r
116
+ end
117
+ when :node
118
+ return first( path[1..-1], node )
119
+ when :any
120
+ return first( path[1..-1], node )
121
+ end
122
+ return nil
123
+ end
124
+
125
+
126
+ def match( path_stack, nodeset )
127
+ #puts "MATCH: path_stack = #{path_stack.inspect}"
128
+ #puts "MATCH: nodeset = #{nodeset.inspect}"
129
+ r = expr( path_stack, nodeset )
130
+ #puts "MAIN EXPR => #{r.inspect}"
131
+ r
132
+ end
133
+
134
+ private
135
+
136
+
137
+ # Returns a String namespace for a node, given a prefix
138
+ # The rules are:
139
+ #
140
+ # 1. Use the supplied namespace mapping first.
141
+ # 2. If no mapping was supplied, use the context node to look up the namespace
142
+ def get_namespace( node, prefix )
143
+ if @namespaces
144
+ return @namespaces[prefix] || ''
145
+ else
146
+ return node.namespace( prefix ) if node.node_type == :element
147
+ return ''
148
+ end
149
+ end
150
+
151
+
152
+ # Expr takes a stack of path elements and a set of nodes (either a Parent
153
+ # or an Array and returns an Array of matching nodes
154
+ ALL = [ :attribute, :element, :text, :processing_instruction, :comment ]
155
+ ELEMENTS = [ :element ]
156
+ def expr( path_stack, nodeset, context=nil )
157
+ #puts "#"*15
158
+ #puts "In expr with #{path_stack.inspect}"
159
+ #puts "Returning" if path_stack.length == 0 || nodeset.length == 0
160
+ node_types = ELEMENTS
161
+ return nodeset if path_stack.length == 0 || nodeset.length == 0
162
+ while path_stack.length > 0
163
+ #puts "#"*5
164
+ #puts "Path stack = #{path_stack.inspect}"
165
+ #puts "Nodeset is #{nodeset.inspect}"
166
+ if nodeset.length == 0
167
+ path_stack.clear
168
+ return []
169
+ end
170
+ case (op = path_stack.shift)
171
+ when :document
172
+ nodeset = [ nodeset[0].root_node ]
173
+ #puts ":document, nodeset = #{nodeset.inspect}"
174
+
175
+ when :qname
176
+ #puts "IN QNAME"
177
+ prefix = path_stack.shift
178
+ name = path_stack.shift
179
+ nodeset.delete_if do |node|
180
+ # FIXME: This DOUBLES the time XPath searches take
181
+ ns = get_namespace( node, prefix )
182
+ #puts "NS = #{ns.inspect}"
183
+ #puts "node.node_type == :element => #{node.node_type == :element}"
184
+ if node.node_type == :element
185
+ #puts "node.name == #{name} => #{node.name == name}"
186
+ if node.name == name
187
+ #puts "node.namespace == #{ns.inspect} => #{node.namespace == ns}"
188
+ end
189
+ end
190
+ !(node.node_type == :element and
191
+ node.name == name and
192
+ node.namespace == ns )
193
+ end
194
+ node_types = ELEMENTS
195
+
196
+ when :any
197
+ #puts "ANY 1: nodeset = #{nodeset.inspect}"
198
+ #puts "ANY 1: node_types = #{node_types.inspect}"
199
+ nodeset.delete_if { |node| !node_types.include?(node.node_type) }
200
+ #puts "ANY 2: nodeset = #{nodeset.inspect}"
201
+
202
+ when :self
203
+ # This space left intentionally blank
204
+
205
+ when :processing_instruction
206
+ target = path_stack.shift
207
+ nodeset.delete_if do |node|
208
+ (node.node_type != :processing_instruction) or
209
+ ( target!='' and ( node.target != target ) )
210
+ end
211
+
212
+ when :text
213
+ nodeset.delete_if { |node| node.node_type != :text }
214
+
215
+ when :comment
216
+ nodeset.delete_if { |node| node.node_type != :comment }
217
+
218
+ when :node
219
+ # This space left intentionally blank
220
+ node_types = ALL
221
+
222
+ when :child
223
+ new_nodeset = []
224
+ nt = nil
225
+ for node in nodeset
226
+ nt = node.node_type
227
+ new_nodeset += node.children if nt == :element or nt == :document
228
+ end
229
+ nodeset = new_nodeset
230
+ node_types = ELEMENTS
231
+
232
+ when :literal
233
+ return path_stack.shift
234
+
235
+ when :attribute
236
+ new_nodeset = []
237
+ case path_stack.shift
238
+ when :qname
239
+ prefix = path_stack.shift
240
+ name = path_stack.shift
241
+ for element in nodeset
242
+ if element.node_type == :element
243
+ #puts "Element name = #{element.name}"
244
+ #puts "get_namespace( #{element.inspect}, #{prefix} ) = #{get_namespace(element, prefix)}"
245
+ attrib = element.attribute( name, get_namespace(element, prefix) )
246
+ #puts "attrib = #{attrib.inspect}"
247
+ new_nodeset << attrib if attrib
248
+ end
249
+ end
250
+ when :any
251
+ #puts "ANY"
252
+ for element in nodeset
253
+ if element.node_type == :element
254
+ new_nodeset += element.attributes.to_a
255
+ end
256
+ end
257
+ end
258
+ nodeset = new_nodeset
259
+
260
+ when :parent
261
+ #puts "PARENT 1: nodeset = #{nodeset}"
262
+ nodeset = nodeset.collect{|n| n.parent}.compact
263
+ #nodeset = expr(path_stack.dclone, nodeset.collect{|n| n.parent}.compact)
264
+ #puts "PARENT 2: nodeset = #{nodeset.inspect}"
265
+ node_types = ELEMENTS
266
+
267
+ when :ancestor
268
+ new_nodeset = []
269
+ for node in nodeset
270
+ while node.parent
271
+ node = node.parent
272
+ new_nodeset << node unless new_nodeset.include? node
273
+ end
274
+ end
275
+ nodeset = new_nodeset
276
+ node_types = ELEMENTS
277
+
278
+ when :ancestor_or_self
279
+ new_nodeset = []
280
+ for node in nodeset
281
+ if node.node_type == :element
282
+ new_nodeset << node
283
+ while ( node.parent )
284
+ node = node.parent
285
+ new_nodeset << node unless new_nodeset.include? node
286
+ end
287
+ end
288
+ end
289
+ nodeset = new_nodeset
290
+ node_types = ELEMENTS
291
+
292
+ when :predicate
293
+ new_nodeset = []
294
+ subcontext = { :size => nodeset.size }
295
+ pred = path_stack.shift
296
+ nodeset.each_with_index { |node, index|
297
+ subcontext[ :node ] = node
298
+ #puts "PREDICATE SETTING CONTEXT INDEX TO #{index+1}"
299
+ subcontext[ :index ] = index+1
300
+ pc = pred.dclone
301
+ #puts "#{node.hash}) Recursing with #{pred.inspect} and [#{node.inspect}]"
302
+ result = expr( pc, [node], subcontext )
303
+ result = result[0] if result.kind_of? Array and result.length == 1
304
+ #puts "#{node.hash}) Result = #{result.inspect} (#{result.class.name})"
305
+ if result.kind_of? Numeric
306
+ #puts "Adding node #{node.inspect}" if result == (index+1)
307
+ new_nodeset << node if result == (index+1)
308
+ elsif result.instance_of? Array
309
+ if result.size > 0 and result.inject(false) {|k,s| s or k}
310
+ #puts "Adding node #{node.inspect}" if result.size > 0
311
+ new_nodeset << node if result.size > 0
312
+ end
313
+ else
314
+ #puts "Adding node #{node.inspect}" if result
315
+ new_nodeset << node if result
316
+ end
317
+ }
318
+ #puts "New nodeset = #{new_nodeset.inspect}"
319
+ #puts "Path_stack = #{path_stack.inspect}"
320
+ nodeset = new_nodeset
321
+ =begin
322
+ predicate = path_stack.shift
323
+ ns = nodeset.clone
324
+ result = expr( predicate, ns )
325
+ #puts "Result = #{result.inspect} (#{result.class.name})"
326
+ #puts "nodeset = #{nodeset.inspect}"
327
+ if result.kind_of? Array
328
+ nodeset = result.zip(ns).collect{|m,n| n if m}.compact
329
+ else
330
+ nodeset = result ? nodeset : []
331
+ end
332
+ #puts "Outgoing NS = #{nodeset.inspect}"
333
+ =end
334
+
335
+ when :descendant_or_self
336
+ rv = descendant_or_self( path_stack, nodeset )
337
+ path_stack.clear
338
+ nodeset = rv
339
+ node_types = ELEMENTS
340
+
341
+ when :descendant
342
+ results = []
343
+ nt = nil
344
+ for node in nodeset
345
+ nt = node.node_type
346
+ results += expr( path_stack.dclone.unshift( :descendant_or_self ),
347
+ node.children ) if nt == :element or nt == :document
348
+ end
349
+ nodeset = results
350
+ node_types = ELEMENTS
351
+
352
+ when :following_sibling
353
+ #puts "FOLLOWING_SIBLING 1: nodeset = #{nodeset}"
354
+ results = []
355
+ nodeset.each do |node|
356
+ next if node.parent.nil?
357
+ all_siblings = node.parent.children
358
+ current_index = all_siblings.index( node )
359
+ following_siblings = all_siblings[ current_index+1 .. -1 ]
360
+ results += expr( path_stack.dclone, following_siblings )
361
+ end
362
+ #puts "FOLLOWING_SIBLING 2: nodeset = #{nodeset}"
363
+ nodeset = results
364
+
365
+ when :preceding_sibling
366
+ results = []
367
+ nodeset.each do |node|
368
+ next if node.parent.nil?
369
+ all_siblings = node.parent.children
370
+ current_index = all_siblings.index( node )
371
+ preceding_siblings = all_siblings[ 0, current_index ].reverse
372
+ results += preceding_siblings
373
+ end
374
+ nodeset = results
375
+ node_types = ELEMENTS
376
+
377
+ when :preceding
378
+ new_nodeset = []
379
+ for node in nodeset
380
+ new_nodeset += preceding( node )
381
+ end
382
+ #puts "NEW NODESET => #{new_nodeset.inspect}"
383
+ nodeset = new_nodeset
384
+ node_types = ELEMENTS
385
+
386
+ when :following
387
+ new_nodeset = []
388
+ for node in nodeset
389
+ new_nodeset += following( node )
390
+ end
391
+ nodeset = new_nodeset
392
+ node_types = ELEMENTS
393
+
394
+ when :namespace
395
+ #puts "In :namespace"
396
+ new_nodeset = []
397
+ prefix = path_stack.shift
398
+ for node in nodeset
399
+ if (node.node_type == :element or node.node_type == :attribute)
400
+ if @namespaces
401
+ namespaces = @namespaces
402
+ elsif (node.node_type == :element)
403
+ namespaces = node.namespaces
404
+ else
405
+ namespaces = node.element.namesapces
406
+ end
407
+ #puts "Namespaces = #{namespaces.inspect}"
408
+ #puts "Prefix = #{prefix.inspect}"
409
+ #puts "Node.namespace = #{node.namespace}"
410
+ if (node.namespace == namespaces[prefix])
411
+ new_nodeset << node
412
+ end
413
+ end
414
+ end
415
+ nodeset = new_nodeset
416
+
417
+ when :variable
418
+ var_name = path_stack.shift
419
+ return @variables[ var_name ]
420
+
421
+ # :and, :or, :eq, :neq, :lt, :lteq, :gt, :gteq
422
+ # TODO: Special case for :or and :and -- not evaluate the right
423
+ # operand if the left alone determines result (i.e. is true for
424
+ # :or and false for :and).
425
+ when :eq, :neq, :lt, :lteq, :gt, :gteq, :and, :or
426
+ left = expr( path_stack.shift, nodeset.dup, context )
427
+ #puts "LEFT => #{left.inspect} (#{left.class.name})"
428
+ right = expr( path_stack.shift, nodeset.dup, context )
429
+ #puts "RIGHT => #{right.inspect} (#{right.class.name})"
430
+ res = equality_relational_compare( left, op, right )
431
+ #puts "RES => #{res.inspect}"
432
+ return res
433
+
434
+ when :and
435
+ left = expr( path_stack.shift, nodeset.dup, context )
436
+ #puts "LEFT => #{left.inspect} (#{left.class.name})"
437
+ if left == false || left.nil? || !left.inject(false) {|a,b| a | b}
438
+ return []
439
+ end
440
+ right = expr( path_stack.shift, nodeset.dup, context )
441
+ #puts "RIGHT => #{right.inspect} (#{right.class.name})"
442
+ res = equality_relational_compare( left, op, right )
443
+ #puts "RES => #{res.inspect}"
444
+ return res
445
+
446
+ when :div
447
+ left = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
448
+ right = Functions::number(expr(path_stack.shift, nodeset, context)).to_f
449
+ return (left / right)
450
+
451
+ when :mod
452
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
453
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
454
+ return (left % right)
455
+
456
+ when :mult
457
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
458
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
459
+ return (left * right)
460
+
461
+ when :plus
462
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
463
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
464
+ return (left + right)
465
+
466
+ when :minus
467
+ left = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
468
+ right = Functions::number(expr(path_stack.shift, nodeset, context )).to_f
469
+ return (left - right)
470
+
471
+ when :union
472
+ left = expr( path_stack.shift, nodeset, context )
473
+ right = expr( path_stack.shift, nodeset, context )
474
+ return (left | right)
475
+
476
+ when :neg
477
+ res = expr( path_stack, nodeset, context )
478
+ return -(res.to_f)
479
+
480
+ when :not
481
+ when :function
482
+ func_name = path_stack.shift.tr('-','_')
483
+ arguments = path_stack.shift
484
+ #puts "FUNCTION 0: #{func_name}(#{arguments.collect{|a|a.inspect}.join(', ')})"
485
+ subcontext = context ? nil : { :size => nodeset.size }
486
+
487
+ res = []
488
+ cont = context
489
+ nodeset.each_with_index { |n, i|
490
+ if subcontext
491
+ subcontext[:node] = n
492
+ subcontext[:index] = i
493
+ cont = subcontext
494
+ end
495
+ arg_clone = arguments.dclone
496
+ args = arg_clone.collect { |arg|
497
+ #puts "FUNCTION 1: Calling expr( #{arg.inspect}, [#{n.inspect}] )"
498
+ expr( arg, [n], cont )
499
+ }
500
+ #puts "FUNCTION 2: #{func_name}(#{args.collect{|a|a.inspect}.join(', ')})"
501
+ Functions.context = cont
502
+ res << Functions.send( func_name, *args )
503
+ #puts "FUNCTION 3: #{res[-1].inspect}"
504
+ }
505
+ return res
506
+
507
+ end
508
+ end # while
509
+ #puts "EXPR returning #{nodeset.inspect}"
510
+ return nodeset
511
+ end
512
+
513
+
514
+ ##########################################################
515
+ # FIXME
516
+ # The next two methods are BAD MOJO!
517
+ # This is my achilles heel. If anybody thinks of a better
518
+ # way of doing this, be my guest. This really sucks, but
519
+ # it is a wonder it works at all.
520
+ # ########################################################
521
+
522
+ def descendant_or_self( path_stack, nodeset )
523
+ rs = []
524
+ #puts "#"*80
525
+ #puts "PATH_STACK = #{path_stack.inspect}"
526
+ #puts "NODESET = #{nodeset.collect{|n|n.inspect}.inspect}"
527
+ d_o_s( path_stack, nodeset, rs )
528
+ #puts "RS = #{rs.collect{|n|n.inspect}.inspect}"
529
+ document_order(rs.flatten.compact)
530
+ #rs.flatten.compact
531
+ end
532
+
533
+ def d_o_s( p, ns, r )
534
+ #puts "IN DOS with #{ns.inspect}; ALREADY HAVE #{r.inspect}"
535
+ nt = nil
536
+ ns.each_index do |i|
537
+ n = ns[i]
538
+ #puts "P => #{p.inspect}"
539
+ x = expr( p.dclone, [ n ] )
540
+ nt = n.node_type
541
+ d_o_s( p, n.children, x ) if nt == :element or nt == :document and n.children.size > 0
542
+ r.concat(x) if x.size > 0
543
+ end
544
+ end
545
+
546
+
547
+ # Reorders an array of nodes so that they are in document order
548
+ # It tries to do this efficiently.
549
+ #
550
+ # FIXME: I need to get rid of this, but the issue is that most of the XPath
551
+ # interpreter functions as a filter, which means that we lose context going
552
+ # in and out of function calls. If I knew what the index of the nodes was,
553
+ # I wouldn't have to do this. Maybe add a document IDX for each node?
554
+ # Problems with mutable documents. Or, rewrite everything.
555
+ def document_order( array_of_nodes )
556
+ new_arry = []
557
+ array_of_nodes.each { |node|
558
+ node_idx = []
559
+ np = node.node_type == :attribute ? node.element : node
560
+ while np.parent and np.parent.node_type == :element
561
+ node_idx << np.parent.index( np )
562
+ np = np.parent
563
+ end
564
+ new_arry << [ node_idx.reverse, node ]
565
+ }
566
+ #puts "new_arry = #{new_arry.inspect}"
567
+ new_arry.sort{ |s1, s2| s1[0] <=> s2[0] }.collect{ |s| s[1] }
568
+ end
569
+
570
+
571
+ def recurse( nodeset, &block )
572
+ for node in nodeset
573
+ yield node
574
+ recurse( node, &block ) if node.node_type == :element
575
+ end
576
+ end
577
+
578
+
579
+
580
+ # Builds a nodeset of all of the preceding nodes of the supplied node,
581
+ # in reverse document order
582
+ # preceding:: includes every element in the document that precedes this node,
583
+ # except for ancestors
584
+ def preceding( node )
585
+ #puts "IN PRECEDING"
586
+ ancestors = []
587
+ p = node.parent
588
+ while p
589
+ ancestors << p
590
+ p = p.parent
591
+ end
592
+
593
+ acc = []
594
+ p = preceding_node_of( node )
595
+ #puts "P = #{p.inspect}"
596
+ while p
597
+ if ancestors.include? p
598
+ ancestors.delete(p)
599
+ else
600
+ acc << p
601
+ end
602
+ p = preceding_node_of( p )
603
+ #puts "P = #{p.inspect}"
604
+ end
605
+ acc
606
+ end
607
+
608
+ def preceding_node_of( node )
609
+ #puts "NODE: #{node.inspect}"
610
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
611
+ #puts "PARENT NODE: #{node.parent}"
612
+ psn = node.previous_sibling_node
613
+ if psn.nil?
614
+ if node.parent.nil? or node.parent.class == Document
615
+ return nil
616
+ end
617
+ return node.parent
618
+ #psn = preceding_node_of( node.parent )
619
+ end
620
+ while psn and psn.kind_of? Element and psn.children.size > 0
621
+ psn = psn.children[-1]
622
+ end
623
+ psn
624
+ end
625
+
626
+ def following( node )
627
+ #puts "IN PRECEDING"
628
+ acc = []
629
+ p = next_sibling_node( node )
630
+ #puts "P = #{p.inspect}"
631
+ while p
632
+ acc << p
633
+ p = following_node_of( p )
634
+ #puts "P = #{p.inspect}"
635
+ end
636
+ acc
637
+ end
638
+
639
+ def following_node_of( node )
640
+ #puts "NODE: #{node.inspect}"
641
+ #puts "PREVIOUS NODE: #{node.previous_sibling_node.inspect}"
642
+ #puts "PARENT NODE: #{node.parent}"
643
+ if node.kind_of? Element and node.children.size > 0
644
+ return node.children[0]
645
+ end
646
+ return next_sibling_node(node)
647
+ end
648
+
649
+ def next_sibling_node(node)
650
+ psn = node.next_sibling_node
651
+ while psn.nil?
652
+ if node.parent.nil? or node.parent.class == Document
653
+ return nil
654
+ end
655
+ node = node.parent
656
+ psn = node.next_sibling_node
657
+ #puts "psn = #{psn.inspect}"
658
+ end
659
+ return psn
660
+ end
661
+
662
+ def norm b
663
+ case b
664
+ when true, false
665
+ return b
666
+ when 'true', 'false'
667
+ return Functions::boolean( b )
668
+ when /^\d+(\.\d+)?$/
669
+ return Functions::number( b )
670
+ else
671
+ return Functions::string( b )
672
+ end
673
+ end
674
+
675
+ def equality_relational_compare( set1, op, set2 )
676
+ #puts "EQ_REL_COMP(#{set1.inspect} #{op.inspect} #{set2.inspect})"
677
+ if set1.kind_of? Array and set2.kind_of? Array
678
+ #puts "#{set1.size} & #{set2.size}"
679
+ if set1.size == 1 and set2.size == 1
680
+ set1 = set1[0]
681
+ set2 = set2[0]
682
+ elsif set1.size == 0 or set2.size == 0
683
+ nd = set1.size==0 ? set2 : set1
684
+ rv = nd.collect { |il| compare( il, op, nil ) }
685
+ #puts "RV = #{rv.inspect}"
686
+ return rv
687
+ else
688
+ res = []
689
+ enum = SyncEnumerator.new( set1, set2 ).each { |i1, i2|
690
+ #puts "i1 = #{i1.inspect} (#{i1.class.name})"
691
+ #puts "i2 = #{i2.inspect} (#{i2.class.name})"
692
+ i1 = norm( i1 )
693
+ i2 = norm( i2 )
694
+ res << compare( i1, op, i2 )
695
+ }
696
+ return res
697
+ end
698
+ end
699
+ #puts "EQ_REL_COMP: #{set1.inspect} (#{set1.class.name}), #{op}, #{set2.inspect} (#{set2.class.name})"
700
+ #puts "COMPARING VALUES"
701
+ # If one is nodeset and other is number, compare number to each item
702
+ # in nodeset s.t. number op number(string(item))
703
+ # If one is nodeset and other is string, compare string to each item
704
+ # in nodeset s.t. string op string(item)
705
+ # If one is nodeset and other is boolean, compare boolean to each item
706
+ # in nodeset s.t. boolean op boolean(item)
707
+ if set1.kind_of? Array or set2.kind_of? Array
708
+ #puts "ISA ARRAY"
709
+ if set1.kind_of? Array
710
+ a = set1
711
+ b = set2
712
+ else
713
+ a = set2
714
+ b = set1
715
+ end
716
+
717
+ case b
718
+ when true, false
719
+ return a.collect {|v| compare( Functions::boolean(v), op, b ) }
720
+ when Numeric
721
+ return a.collect {|v| compare( Functions::number(v), op, b )}
722
+ when /^\d+(\.\d+)?$/
723
+ b = Functions::number( b )
724
+ #puts "B = #{b.inspect}"
725
+ return a.collect {|v| compare( Functions::number(v), op, b )}
726
+ else
727
+ #puts "Functions::string( #{b}(#{b.class.name}) ) = #{Functions::string(b)}"
728
+ b = Functions::string( b )
729
+ return a.collect { |v| compare( Functions::string(v), op, b ) }
730
+ end
731
+ else
732
+ # If neither is nodeset,
733
+ # If op is = or !=
734
+ # If either boolean, convert to boolean
735
+ # If either number, convert to number
736
+ # Else, convert to string
737
+ # Else
738
+ # Convert both to numbers and compare
739
+ s1 = set1.to_s
740
+ s2 = set2.to_s
741
+ #puts "EQ_REL_COMP: #{set1}=>#{s1}, #{set2}=>#{s2}"
742
+ if s1 == 'true' or s1 == 'false' or s2 == 'true' or s2 == 'false'
743
+ #puts "Functions::boolean(#{set1})=>#{Functions::boolean(set1)}"
744
+ #puts "Functions::boolean(#{set2})=>#{Functions::boolean(set2)}"
745
+ set1 = Functions::boolean( set1 )
746
+ set2 = Functions::boolean( set2 )
747
+ else
748
+ if op == :eq or op == :neq
749
+ if s1 =~ /^\d+(\.\d+)?$/ or s2 =~ /^\d+(\.\d+)?$/
750
+ set1 = Functions::number( s1 )
751
+ set2 = Functions::number( s2 )
752
+ else
753
+ set1 = Functions::string( set1 )
754
+ set2 = Functions::string( set2 )
755
+ end
756
+ else
757
+ set1 = Functions::number( set1 )
758
+ set2 = Functions::number( set2 )
759
+ end
760
+ end
761
+ #puts "EQ_REL_COMP: #{set1} #{op} #{set2}"
762
+ #puts ">>> #{compare( set1, op, set2 )}"
763
+ return compare( set1, op, set2 )
764
+ end
765
+ return false
766
+ end
767
+
768
+ def compare a, op, b
769
+ #puts "COMPARE #{a.inspect}(#{a.class.name}) #{op} #{b.inspect}(#{b.class.name})"
770
+ case op
771
+ when :eq
772
+ a == b
773
+ when :neq
774
+ a != b
775
+ when :lt
776
+ a < b
777
+ when :lteq
778
+ a <= b
779
+ when :gt
780
+ a > b
781
+ when :gteq
782
+ a >= b
783
+ when :and
784
+ a and b
785
+ when :or
786
+ a or b
787
+ else
788
+ false
789
+ end
790
+ end
791
+ end
792
+ end