nokogiri 1.4.2 → 1.4.3

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

Potentially problematic release.


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

Files changed (54) hide show
  1. data/CHANGELOG.ja.rdoc +28 -8
  2. data/CHANGELOG.rdoc +24 -1
  3. data/Manifest.txt +2 -1
  4. data/README.ja.rdoc +1 -1
  5. data/README.rdoc +22 -4
  6. data/Rakefile +6 -2
  7. data/ext/nokogiri/extconf.rb +55 -32
  8. data/ext/nokogiri/nokogiri.h +2 -0
  9. data/ext/nokogiri/xml_document.c +5 -0
  10. data/ext/nokogiri/xml_libxml2_hacks.c +112 -0
  11. data/ext/nokogiri/xml_libxml2_hacks.h +12 -0
  12. data/ext/nokogiri/xml_node.c +58 -12
  13. data/ext/nokogiri/xml_node_set.c +7 -7
  14. data/ext/nokogiri/xml_reader.c +20 -1
  15. data/ext/nokogiri/xml_xpath_context.c +2 -0
  16. data/lib/nokogiri/css/generated_parser.rb +155 -148
  17. data/lib/nokogiri/css/generated_tokenizer.rb +2 -1
  18. data/lib/nokogiri/css/parser.y +3 -0
  19. data/lib/nokogiri/css/xpath_visitor.rb +1 -7
  20. data/lib/nokogiri/ffi/libxml.rb +29 -4
  21. data/lib/nokogiri/ffi/xml/document.rb +4 -0
  22. data/lib/nokogiri/ffi/xml/node.rb +27 -19
  23. data/lib/nokogiri/ffi/xml/node_set.rb +3 -3
  24. data/lib/nokogiri/ffi/xml/reader.rb +4 -0
  25. data/lib/nokogiri/html.rb +2 -2
  26. data/lib/nokogiri/html/document_fragment.rb +7 -4
  27. data/lib/nokogiri/version.rb +2 -1
  28. data/lib/nokogiri/xml/builder.rb +1 -1
  29. data/lib/nokogiri/xml/document.rb +1 -2
  30. data/lib/nokogiri/xml/document_fragment.rb +7 -0
  31. data/lib/nokogiri/xml/node.rb +4 -2
  32. data/lib/nokogiri/xml/node_set.rb +25 -0
  33. data/lib/nokogiri/xml/reader.rb +2 -0
  34. data/lib/nokogiri/xml/sax/document.rb +3 -1
  35. data/test/css/test_parser.rb +11 -1
  36. data/test/html/sax/test_parser_context.rb +2 -2
  37. data/test/html/test_document.rb +2 -2
  38. data/test/html/test_document_fragment.rb +34 -6
  39. data/test/test_memory_leak.rb +2 -2
  40. data/test/test_reader.rb +28 -6
  41. data/test/test_xslt_transforms.rb +29 -28
  42. data/test/xml/test_attr.rb +31 -4
  43. data/test/xml/test_builder.rb +5 -5
  44. data/test/xml/test_cdata.rb +3 -3
  45. data/test/xml/test_document.rb +8 -8
  46. data/test/xml/test_document_fragment.rb +2 -2
  47. data/test/xml/test_node.rb +1 -1
  48. data/test/xml/test_node_reparenting.rb +26 -11
  49. data/test/xml/test_node_set.rb +38 -2
  50. data/test/xml/test_text.rb +11 -2
  51. data/test/xml/test_unparented_node.rb +1 -1
  52. data/test/xml/test_xpath.rb +78 -11
  53. metadata +24 -5
  54. data/lib/nokogiri/version_warning.rb +0 -14
@@ -1,6 +1,6 @@
1
1
  #--
2
2
  # DO NOT MODIFY!!!!
3
- # This file is automatically generated by rex 1.0.4
3
+ # This file is automatically generated by rex 1.0.5.beta1
4
4
  # from lexical definition file "lib/nokogiri/css/tokenizer.rex".
5
5
  #++
6
6
 
@@ -29,6 +29,7 @@ class GeneratedTokenizer < GeneratedParser
29
29
  scan_setup(str)
30
30
  do_parse
31
31
  end
32
+ alias :scan :scan_str
32
33
 
33
34
  def load_file( filename )
34
35
  @filename = filename
@@ -39,6 +39,9 @@ rule
39
39
  result = Node.new(:CONDITIONAL_SELECTOR, val)
40
40
  }
41
41
  | function
42
+ | function pseudo {
43
+ result = Node.new(:CONDITIONAL_SELECTOR, val)
44
+ }
42
45
  | function attrib {
43
46
  result = Node.new(:CONDITIONAL_SELECTOR, val)
44
47
  }
@@ -48,13 +48,6 @@ module Nokogiri
48
48
  end
49
49
  end
50
50
 
51
- def visit_preceding_selector node
52
- node.value.last.accept(self) +
53
- '[preceding-sibling::' +
54
- node.value.first.accept(self) +
55
- ']'
56
- end
57
-
58
51
  def visit_id node
59
52
  node.value.first =~ /^#(.*)$/
60
53
  "@id = '#{$1}'"
@@ -126,6 +119,7 @@ module Nokogiri
126
119
  {
127
120
  'combinator' => ' and ',
128
121
  'direct_adjacent_selector' => "/following-sibling::*[1]/self::",
122
+ 'preceding_selector' => "/following-sibling::",
129
123
  'descendant_selector' => '//',
130
124
  'child_selector' => '/',
131
125
  }.each do |k,v|
@@ -113,10 +113,9 @@ module Nokogiri
113
113
  attach_function :xmlFreeDoc, [:pointer], :void
114
114
  attach_function :xmlSetTreeDoc, [:pointer, :pointer], :void
115
115
  attach_function :xmlNewReference, [:pointer, :string], :pointer
116
- attach_function :xmlFirstElementChild, [:pointer], :pointer
117
- attach_function :xmlLastElementChild, [:pointer], :pointer
118
- attach_function :xmlNextElementSibling, [:pointer], :pointer
119
- attach_function :xmlPreviousElementSibling, [:pointer], :pointer
116
+ # attach_function :xmlFirstElementChild, [:pointer], :pointer
117
+ # attach_function :xmlLastElementChild, [:pointer], :pointer
118
+ # attach_function :xmlNextElementSibling, [:pointer], :pointer
120
119
  attach_function :xmlNewNode, [:pointer, :string], :pointer
121
120
  attach_function :xmlCopyNode, [:pointer, :int], :pointer
122
121
  attach_function :xmlDocCopyNode, [:pointer, :pointer, :int], :pointer
@@ -157,6 +156,7 @@ module Nokogiri
157
156
  attach_function :xmlNewDtd, [:pointer] * 4, :pointer
158
157
  attach_function :xmlGetNsList, [:pointer, :pointer], :pointer
159
158
  attach_function :xmlTextMerge, [:pointer, :pointer], :pointer
159
+ attach_function :xmlFreeNsList, [:pointer], :void
160
160
 
161
161
  # valid.c
162
162
  attach_function :xmlNewValidCtxt, [], :pointer
@@ -264,6 +264,7 @@ module Nokogiri
264
264
  attach_function :xmlFreeTextReader, [:pointer], :void
265
265
  attach_function :xmlReaderForIO, [:io_read_callback, :io_close_callback, :pointer, :string, :string, :int], :pointer
266
266
  attach_function :xmlTextReaderNodeType, [:pointer], :int
267
+ attach_function :xmlTextReaderIsEmptyElement, [:pointer], :int
267
268
 
268
269
  # xslt.c
269
270
  attach_function :xsltParseStylesheetDoc, [:pointer], :pointer
@@ -311,6 +312,30 @@ module Nokogiri
311
312
  def self.pointer_offset(n)
312
313
  n * POINTER_SIZE # byte offset of nth pointer in an array of pointers
313
314
  end
315
+
316
+ # ZOMG hacks. see GH#303
317
+ class << self
318
+ def xmlFirstElementChildHack(parent)
319
+ return nil if parent.nil?
320
+ return nil unless [Nokogiri::XML::Node::ELEMENT_NODE, Nokogiri::XML::Node::ENTITY_NODE, Nokogiri::XML::Node::DOCUMENT_NODE, Nokogiri::XML::Node::HTML_DOCUMENT_NODE].include?(parent.type)
321
+ parent.children.find { |child| child.element? }
322
+ end
323
+
324
+ def xmlLastElementChildHack(parent)
325
+ return nil if parent.nil?
326
+ return nil unless [Nokogiri::XML::Node::ELEMENT_NODE, Nokogiri::XML::Node::ENTITY_NODE, Nokogiri::XML::Node::DOCUMENT_NODE, Nokogiri::XML::Node::HTML_DOCUMENT_NODE].include?(parent.type)
327
+ parent.children.reverse.find { |child| child.element? }
328
+ end
329
+
330
+ def xmlNextElementSiblingHack(sibling)
331
+ return nil if sibling.nil?
332
+ return nil unless [Nokogiri::XML::Node::ELEMENT_NODE, Nokogiri::XML::Node::ENTITY_NODE, Nokogiri::XML::Node::DOCUMENT_NODE, Nokogiri::XML::Node::HTML_DOCUMENT_NODE].include?(sibling.type)
333
+ while (sibling = sibling.next_sibling)
334
+ return sibling if sibling.element?
335
+ end
336
+ nil
337
+ end
338
+ end
314
339
  end
315
340
  end
316
341
 
@@ -153,6 +153,10 @@ module Nokogiri
153
153
  node.children.each do |child|
154
154
  recursively_remove_namespaces_from_node(child)
155
155
  end
156
+ unless node.cstruct[:nsDef].nil?
157
+ LibXML.xmlFreeNsList(node.cstruct[:nsDef])
158
+ node.cstruct[:nsDef] = nil
159
+ end
156
160
  end
157
161
  end
158
162
 
@@ -78,8 +78,7 @@ module Nokogiri
78
78
  end
79
79
 
80
80
  def next_element
81
- sibling_ptr = LibXML.xmlNextElementSibling cstruct
82
- sibling_ptr.null? ? nil : Node.wrap(sibling_ptr)
81
+ LibXML.xmlNextElementSiblingHack self
83
82
  end
84
83
 
85
84
  def previous_element
@@ -103,10 +102,10 @@ module Nokogiri
103
102
  retval = reparentee_struct if retval == pivot_struct.pointer # for reparent_node_with semantics
104
103
  retval = LibXML::XmlNode.new(retval) if retval.is_a?(FFI::Pointer)
105
104
  if retval[:type] == TEXT_NODE
106
- if retval[:prev] && LibXML::XmlNode.new(retval[:prev])[:type] == TEXT_NODE
105
+ if !retval[:prev].null? && LibXML::XmlNode.new(retval[:prev])[:type] == TEXT_NODE
107
106
  retval = LibXML::XmlNode.new(LibXML.xmlTextMerge(retval[:prev], retval))
108
107
  end
109
- if retval[:next] && LibXML::XmlNode.new(retval[:next])[:type] == TEXT_NODE
108
+ if !retval[:next].null? && LibXML::XmlNode.new(retval[:next])[:type] == TEXT_NODE
110
109
  retval = LibXML::XmlNode.new(LibXML.xmlTextMerge(retval, retval[:next]))
111
110
  end
112
111
  end
@@ -132,18 +131,17 @@ module Nokogiri
132
131
  end
133
132
 
134
133
  def element_children
135
- child = LibXML.xmlFirstElementChild(cstruct)
136
- return NodeSet.new(nil) if child.null?
137
- child = Node.wrap(child)
134
+ child = LibXML.xmlFirstElementChildHack(self)
135
+ return NodeSet.new(nil) if child.nil?
138
136
 
139
137
  set = NodeSet.wrap(LibXML.xmlXPathNodeSetCreate(child.cstruct), self.document)
140
138
  return set unless child
141
139
 
142
- next_sibling = LibXML.xmlNextElementSibling(child.cstruct)
143
- while ! next_sibling.null?
144
- child = Node.wrap(next_sibling)
140
+ next_sibling = LibXML.xmlNextElementSiblingHack(child)
141
+ while ! next_sibling.nil?
142
+ child = next_sibling
145
143
  LibXML.xmlXPathNodeSetAddUnique(set.cstruct, child.cstruct)
146
- next_sibling = LibXML.xmlNextElementSibling(child.cstruct)
144
+ next_sibling = LibXML.xmlNextElementSiblingHack(child)
147
145
  end
148
146
 
149
147
  return set
@@ -154,13 +152,11 @@ module Nokogiri
154
152
  end
155
153
 
156
154
  def first_element_child
157
- element_child = LibXML.xmlFirstElementChild(cstruct)
158
- element_child.null? ? nil : Node.wrap(element_child)
155
+ LibXML.xmlFirstElementChildHack(self)
159
156
  end
160
157
 
161
158
  def last_element_child
162
- element_child = LibXML.xmlLastElementChild(cstruct)
163
- element_child.null? ? nil : Node.wrap(element_child)
159
+ LibXML.xmlLastElementChildHack(self)
164
160
  end
165
161
 
166
162
  def key?(attribute)
@@ -313,12 +309,14 @@ module Nokogiri
313
309
  end
314
310
 
315
311
  def add_namespace_definition(prefix, href)
316
- ns = LibXML.xmlNewNs(cstruct, href, prefix)
312
+ ns = LibXML.xmlSearchNs(cstruct.document, cstruct, prefix.nil? ? nil : prefix.to_s)
313
+ namespacee = self
317
314
  if ns.null?
318
- ns = LibXML.xmlSearchNs(cstruct.document, cstruct,
319
- prefix.nil? ? nil : prefix.to_s)
315
+ namespacee = parent if type != ELEMENT_NODE
316
+ ns = LibXML.xmlNewNs(namespacee.cstruct, href, prefix)
320
317
  end
321
- LibXML.xmlSetNs(cstruct, ns) if prefix.nil?
318
+ return nil if ns.null?
319
+ LibXML.xmlSetNs(cstruct, ns) if (prefix.nil? || self != namespacee)
322
320
  Namespace.wrap(cstruct.document, ns)
323
321
  end
324
322
 
@@ -460,6 +458,16 @@ module Nokogiri
460
458
  reparentee_struct = LibXML::XmlNode.new(reparentee_struct)
461
459
  end
462
460
 
461
+ if reparentee_struct[:type] == TEXT_NODE && !pivot_struct[:next].null?
462
+ next_text = Node.wrap(pivot_struct[:next])
463
+ if next_text.cstruct[:type] == TEXT_NODE
464
+ new_next_text = LibXML.xmlDocCopyNode(next_text.cstruct, pivot_struct[:doc], 1)
465
+ LibXML.xmlUnlinkNode(next_text.cstruct)
466
+ next_text.cstruct.keep_reference_from_document!
467
+ LibXML.xmlAddNextSibling(pivot_struct, new_next_text);
468
+ end
469
+ end
470
+
463
471
  if reparentee_struct[:type] == TEXT_NODE && pivot_struct[:type] == TEXT_NODE && Nokogiri.is_2_6_16?
464
472
  pivot_struct.pointer.put_pointer(pivot_struct.offset_of(:content), LibXML.xmlStrdup(pivot_struct[:content]))
465
473
  end
@@ -14,7 +14,7 @@ module Nokogiri
14
14
  end
15
15
 
16
16
  def push(node) # :nodoc:
17
- raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node)
17
+ raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node) || node.is_a?(XML::Namespace)
18
18
  LibXML.xmlXPathNodeSetAdd(cstruct, node.cstruct)
19
19
  self
20
20
  end
@@ -39,7 +39,7 @@ module Nokogiri
39
39
  end
40
40
 
41
41
  def delete(node) # :nodoc:
42
- raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node)
42
+ raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node) || node.is_a?(XML::Namespace)
43
43
  if LibXML.xmlXPathNodeSetContains(cstruct, node.cstruct) != 0
44
44
  LibXML.xmlXPathNodeSetDel(cstruct, node.cstruct)
45
45
  return node
@@ -71,7 +71,7 @@ module Nokogiri
71
71
  end
72
72
 
73
73
  def include?(node) # :nodoc:
74
- raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node)
74
+ raise(ArgumentError, "node must be a Nokogiri::XML::Node") unless node.is_a?(XML::Node) || node.is_a?(XML::Namespace)
75
75
  (LibXML.xmlXPathNodeSetContains(cstruct, node.cstruct) != 0) ? true : false
76
76
  end
77
77
 
@@ -176,6 +176,10 @@ module Nokogiri
176
176
  LibXML.xmlTextReaderNodeType(cstruct)
177
177
  end
178
178
 
179
+ def empty_element?
180
+ LibXML.xmlTextReaderIsEmptyElement(cstruct) != 0
181
+ end
182
+
179
183
  def self.from_memory(buffer, url=nil, encoding=nil, options=0)
180
184
  raise(ArgumentError, "string cannot be nil") if buffer.nil?
181
185
 
@@ -24,8 +24,8 @@ module Nokogiri
24
24
 
25
25
  ####
26
26
  # Parse a fragment from +string+ in to a NodeSet.
27
- def fragment string
28
- HTML::DocumentFragment.parse(string)
27
+ def fragment string, encoding = nil
28
+ HTML::DocumentFragment.parse string, encoding
29
29
  end
30
30
  end
31
31
 
@@ -2,11 +2,14 @@ module Nokogiri
2
2
  module HTML
3
3
  class DocumentFragment < Nokogiri::XML::DocumentFragment
4
4
  ####
5
- # Create a Nokogiri::XML::DocumentFragment from +tags+
6
- def self.parse tags
5
+ # Create a Nokogiri::XML::DocumentFragment from +tags+, using +encoding+
6
+ def self.parse tags, encoding = nil
7
7
  doc = HTML::Document.new
8
- doc.encoding = 'UTF-8'
9
- self.new(doc, tags)
8
+
9
+ encoding ||= tags.respond_to?(:encoding) ? tags.encoding.name : 'UTF-8'
10
+ doc.encoding = encoding
11
+
12
+ new(doc, tags)
10
13
  end
11
14
 
12
15
  def initialize document, tags = nil, ctx = nil
@@ -1,6 +1,6 @@
1
1
  module Nokogiri
2
2
  # The version of Nokogiri you are using
3
- VERSION = '1.4.2'
3
+ VERSION = '1.4.3'
4
4
 
5
5
  # More complete version information about libxml
6
6
  VERSION_INFO = {}
@@ -9,6 +9,7 @@ module Nokogiri
9
9
  VERSION_INFO['ruby'] = {}
10
10
  VERSION_INFO['ruby']['version'] = ::RUBY_VERSION
11
11
  VERSION_INFO['ruby']['platform'] = ::RUBY_PLATFORM
12
+ VERSION_INFO['ruby']['engine'] = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'mri'
12
13
  VERSION_INFO['ruby']['jruby'] = ::JRUBY_VERSION if RUBY_PLATFORM == "java"
13
14
  if defined?(LIBXML_VERSION)
14
15
  VERSION_INFO['libxml'] = {}
@@ -251,7 +251,7 @@ module Nokogiri
251
251
  # end
252
252
  #
253
253
  def self.with root, &block
254
- builder = self.new({}, root, &block)
254
+ new({}, root, &block)
255
255
  end
256
256
 
257
257
  ###
@@ -7,7 +7,7 @@ module Nokogiri
7
7
  #
8
8
  # For searching a Document, see Nokogiri::XML::Node#css and
9
9
  # Nokogiri::XML::Node#xpath
10
- class Document < Node
10
+ class Document < Nokogiri::XML::Node
11
11
  ##
12
12
  # Parse an XML file. +thing+ may be a String, or any object that
13
13
  # responds to _read_ and _close_ such as an IO, or StringIO.
@@ -172,7 +172,6 @@ module Nokogiri
172
172
  undef_method :swap, :parent, :namespace, :default_namespace=
173
173
  undef_method :add_namespace_definition, :attributes
174
174
  undef_method :namespace_definitions, :line, :add_namespace
175
- undef_method :parse, :in_context
176
175
 
177
176
  def add_child child
178
177
  raise "Document already has a root node" if root
@@ -72,6 +72,13 @@ module Nokogiri
72
72
  end
73
73
  end
74
74
 
75
+ private
76
+
77
+ def coerce data
78
+ return super unless String === data
79
+
80
+ document.fragment(data).children
81
+ end
75
82
  end
76
83
  end
77
84
  end
@@ -375,8 +375,10 @@ module Nokogiri
375
375
 
376
376
  ####
377
377
  # Returns a hash containing the node's attributes. The key is
378
- # the attribute name, the value is a Nokogiri::XML::Attr
378
+ # the attribute name without any namespace, the value is a Nokogiri::XML::Attr
379
379
  # representing the attribute.
380
+ # If you need to distinguish attributes with the same name, with different namespaces
381
+ # use #attribute_nodes instead.
380
382
  def attributes
381
383
  Hash[*(attribute_nodes.map { |node|
382
384
  [node.node_name, node]
@@ -770,7 +772,7 @@ module Nokogiri
770
772
 
771
773
  private
772
774
 
773
- def coerce(data) # :nodoc:
775
+ def coerce data # :nodoc:
774
776
  return data if data.is_a?(XML::NodeSet)
775
777
  return data.children if data.is_a?(XML::DocumentFragment)
776
778
  return fragment(data).children if data.is_a?(String)
@@ -138,6 +138,13 @@ module Nokogiri
138
138
  sub_set
139
139
  end
140
140
 
141
+ ###
142
+ # Search this NodeSet's nodes' immediate children using CSS selector +selector+
143
+ def > selector
144
+ ns = document.root.namespaces
145
+ xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
146
+ end
147
+
141
148
  ###
142
149
  # If path is a string, search this document for +path+ returning the
143
150
  # first Node. Otherwise, index in to the array with +path+.
@@ -147,6 +154,24 @@ module Nokogiri
147
154
  end
148
155
  alias :% :at
149
156
 
157
+ ##
158
+ # Search this NodeSet for the first occurrence of XPath +paths+.
159
+ # Equivalent to <tt>xpath(paths).first</tt>
160
+ # See NodeSet#xpath for more information.
161
+ #
162
+ def at_xpath *paths
163
+ xpath(*paths).first
164
+ end
165
+
166
+ ##
167
+ # Search this NodeSet for the first occurrence of CSS +rules+.
168
+ # Equivalent to <tt>css(rules).first</tt>
169
+ # See NodeSet#css for more information.
170
+ #
171
+ def at_css *rules
172
+ css(*rules).first
173
+ end
174
+
150
175
  ###
151
176
  # Filter this list for nodes that match +expr+
152
177
  def filter expr
@@ -39,6 +39,8 @@ module Nokogiri
39
39
  # The XML source
40
40
  attr_reader :source
41
41
 
42
+ alias :self_closing? :empty_element?
43
+
42
44
  def initialize source, url = nil, encoding = nil # :nodoc:
43
45
  @source = source
44
46
  @errors = []
@@ -126,7 +126,9 @@ module Nokogiri
126
126
  end
127
127
 
128
128
  ###
129
- # Characters read between a tag
129
+ # Characters read between a tag. This method might be called multiple
130
+ # times given one contiguous string of characters.
131
+ #
130
132
  # +string+ contains the character data
131
133
  def characters string
132
134
  end
@@ -16,6 +16,10 @@ module Nokogiri
16
16
  assert_raises(CSS::SyntaxError) { @parser.parse("a[x=]") }
17
17
  end
18
18
 
19
+ def test_function_and_pseudo
20
+ assert_xpath '//child::text()[position() = 99]', @parser.parse('text():nth-of-type(99)')
21
+ end
22
+
19
23
  def test_find_by_type
20
24
  ast = @parser.parse("a:nth-child(2)").first
21
25
  matches = ast.find_by_type(
@@ -184,13 +188,19 @@ module Nokogiri
184
188
  end
185
189
 
186
190
  def test_preceding_selector
187
- assert_xpath "//F[preceding-sibling::E]",
191
+ assert_xpath "//E/following-sibling::F",
188
192
  @parser.parse("E ~ F")
193
+
194
+ assert_xpath "//E/following-sibling::F//G",
195
+ @parser.parse("E ~ F G")
189
196
  end
190
197
 
191
198
  def test_direct_preceding_selector
192
199
  assert_xpath "//E/following-sibling::*[1]/self::F",
193
200
  @parser.parse("E + F")
201
+
202
+ assert_xpath "//E/following-sibling::*[1]/self::F//G",
203
+ @parser.parse("E + F G")
194
204
  end
195
205
 
196
206
  def test_attribute