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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rexml/attlistdecl.rb +63 -0
- data/lib/rexml/attribute.rb +192 -0
- data/lib/rexml/cdata.rb +68 -0
- data/lib/rexml/child.rb +97 -0
- data/lib/rexml/comment.rb +80 -0
- data/lib/rexml/doctype.rb +270 -0
- data/lib/rexml/document.rb +291 -0
- data/lib/rexml/dtd/attlistdecl.rb +11 -0
- data/lib/rexml/dtd/dtd.rb +47 -0
- data/lib/rexml/dtd/elementdecl.rb +18 -0
- data/lib/rexml/dtd/entitydecl.rb +57 -0
- data/lib/rexml/dtd/notationdecl.rb +40 -0
- data/lib/rexml/element.rb +1267 -0
- data/lib/rexml/encoding.rb +51 -0
- data/lib/rexml/entity.rb +171 -0
- data/lib/rexml/formatters/default.rb +112 -0
- data/lib/rexml/formatters/pretty.rb +142 -0
- data/lib/rexml/formatters/transitive.rb +58 -0
- data/lib/rexml/functions.rb +447 -0
- data/lib/rexml/instruction.rb +71 -0
- data/lib/rexml/light/node.rb +196 -0
- data/lib/rexml/namespace.rb +48 -0
- data/lib/rexml/node.rb +76 -0
- data/lib/rexml/output.rb +30 -0
- data/lib/rexml/parent.rb +166 -0
- data/lib/rexml/parseexception.rb +52 -0
- data/lib/rexml/parsers/baseparser.rb +586 -0
- data/lib/rexml/parsers/lightparser.rb +59 -0
- data/lib/rexml/parsers/pullparser.rb +197 -0
- data/lib/rexml/parsers/sax2parser.rb +273 -0
- data/lib/rexml/parsers/streamparser.rb +61 -0
- data/lib/rexml/parsers/treeparser.rb +101 -0
- data/lib/rexml/parsers/ultralightparser.rb +57 -0
- data/lib/rexml/parsers/xpathparser.rb +675 -0
- data/lib/rexml/quickpath.rb +266 -0
- data/lib/rexml/rexml.rb +32 -0
- data/lib/rexml/sax2listener.rb +98 -0
- data/lib/rexml/security.rb +28 -0
- data/lib/rexml/source.rb +298 -0
- data/lib/rexml/streamlistener.rb +93 -0
- data/lib/rexml/syncenumerator.rb +33 -0
- data/lib/rexml/text.rb +424 -0
- data/lib/rexml/undefinednamespaceexception.rb +9 -0
- data/lib/rexml/validation/relaxng.rb +539 -0
- data/lib/rexml/validation/validation.rb +144 -0
- data/lib/rexml/validation/validationexception.rb +10 -0
- data/lib/rexml/xmldecl.rb +116 -0
- data/lib/rexml/xmltokens.rb +85 -0
- data/lib/rexml/xpath.rb +81 -0
- data/lib/rexml/xpath_parser.rb +934 -0
- data/rexml.gemspec +42 -0
- 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
|