rexml 3.1.7.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rexml might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +60 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/rexml/attlistdecl.rb +63 -0
  11. data/lib/rexml/attribute.rb +192 -0
  12. data/lib/rexml/cdata.rb +68 -0
  13. data/lib/rexml/child.rb +97 -0
  14. data/lib/rexml/comment.rb +80 -0
  15. data/lib/rexml/doctype.rb +270 -0
  16. data/lib/rexml/document.rb +291 -0
  17. data/lib/rexml/dtd/attlistdecl.rb +11 -0
  18. data/lib/rexml/dtd/dtd.rb +47 -0
  19. data/lib/rexml/dtd/elementdecl.rb +18 -0
  20. data/lib/rexml/dtd/entitydecl.rb +57 -0
  21. data/lib/rexml/dtd/notationdecl.rb +40 -0
  22. data/lib/rexml/element.rb +1267 -0
  23. data/lib/rexml/encoding.rb +51 -0
  24. data/lib/rexml/entity.rb +171 -0
  25. data/lib/rexml/formatters/default.rb +112 -0
  26. data/lib/rexml/formatters/pretty.rb +142 -0
  27. data/lib/rexml/formatters/transitive.rb +58 -0
  28. data/lib/rexml/functions.rb +447 -0
  29. data/lib/rexml/instruction.rb +71 -0
  30. data/lib/rexml/light/node.rb +196 -0
  31. data/lib/rexml/namespace.rb +48 -0
  32. data/lib/rexml/node.rb +76 -0
  33. data/lib/rexml/output.rb +30 -0
  34. data/lib/rexml/parent.rb +166 -0
  35. data/lib/rexml/parseexception.rb +52 -0
  36. data/lib/rexml/parsers/baseparser.rb +586 -0
  37. data/lib/rexml/parsers/lightparser.rb +59 -0
  38. data/lib/rexml/parsers/pullparser.rb +197 -0
  39. data/lib/rexml/parsers/sax2parser.rb +273 -0
  40. data/lib/rexml/parsers/streamparser.rb +61 -0
  41. data/lib/rexml/parsers/treeparser.rb +101 -0
  42. data/lib/rexml/parsers/ultralightparser.rb +57 -0
  43. data/lib/rexml/parsers/xpathparser.rb +675 -0
  44. data/lib/rexml/quickpath.rb +266 -0
  45. data/lib/rexml/rexml.rb +32 -0
  46. data/lib/rexml/sax2listener.rb +98 -0
  47. data/lib/rexml/security.rb +28 -0
  48. data/lib/rexml/source.rb +298 -0
  49. data/lib/rexml/streamlistener.rb +93 -0
  50. data/lib/rexml/syncenumerator.rb +33 -0
  51. data/lib/rexml/text.rb +424 -0
  52. data/lib/rexml/undefinednamespaceexception.rb +9 -0
  53. data/lib/rexml/validation/relaxng.rb +539 -0
  54. data/lib/rexml/validation/validation.rb +144 -0
  55. data/lib/rexml/validation/validationexception.rb +10 -0
  56. data/lib/rexml/xmldecl.rb +116 -0
  57. data/lib/rexml/xmltokens.rb +85 -0
  58. data/lib/rexml/xpath.rb +81 -0
  59. data/lib/rexml/xpath_parser.rb +934 -0
  60. data/rexml.gemspec +42 -0
  61. metadata +131 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: false
2
+ require_relative "baseparser"
3
+
4
+ module REXML
5
+ module Parsers
6
+ class StreamParser
7
+ def initialize source, listener
8
+ @listener = listener
9
+ @parser = BaseParser.new( source )
10
+ @tag_stack = []
11
+ end
12
+
13
+ def add_listener( listener )
14
+ @parser.add_listener( listener )
15
+ end
16
+
17
+ def parse
18
+ # entity string
19
+ while true
20
+ event = @parser.pull
21
+ case event[0]
22
+ when :end_document
23
+ unless @tag_stack.empty?
24
+ tag_path = "/" + @tag_stack.join("/")
25
+ raise ParseException.new("Missing end tag for '#{tag_path}'",
26
+ @parser.source)
27
+ end
28
+ return
29
+ when :start_element
30
+ @tag_stack << event[1]
31
+ attrs = event[2].each do |n, v|
32
+ event[2][n] = @parser.unnormalize( v )
33
+ end
34
+ @listener.tag_start( event[1], attrs )
35
+ when :end_element
36
+ @listener.tag_end( event[1] )
37
+ @tag_stack.pop
38
+ when :text
39
+ normalized = @parser.unnormalize( event[1] )
40
+ @listener.text( normalized )
41
+ when :processing_instruction
42
+ @listener.instruction( *event[1,2] )
43
+ when :start_doctype
44
+ @listener.doctype( *event[1..-1] )
45
+ when :end_doctype
46
+ # FIXME: remove this condition for milestone:3.2
47
+ @listener.doctype_end if @listener.respond_to? :doctype_end
48
+ when :comment, :attlistdecl, :cdata, :xmldecl, :elementdecl
49
+ @listener.send( event[0].to_s, *event[1..-1] )
50
+ when :entitydecl, :notationdecl
51
+ @listener.send( event[0].to_s, event[1..-1] )
52
+ when :externalentity
53
+ entity_reference = event[1]
54
+ content = entity_reference.gsub(/\A%|;\z/, "")
55
+ @listener.entity(content)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: false
2
+ require_relative '../validation/validationexception'
3
+ require_relative '../undefinednamespaceexception'
4
+
5
+ module REXML
6
+ module Parsers
7
+ class TreeParser
8
+ def initialize( source, build_context = Document.new )
9
+ @build_context = build_context
10
+ @parser = Parsers::BaseParser.new( source )
11
+ end
12
+
13
+ def add_listener( listener )
14
+ @parser.add_listener( listener )
15
+ end
16
+
17
+ def parse
18
+ tag_stack = []
19
+ in_doctype = false
20
+ entities = nil
21
+ begin
22
+ while true
23
+ event = @parser.pull
24
+ #STDERR.puts "TREEPARSER GOT #{event.inspect}"
25
+ case event[0]
26
+ when :end_document
27
+ unless tag_stack.empty?
28
+ raise ParseException.new("No close tag for #{@build_context.xpath}",
29
+ @parser.source, @parser)
30
+ end
31
+ return
32
+ when :start_element
33
+ tag_stack.push(event[1])
34
+ el = @build_context = @build_context.add_element( event[1] )
35
+ event[2].each do |key, value|
36
+ el.attributes[key]=Attribute.new(key,value,self)
37
+ end
38
+ when :end_element
39
+ tag_stack.pop
40
+ @build_context = @build_context.parent
41
+ when :text
42
+ if not in_doctype
43
+ if @build_context[-1].instance_of? Text
44
+ @build_context[-1] << event[1]
45
+ else
46
+ @build_context.add(
47
+ Text.new(event[1], @build_context.whitespace, nil, true)
48
+ ) unless (
49
+ @build_context.ignore_whitespace_nodes and
50
+ event[1].strip.size==0
51
+ )
52
+ end
53
+ end
54
+ when :comment
55
+ c = Comment.new( event[1] )
56
+ @build_context.add( c )
57
+ when :cdata
58
+ c = CData.new( event[1] )
59
+ @build_context.add( c )
60
+ when :processing_instruction
61
+ @build_context.add( Instruction.new( event[1], event[2] ) )
62
+ when :end_doctype
63
+ in_doctype = false
64
+ entities.each { |k,v| entities[k] = @build_context.entities[k].value }
65
+ @build_context = @build_context.parent
66
+ when :start_doctype
67
+ doctype = DocType.new( event[1..-1], @build_context )
68
+ @build_context = doctype
69
+ entities = {}
70
+ in_doctype = true
71
+ when :attlistdecl
72
+ n = AttlistDecl.new( event[1..-1] )
73
+ @build_context.add( n )
74
+ when :externalentity
75
+ n = ExternalEntity.new( event[1] )
76
+ @build_context.add( n )
77
+ when :elementdecl
78
+ n = ElementDecl.new( event[1] )
79
+ @build_context.add(n)
80
+ when :entitydecl
81
+ entities[ event[1] ] = event[2] unless event[2] =~ /PUBLIC|SYSTEM/
82
+ @build_context.add(Entity.new(event))
83
+ when :notationdecl
84
+ n = NotationDecl.new( *event[1..-1] )
85
+ @build_context.add( n )
86
+ when :xmldecl
87
+ x = XMLDecl.new( event[1], event[2], event[3] )
88
+ @build_context.add( x )
89
+ end
90
+ end
91
+ rescue REXML::Validation::ValidationException
92
+ raise
93
+ rescue REXML::ParseException
94
+ raise
95
+ rescue
96
+ raise ParseException.new( $!.message, @parser.source, @parser, $! )
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: false
2
+ require_relative 'streamparser'
3
+ require_relative 'baseparser'
4
+
5
+ module REXML
6
+ module Parsers
7
+ class UltraLightParser
8
+ def initialize stream
9
+ @stream = stream
10
+ @parser = REXML::Parsers::BaseParser.new( stream )
11
+ end
12
+
13
+ def add_listener( listener )
14
+ @parser.add_listener( listener )
15
+ end
16
+
17
+ def rewind
18
+ @stream.rewind
19
+ @parser.stream = @stream
20
+ end
21
+
22
+ def parse
23
+ root = context = []
24
+ while true
25
+ event = @parser.pull
26
+ case event[0]
27
+ when :end_document
28
+ break
29
+ when :end_doctype
30
+ context = context[1]
31
+ when :start_element, :start_doctype
32
+ context << event
33
+ event[1,0] = [context]
34
+ context = event
35
+ when :end_element
36
+ context = context[1]
37
+ else
38
+ context << event
39
+ end
40
+ end
41
+ root
42
+ end
43
+ end
44
+
45
+ # An element is an array. The array contains:
46
+ # 0 The parent element
47
+ # 1 The tag name
48
+ # 2 A hash of attributes
49
+ # 3..-1 The child elements
50
+ # An element is an array of size > 3
51
+ # Text is a String
52
+ # PIs are [ :processing_instruction, target, data ]
53
+ # Comments are [ :comment, data ]
54
+ # DocTypes are DocType structs
55
+ # The root is an array with XMLDecls, Text, DocType, Array, Text
56
+ end
57
+ end
@@ -0,0 +1,675 @@
1
+ # frozen_string_literal: false
2
+ require_relative '../namespace'
3
+ require_relative '../xmltokens'
4
+
5
+ module REXML
6
+ module Parsers
7
+ # You don't want to use this class. Really. Use XPath, which is a wrapper
8
+ # for this class. Believe me. You don't want to poke around in here.
9
+ # There is strange, dark magic at work in this code. Beware. Go back! Go
10
+ # back while you still can!
11
+ class XPathParser
12
+ include XMLTokens
13
+ LITERAL = /^'([^']*)'|^"([^"]*)"/u
14
+
15
+ def namespaces=( namespaces )
16
+ Functions::namespace_context = namespaces
17
+ @namespaces = namespaces
18
+ end
19
+
20
+ def parse path
21
+ path = path.dup
22
+ path.gsub!(/([\(\[])\s+/, '\1') # Strip ignorable spaces
23
+ path.gsub!( /\s+([\]\)])/, '\1')
24
+ parsed = []
25
+ OrExpr(path, parsed)
26
+ parsed
27
+ end
28
+
29
+ def predicate path
30
+ parsed = []
31
+ Predicate( "[#{path}]", parsed )
32
+ parsed
33
+ end
34
+
35
+ def abbreviate( path )
36
+ path = path.kind_of?(String) ? parse( path ) : path
37
+ string = ""
38
+ document = false
39
+ while path.size > 0
40
+ op = path.shift
41
+ case op
42
+ when :node
43
+ when :attribute
44
+ string << "/" if string.size > 0
45
+ string << "@"
46
+ when :child
47
+ string << "/" if string.size > 0
48
+ when :descendant_or_self
49
+ string << "/"
50
+ when :self
51
+ string << "."
52
+ when :parent
53
+ string << ".."
54
+ when :any
55
+ string << "*"
56
+ when :text
57
+ string << "text()"
58
+ when :following, :following_sibling,
59
+ :ancestor, :ancestor_or_self, :descendant,
60
+ :namespace, :preceding, :preceding_sibling
61
+ string << "/" unless string.size == 0
62
+ string << op.to_s.tr("_", "-")
63
+ string << "::"
64
+ when :qname
65
+ prefix = path.shift
66
+ name = path.shift
67
+ string << prefix+":" if prefix.size > 0
68
+ string << name
69
+ when :predicate
70
+ string << '['
71
+ string << predicate_to_string( path.shift ) {|x| abbreviate( x ) }
72
+ string << ']'
73
+ when :document
74
+ document = true
75
+ when :function
76
+ string << path.shift
77
+ string << "( "
78
+ string << predicate_to_string( path.shift[0] ) {|x| abbreviate( x )}
79
+ string << " )"
80
+ when :literal
81
+ string << %Q{ "#{path.shift}" }
82
+ else
83
+ string << "/" unless string.size == 0
84
+ string << "UNKNOWN("
85
+ string << op.inspect
86
+ string << ")"
87
+ end
88
+ end
89
+ string = "/"+string if document
90
+ return string
91
+ end
92
+
93
+ def expand( path )
94
+ path = path.kind_of?(String) ? parse( path ) : path
95
+ string = ""
96
+ document = false
97
+ while path.size > 0
98
+ op = path.shift
99
+ case op
100
+ when :node
101
+ string << "node()"
102
+ when :attribute, :child, :following, :following_sibling,
103
+ :ancestor, :ancestor_or_self, :descendant, :descendant_or_self,
104
+ :namespace, :preceding, :preceding_sibling, :self, :parent
105
+ string << "/" unless string.size == 0
106
+ string << op.to_s.tr("_", "-")
107
+ string << "::"
108
+ when :any
109
+ string << "*"
110
+ when :qname
111
+ prefix = path.shift
112
+ name = path.shift
113
+ string << prefix+":" if prefix.size > 0
114
+ string << name
115
+ when :predicate
116
+ string << '['
117
+ string << predicate_to_string( path.shift ) { |x| expand(x) }
118
+ string << ']'
119
+ when :document
120
+ document = true
121
+ else
122
+ string << "/" unless string.size == 0
123
+ string << "UNKNOWN("
124
+ string << op.inspect
125
+ string << ")"
126
+ end
127
+ end
128
+ string = "/"+string if document
129
+ return string
130
+ end
131
+
132
+ def predicate_to_string( path, &block )
133
+ string = ""
134
+ case path[0]
135
+ when :and, :or, :mult, :plus, :minus, :neq, :eq, :lt, :gt, :lteq, :gteq, :div, :mod, :union
136
+ op = path.shift
137
+ case op
138
+ when :eq
139
+ op = "="
140
+ when :lt
141
+ op = "<"
142
+ when :gt
143
+ op = ">"
144
+ when :lteq
145
+ op = "<="
146
+ when :gteq
147
+ op = ">="
148
+ when :neq
149
+ op = "!="
150
+ when :union
151
+ op = "|"
152
+ end
153
+ left = predicate_to_string( path.shift, &block )
154
+ right = predicate_to_string( path.shift, &block )
155
+ string << " "
156
+ string << left
157
+ string << " "
158
+ string << op.to_s
159
+ string << " "
160
+ string << right
161
+ string << " "
162
+ when :function
163
+ path.shift
164
+ name = path.shift
165
+ string << name
166
+ string << "( "
167
+ string << predicate_to_string( path.shift, &block )
168
+ string << " )"
169
+ when :literal
170
+ path.shift
171
+ string << " "
172
+ string << path.shift.inspect
173
+ string << " "
174
+ else
175
+ string << " "
176
+ string << yield( path )
177
+ string << " "
178
+ end
179
+ return string.squeeze(" ")
180
+ end
181
+
182
+ private
183
+ #LocationPath
184
+ # | RelativeLocationPath
185
+ # | '/' RelativeLocationPath?
186
+ # | '//' RelativeLocationPath
187
+ def LocationPath path, parsed
188
+ path = path.lstrip
189
+ if path[0] == ?/
190
+ parsed << :document
191
+ if path[1] == ?/
192
+ parsed << :descendant_or_self
193
+ parsed << :node
194
+ path = path[2..-1]
195
+ else
196
+ path = path[1..-1]
197
+ end
198
+ end
199
+ return RelativeLocationPath( path, parsed ) if path.size > 0
200
+ end
201
+
202
+ #RelativeLocationPath
203
+ # | Step
204
+ # | (AXIS_NAME '::' | '@' | '') AxisSpecifier
205
+ # NodeTest
206
+ # Predicate
207
+ # | '.' | '..' AbbreviatedStep
208
+ # | RelativeLocationPath '/' Step
209
+ # | RelativeLocationPath '//' Step
210
+ AXIS = /^(ancestor|ancestor-or-self|attribute|child|descendant|descendant-or-self|following|following-sibling|namespace|parent|preceding|preceding-sibling|self)::/
211
+ def RelativeLocationPath path, parsed
212
+ loop do
213
+ original_path = path
214
+ path = path.lstrip
215
+
216
+ return original_path if path.empty?
217
+
218
+ # (axis or @ or <child::>) nodetest predicate >
219
+ # OR > / Step
220
+ # (. or ..) >
221
+ if path[0] == ?.
222
+ if path[1] == ?.
223
+ parsed << :parent
224
+ parsed << :node
225
+ path = path[2..-1]
226
+ else
227
+ parsed << :self
228
+ parsed << :node
229
+ path = path[1..-1]
230
+ end
231
+ else
232
+ if path[0] == ?@
233
+ parsed << :attribute
234
+ path = path[1..-1]
235
+ # Goto Nodetest
236
+ elsif path =~ AXIS
237
+ parsed << $1.tr('-','_').intern
238
+ path = $'
239
+ # Goto Nodetest
240
+ else
241
+ parsed << :child
242
+ end
243
+
244
+ n = []
245
+ path = NodeTest( path, n)
246
+
247
+ path = Predicate( path, n )
248
+
249
+ parsed.concat(n)
250
+ end
251
+
252
+ original_path = path
253
+ path = path.lstrip
254
+ return original_path if path.empty?
255
+
256
+ return original_path if path[0] != ?/
257
+
258
+ if path[1] == ?/
259
+ parsed << :descendant_or_self
260
+ parsed << :node
261
+ path = path[2..-1]
262
+ else
263
+ path = path[1..-1]
264
+ end
265
+ end
266
+ end
267
+
268
+ # Returns a 1-1 map of the nodeset
269
+ # The contents of the resulting array are either:
270
+ # true/false, if a positive match
271
+ # String, if a name match
272
+ #NodeTest
273
+ # | ('*' | NCNAME ':' '*' | QNAME) NameTest
274
+ # | '*' ':' NCNAME NameTest since XPath 2.0
275
+ # | NODE_TYPE '(' ')' NodeType
276
+ # | PI '(' LITERAL ')' PI
277
+ # | '[' expr ']' Predicate
278
+ PREFIX_WILDCARD = /^\*:(#{NCNAME_STR})/u
279
+ LOCAL_NAME_WILDCARD = /^(#{NCNAME_STR}):\*/u
280
+ QNAME = Namespace::NAMESPLIT
281
+ NODE_TYPE = /^(comment|text|node)\(\s*\)/m
282
+ PI = /^processing-instruction\(/
283
+ def NodeTest path, parsed
284
+ original_path = path
285
+ path = path.lstrip
286
+ case path
287
+ when PREFIX_WILDCARD
288
+ prefix = nil
289
+ name = $1
290
+ path = $'
291
+ parsed << :qname
292
+ parsed << prefix
293
+ parsed << name
294
+ when /^\*/
295
+ path = $'
296
+ parsed << :any
297
+ when NODE_TYPE
298
+ type = $1
299
+ path = $'
300
+ parsed << type.tr('-', '_').intern
301
+ when PI
302
+ path = $'
303
+ literal = nil
304
+ if path !~ /^\s*\)/
305
+ path =~ LITERAL
306
+ literal = $1
307
+ path = $'
308
+ raise ParseException.new("Missing ')' after processing instruction") if path[0] != ?)
309
+ path = path[1..-1]
310
+ end
311
+ parsed << :processing_instruction
312
+ parsed << (literal || '')
313
+ when LOCAL_NAME_WILDCARD
314
+ prefix = $1
315
+ path = $'
316
+ parsed << :namespace
317
+ parsed << prefix
318
+ when QNAME
319
+ prefix = $1
320
+ name = $2
321
+ path = $'
322
+ prefix = "" unless prefix
323
+ parsed << :qname
324
+ parsed << prefix
325
+ parsed << name
326
+ else
327
+ path = original_path
328
+ end
329
+ return path
330
+ end
331
+
332
+ # Filters the supplied nodeset on the predicate(s)
333
+ def Predicate path, parsed
334
+ original_path = path
335
+ path = path.lstrip
336
+ return original_path unless path[0] == ?[
337
+ predicates = []
338
+ while path[0] == ?[
339
+ path, expr = get_group(path)
340
+ predicates << expr[1..-2] if expr
341
+ end
342
+ predicates.each{ |pred|
343
+ preds = []
344
+ parsed << :predicate
345
+ parsed << preds
346
+ OrExpr(pred, preds)
347
+ }
348
+ path
349
+ end
350
+
351
+ # The following return arrays of true/false, a 1-1 mapping of the
352
+ # supplied nodeset, except for axe(), which returns a filtered
353
+ # nodeset
354
+
355
+ #| OrExpr S 'or' S AndExpr
356
+ #| AndExpr
357
+ def OrExpr path, parsed
358
+ n = []
359
+ rest = AndExpr( path, n )
360
+ if rest != path
361
+ while rest =~ /^\s*( or )/
362
+ n = [ :or, n, [] ]
363
+ rest = AndExpr( $', n[-1] )
364
+ end
365
+ end
366
+ if parsed.size == 0 and n.size != 0
367
+ parsed.replace(n)
368
+ elsif n.size > 0
369
+ parsed << n
370
+ end
371
+ rest
372
+ end
373
+
374
+ #| AndExpr S 'and' S EqualityExpr
375
+ #| EqualityExpr
376
+ def AndExpr path, parsed
377
+ n = []
378
+ rest = EqualityExpr( path, n )
379
+ if rest != path
380
+ while rest =~ /^\s*( and )/
381
+ n = [ :and, n, [] ]
382
+ rest = EqualityExpr( $', n[-1] )
383
+ end
384
+ end
385
+ if parsed.size == 0 and n.size != 0
386
+ parsed.replace(n)
387
+ elsif n.size > 0
388
+ parsed << n
389
+ end
390
+ rest
391
+ end
392
+
393
+ #| EqualityExpr ('=' | '!=') RelationalExpr
394
+ #| RelationalExpr
395
+ def EqualityExpr path, parsed
396
+ n = []
397
+ rest = RelationalExpr( path, n )
398
+ if rest != path
399
+ while rest =~ /^\s*(!?=)\s*/
400
+ if $1[0] == ?!
401
+ n = [ :neq, n, [] ]
402
+ else
403
+ n = [ :eq, n, [] ]
404
+ end
405
+ rest = RelationalExpr( $', n[-1] )
406
+ end
407
+ end
408
+ if parsed.size == 0 and n.size != 0
409
+ parsed.replace(n)
410
+ elsif n.size > 0
411
+ parsed << n
412
+ end
413
+ rest
414
+ end
415
+
416
+ #| RelationalExpr ('<' | '>' | '<=' | '>=') AdditiveExpr
417
+ #| AdditiveExpr
418
+ def RelationalExpr path, parsed
419
+ n = []
420
+ rest = AdditiveExpr( path, n )
421
+ if rest != path
422
+ while rest =~ /^\s*([<>]=?)\s*/
423
+ if $1[0] == ?<
424
+ sym = "lt"
425
+ else
426
+ sym = "gt"
427
+ end
428
+ sym << "eq" if $1[-1] == ?=
429
+ n = [ sym.intern, n, [] ]
430
+ rest = AdditiveExpr( $', n[-1] )
431
+ end
432
+ end
433
+ if parsed.size == 0 and n.size != 0
434
+ parsed.replace(n)
435
+ elsif n.size > 0
436
+ parsed << n
437
+ end
438
+ rest
439
+ end
440
+
441
+ #| AdditiveExpr ('+' | '-') MultiplicativeExpr
442
+ #| MultiplicativeExpr
443
+ def AdditiveExpr path, parsed
444
+ n = []
445
+ rest = MultiplicativeExpr( path, n )
446
+ if rest != path
447
+ while rest =~ /^\s*(\+|-)\s*/
448
+ if $1[0] == ?+
449
+ n = [ :plus, n, [] ]
450
+ else
451
+ n = [ :minus, n, [] ]
452
+ end
453
+ rest = MultiplicativeExpr( $', n[-1] )
454
+ end
455
+ end
456
+ if parsed.size == 0 and n.size != 0
457
+ parsed.replace(n)
458
+ elsif n.size > 0
459
+ parsed << n
460
+ end
461
+ rest
462
+ end
463
+
464
+ #| MultiplicativeExpr ('*' | S ('div' | 'mod') S) UnaryExpr
465
+ #| UnaryExpr
466
+ def MultiplicativeExpr path, parsed
467
+ n = []
468
+ rest = UnaryExpr( path, n )
469
+ if rest != path
470
+ while rest =~ /^\s*(\*| div | mod )\s*/
471
+ if $1[0] == ?*
472
+ n = [ :mult, n, [] ]
473
+ elsif $1.include?( "div" )
474
+ n = [ :div, n, [] ]
475
+ else
476
+ n = [ :mod, n, [] ]
477
+ end
478
+ rest = UnaryExpr( $', n[-1] )
479
+ end
480
+ end
481
+ if parsed.size == 0 and n.size != 0
482
+ parsed.replace(n)
483
+ elsif n.size > 0
484
+ parsed << n
485
+ end
486
+ rest
487
+ end
488
+
489
+ #| '-' UnaryExpr
490
+ #| UnionExpr
491
+ def UnaryExpr path, parsed
492
+ path =~ /^(\-*)/
493
+ path = $'
494
+ if $1 and (($1.size % 2) != 0)
495
+ mult = -1
496
+ else
497
+ mult = 1
498
+ end
499
+ parsed << :neg if mult < 0
500
+
501
+ n = []
502
+ path = UnionExpr( path, n )
503
+ parsed.concat( n )
504
+ path
505
+ end
506
+
507
+ #| UnionExpr '|' PathExpr
508
+ #| PathExpr
509
+ def UnionExpr path, parsed
510
+ n = []
511
+ rest = PathExpr( path, n )
512
+ if rest != path
513
+ while rest =~ /^\s*(\|)\s*/
514
+ n = [ :union, n, [] ]
515
+ rest = PathExpr( $', n[-1] )
516
+ end
517
+ end
518
+ if parsed.size == 0 and n.size != 0
519
+ parsed.replace( n )
520
+ elsif n.size > 0
521
+ parsed << n
522
+ end
523
+ rest
524
+ end
525
+
526
+ #| LocationPath
527
+ #| FilterExpr ('/' | '//') RelativeLocationPath
528
+ def PathExpr path, parsed
529
+ path = path.lstrip
530
+ n = []
531
+ rest = FilterExpr( path, n )
532
+ if rest != path
533
+ if rest and rest[0] == ?/
534
+ rest = RelativeLocationPath(rest, n)
535
+ parsed.concat(n)
536
+ return rest
537
+ end
538
+ end
539
+ rest = LocationPath(rest, n) if rest =~ /\A[\/\.\@\[\w*]/
540
+ parsed.concat(n)
541
+ return rest
542
+ end
543
+
544
+ #| FilterExpr Predicate
545
+ #| PrimaryExpr
546
+ def FilterExpr path, parsed
547
+ n = []
548
+ path = PrimaryExpr( path, n )
549
+ path = Predicate(path, n)
550
+ parsed.concat(n)
551
+ path
552
+ end
553
+
554
+ #| VARIABLE_REFERENCE
555
+ #| '(' expr ')'
556
+ #| LITERAL
557
+ #| NUMBER
558
+ #| FunctionCall
559
+ VARIABLE_REFERENCE = /^\$(#{NAME_STR})/u
560
+ NUMBER = /^(\d*\.?\d+)/
561
+ NT = /^comment|text|processing-instruction|node$/
562
+ def PrimaryExpr path, parsed
563
+ case path
564
+ when VARIABLE_REFERENCE
565
+ varname = $1
566
+ path = $'
567
+ parsed << :variable
568
+ parsed << varname
569
+ #arry << @variables[ varname ]
570
+ when /^(\w[-\w]*)(?:\()/
571
+ fname = $1
572
+ tmp = $'
573
+ return path if fname =~ NT
574
+ path = tmp
575
+ parsed << :function
576
+ parsed << fname
577
+ path = FunctionCall(path, parsed)
578
+ when NUMBER
579
+ varname = $1.nil? ? $2 : $1
580
+ path = $'
581
+ parsed << :literal
582
+ parsed << (varname.include?('.') ? varname.to_f : varname.to_i)
583
+ when LITERAL
584
+ varname = $1.nil? ? $2 : $1
585
+ path = $'
586
+ parsed << :literal
587
+ parsed << varname
588
+ when /^\(/ #/
589
+ path, contents = get_group(path)
590
+ contents = contents[1..-2]
591
+ n = []
592
+ OrExpr( contents, n )
593
+ parsed.concat(n)
594
+ end
595
+ path
596
+ end
597
+
598
+ #| FUNCTION_NAME '(' ( expr ( ',' expr )* )? ')'
599
+ def FunctionCall rest, parsed
600
+ path, arguments = parse_args(rest)
601
+ argset = []
602
+ for argument in arguments
603
+ args = []
604
+ OrExpr( argument, args )
605
+ argset << args
606
+ end
607
+ parsed << argset
608
+ path
609
+ end
610
+
611
+ # get_group( '[foo]bar' ) -> ['bar', '[foo]']
612
+ def get_group string
613
+ ind = 0
614
+ depth = 0
615
+ st = string[0,1]
616
+ en = (st == "(" ? ")" : "]")
617
+ begin
618
+ case string[ind,1]
619
+ when st
620
+ depth += 1
621
+ when en
622
+ depth -= 1
623
+ end
624
+ ind += 1
625
+ end while depth > 0 and ind < string.length
626
+ return nil unless depth==0
627
+ [string[ind..-1], string[0..ind-1]]
628
+ end
629
+
630
+ def parse_args( string )
631
+ arguments = []
632
+ ind = 0
633
+ inquot = false
634
+ inapos = false
635
+ depth = 1
636
+ begin
637
+ case string[ind]
638
+ when ?"
639
+ inquot = !inquot unless inapos
640
+ when ?'
641
+ inapos = !inapos unless inquot
642
+ else
643
+ unless inquot or inapos
644
+ case string[ind]
645
+ when ?(
646
+ depth += 1
647
+ if depth == 1
648
+ string = string[1..-1]
649
+ ind -= 1
650
+ end
651
+ when ?)
652
+ depth -= 1
653
+ if depth == 0
654
+ s = string[0,ind].strip
655
+ arguments << s unless s == ""
656
+ string = string[ind+1..-1]
657
+ end
658
+ when ?,
659
+ if depth == 1
660
+ s = string[0,ind].strip
661
+ arguments << s unless s == ""
662
+ string = string[ind+1..-1]
663
+ ind = -1
664
+ end
665
+ end
666
+ end
667
+ end
668
+ ind += 1
669
+ end while depth > 0 and ind < string.length
670
+ return nil unless depth==0
671
+ [string,arguments]
672
+ end
673
+ end
674
+ end
675
+ end