PageRenderTime 131ms CodeModel.GetById 40ms app.highlight 56ms RepoModel.GetById 14ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/rexml/quickpath.rb

http://github.com/agross/netopenspace
Ruby | 266 lines | 218 code | 15 blank | 33 comment | 27 complexity | 8177182db9da55183dc38f3beadf7ba2 MD5 | raw file
  1require 'rexml/functions'
  2require 'rexml/xmltokens'
  3
  4module REXML
  5	class QuickPath
  6		include Functions
  7		include XMLTokens
  8
  9		EMPTY_HASH = {}
 10
 11		def QuickPath::first element, path, namespaces=EMPTY_HASH
 12			match(element, path, namespaces)[0]
 13		end
 14
 15		def QuickPath::each element, path, namespaces=EMPTY_HASH, &block
 16			path = "*" unless path
 17			match(element, path, namespaces).each( &block )
 18		end
 19
 20		def QuickPath::match element, path, namespaces=EMPTY_HASH
 21			raise "nil is not a valid xpath" unless path
 22			results = nil
 23			Functions::namespace_context = namespaces
 24			case path
 25			when /^\/([^\/]|$)/u
 26				# match on root
 27				path = path[1..-1]
 28				return [element.root.parent] if path == ''
 29				results = filter([element.root], path)
 30			when /^[-\w]*::/u
 31				results = filter([element], path)
 32			when /^\*/u
 33				results = filter(element.to_a, path)
 34			when /^[\[!\w:]/u
 35				# match on child
 36				matches = []
 37				children = element.to_a
 38				results = filter(children, path)
 39			else
 40				results = filter([element], path)
 41			end
 42			return results
 43		end
 44
 45		# Given an array of nodes it filters the array based on the path. The
 46		# result is that when this method returns, the array will contain elements
 47		# which match the path
 48		def QuickPath::filter elements, path
 49			return elements if path.nil? or path == '' or elements.size == 0
 50			case path
 51			when /^\/\//u											# Descendant
 52				return axe( elements, "descendant-or-self", $' )
 53			when /^\/?\b(\w[-\w]*)\b::/u							# Axe
 54				axe_name = $1
 55				rest = $'
 56				return axe( elements, $1, $' )
 57			when /^\/(?=\b([:!\w][-\.\w]*:)?[-!\*\.\w]*\b([^:(]|$)|\*)/u	# Child
 58				rest = $'
 59				results = []
 60				elements.each do |element|
 61					results |= filter( element.to_a, rest )
 62				end
 63				return results
 64			when /^\/?(\w[-\w]*)\(/u							# / Function
 65				return function( elements, $1, $' )
 66			when Namespace::NAMESPLIT		# Element name
 67				name = $2
 68				ns = $1
 69				rest = $'
 70				elements.delete_if do |element|
 71					!(element.kind_of? Element and 
 72						(element.expanded_name == name or
 73						 (element.name == name and
 74						  element.namespace == Functions.namespace_context[ns])))
 75				end
 76				return filter( elements, rest )
 77			when /^\/\[/u
 78				matches = []
 79				elements.each do |element|
 80					matches |= predicate( element.to_a, path[1..-1] ) if element.kind_of? Element
 81				end
 82				return matches
 83			when /^\[/u												# Predicate
 84				return predicate( elements, path )
 85			when /^\/?\.\.\./u										# Ancestor
 86				return axe( elements, "ancestor", $' )
 87			when /^\/?\.\./u											# Parent
 88				return filter( elements.collect{|e|e.parent}, $' )
 89			when /^\/?\./u												# Self
 90				return filter( elements, $' )
 91			when /^\*/u													# Any
 92				results = []
 93				elements.each do |element|
 94					results |= filter( [element], $' ) if element.kind_of? Element
 95					#if element.kind_of? Element
 96					#	children = element.to_a
 97					#	children.delete_if { |child| !child.kind_of?(Element) }
 98					#	results |= filter( children, $' )
 99					#end
100				end
101				return results
102			end
103			return []
104		end
105
106		def QuickPath::axe( elements, axe_name, rest )
107			matches = []
108			matches = filter( elements.dup, rest ) if axe_name =~ /-or-self$/u
109			case axe_name
110			when /^descendant/u
111				elements.each do |element|
112					matches |= filter( element.to_a, "descendant-or-self::#{rest}" ) if element.kind_of? Element
113				end
114			when /^ancestor/u
115				elements.each do |element|
116					while element.parent
117						matches << element.parent
118						element = element.parent
119					end
120				end
121				matches = filter( matches, rest )
122			when "self"
123				matches = filter( elements, rest )
124			when "child"
125				elements.each do |element|
126					matches |= filter( element.to_a, rest ) if element.kind_of? Element
127				end
128			when "attribute"
129				elements.each do |element|
130					matches << element.attributes[ rest ] if element.kind_of? Element
131				end
132			when "parent"
133				matches = filter(elements.collect{|element| element.parent}.uniq, rest)
134			when "following-sibling"
135				matches = filter(elements.collect{|element| element.next_sibling}.uniq,
136					rest)
137			when "previous-sibling"
138				matches = filter(elements.collect{|element| 
139					element.previous_sibling}.uniq, rest )
140			end
141			return matches.uniq
142		end
143
144		# A predicate filters a node-set with respect to an axis to produce a
145		# new node-set. For each node in the node-set to be filtered, the 
146		# PredicateExpr is evaluated with that node as the context node, with 
147		# the number of nodes in the node-set as the context size, and with the 
148		# proximity position of the node in the node-set with respect to the
149		# axis as the context position; if PredicateExpr evaluates to true for
150		# that node, the node is included in the new node-set; otherwise, it is
151		# not included.
152		#
153		# A PredicateExpr is evaluated by evaluating the Expr and converting
154		# the result to a boolean. If the result is a number, the result will
155		# be converted to true if the number is equal to the context position
156		# and will be converted to false otherwise; if the result is not a
157		# number, then the result will be converted as if by a call to the
158		# boolean function. Thus a location path para[3] is equivalent to
159		# para[position()=3].
160		def QuickPath::predicate( elements, path ) 
161			ind = 1
162			bcount = 1
163			while bcount > 0
164				bcount += 1 if path[ind] == ?[
165				bcount -= 1 if path[ind] == ?]
166				ind += 1
167			end
168			ind -= 1
169			predicate = path[1..ind-1]
170			rest = path[ind+1..-1]
171
172			# have to change 'a [=<>] b [=<>] c' into 'a [=<>] b and b [=<>] c'
173			predicate.gsub!( /([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)/u ) { 
174				"#$1 #$2 #$3 and #$3 #$4 #$5"
175			}
176			# Let's do some Ruby trickery to avoid some work:
177			predicate.gsub!( /&/u, "&&" )
178			predicate.gsub!( /=/u, "==" )
179			predicate.gsub!( /@(\w[-\w.]*)/u ) {
180				"attribute(\"#$1\")" 
181			}
182			predicate.gsub!( /\bmod\b/u, "%" )
183			predicate.gsub!( /\b(\w[-\w.]*\()/u ) {
184				fname = $1
185				fname.gsub( /-/u, "_" )
186			}
187			
188			Functions.pair = [ 0, elements.size ]
189			results = []
190			elements.each do |element|
191				Functions.pair[0] += 1
192				Functions.node = element
193				res = eval( predicate )
194				case res
195				when true
196					results << element
197				when Fixnum
198					results << element if Functions.pair[0] == res
199				when String
200					results << element
201				end
202			end
203			return filter( results, rest )
204		end
205
206		def QuickPath::attribute( name )
207			return Functions.node.attributes[name] if Functions.node.kind_of? Element
208		end
209
210		def QuickPath::name()
211			return Functions.node.name if Functions.node.kind_of? Element
212		end
213
214		def QuickPath::method_missing( id, *args )
215			begin
216				Functions.send( id.id2name, *args )
217			rescue Exception
218				raise "METHOD: #{id.id2name}(#{args.join ', '})\n#{$!.message}"
219			end
220		end
221
222		def QuickPath::function( elements, fname, rest )
223			args = parse_args( elements, rest )
224			Functions.pair = [0, elements.size]
225			results = []
226			elements.each do |element|
227				Functions.pair[0] += 1
228				Functions.node = element
229				res = Functions.send( fname, *args )
230				case res
231				when true
232					results << element
233				when Fixnum
234					results << element if Functions.pair[0] == res
235				end
236			end
237			return results
238		end
239
240		def QuickPath::parse_args( element, string )
241			# /.*?(?:\)|,)/
242			arguments = []
243			buffer = ""
244			while string and string != ""
245				c = string[0]
246				string.sub!(/^./u, "")
247				case c
248				when ?,
249					# if depth = 1, then we start a new argument
250					arguments << evaluate( buffer )
251					#arguments << evaluate( string[0..count] )
252				when ?(
253					# start a new method call
254					function( element, buffer, string )
255					buffer = ""
256				when ?)
257					# close the method call and return arguments
258					return arguments
259				else
260					buffer << c
261				end
262			end
263			""
264		end
265	end
266end