nokogiri 1.10.10 → 1.11.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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/LICENSE-DEPENDENCIES.md +1015 -947
  4. data/LICENSE.md +1 -1
  5. data/README.md +173 -94
  6. data/ext/nokogiri/depend +37 -358
  7. data/ext/nokogiri/extconf.rb +611 -391
  8. data/ext/nokogiri/html_document.c +78 -82
  9. data/ext/nokogiri/html_element_description.c +84 -71
  10. data/ext/nokogiri/html_entity_lookup.c +21 -16
  11. data/ext/nokogiri/html_sax_parser_context.c +69 -66
  12. data/ext/nokogiri/html_sax_push_parser.c +42 -34
  13. data/ext/nokogiri/libxml2_backwards_compat.c +121 -0
  14. data/ext/nokogiri/nokogiri.c +192 -87
  15. data/ext/nokogiri/nokogiri.h +181 -89
  16. data/ext/nokogiri/test_global_handlers.c +40 -0
  17. data/ext/nokogiri/xml_attr.c +15 -15
  18. data/ext/nokogiri/xml_attribute_decl.c +18 -18
  19. data/ext/nokogiri/xml_cdata.c +13 -18
  20. data/ext/nokogiri/xml_comment.c +19 -26
  21. data/ext/nokogiri/xml_document.c +255 -183
  22. data/ext/nokogiri/xml_document_fragment.c +13 -15
  23. data/ext/nokogiri/xml_dtd.c +54 -48
  24. data/ext/nokogiri/xml_element_content.c +30 -27
  25. data/ext/nokogiri/xml_element_decl.c +22 -22
  26. data/ext/nokogiri/xml_encoding_handler.c +17 -11
  27. data/ext/nokogiri/xml_entity_decl.c +32 -30
  28. data/ext/nokogiri/xml_entity_reference.c +16 -18
  29. data/ext/nokogiri/xml_namespace.c +56 -49
  30. data/ext/nokogiri/xml_node.c +387 -316
  31. data/ext/nokogiri/xml_node_set.c +168 -156
  32. data/ext/nokogiri/xml_processing_instruction.c +17 -19
  33. data/ext/nokogiri/xml_reader.c +195 -172
  34. data/ext/nokogiri/xml_relax_ng.c +52 -28
  35. data/ext/nokogiri/xml_sax_parser.c +118 -118
  36. data/ext/nokogiri/xml_sax_parser_context.c +103 -86
  37. data/ext/nokogiri/xml_sax_push_parser.c +36 -27
  38. data/ext/nokogiri/xml_schema.c +95 -47
  39. data/ext/nokogiri/xml_syntax_error.c +42 -21
  40. data/ext/nokogiri/xml_text.c +13 -17
  41. data/ext/nokogiri/xml_xpath_context.c +206 -123
  42. data/ext/nokogiri/xslt_stylesheet.c +158 -165
  43. data/lib/nokogiri.rb +6 -27
  44. data/lib/nokogiri/css.rb +1 -0
  45. data/lib/nokogiri/css/node.rb +1 -0
  46. data/lib/nokogiri/css/parser.rb +63 -62
  47. data/lib/nokogiri/css/parser.y +2 -2
  48. data/lib/nokogiri/css/parser_extras.rb +39 -36
  49. data/lib/nokogiri/css/syntax_error.rb +1 -0
  50. data/lib/nokogiri/css/tokenizer.rb +1 -0
  51. data/lib/nokogiri/css/xpath_visitor.rb +73 -43
  52. data/lib/nokogiri/decorators/slop.rb +1 -0
  53. data/lib/nokogiri/extension.rb +26 -0
  54. data/lib/nokogiri/html.rb +1 -0
  55. data/lib/nokogiri/html/builder.rb +1 -0
  56. data/lib/nokogiri/html/document.rb +13 -26
  57. data/lib/nokogiri/html/document_fragment.rb +16 -15
  58. data/lib/nokogiri/html/element_description.rb +1 -0
  59. data/lib/nokogiri/html/element_description_defaults.rb +1 -0
  60. data/lib/nokogiri/html/entity_lookup.rb +1 -0
  61. data/lib/nokogiri/html/sax/parser.rb +1 -0
  62. data/lib/nokogiri/html/sax/parser_context.rb +1 -0
  63. data/lib/nokogiri/html/sax/push_parser.rb +1 -0
  64. data/lib/nokogiri/jruby/dependencies.rb +20 -0
  65. data/lib/nokogiri/syntax_error.rb +1 -0
  66. data/lib/nokogiri/version.rb +3 -109
  67. data/lib/nokogiri/version/constant.rb +5 -0
  68. data/lib/nokogiri/version/info.rb +205 -0
  69. data/lib/nokogiri/xml.rb +1 -0
  70. data/lib/nokogiri/xml/attr.rb +1 -0
  71. data/lib/nokogiri/xml/attribute_decl.rb +1 -0
  72. data/lib/nokogiri/xml/builder.rb +3 -2
  73. data/lib/nokogiri/xml/cdata.rb +1 -0
  74. data/lib/nokogiri/xml/character_data.rb +1 -0
  75. data/lib/nokogiri/xml/document.rb +92 -41
  76. data/lib/nokogiri/xml/document_fragment.rb +5 -6
  77. data/lib/nokogiri/xml/dtd.rb +1 -0
  78. data/lib/nokogiri/xml/element_content.rb +1 -0
  79. data/lib/nokogiri/xml/element_decl.rb +1 -0
  80. data/lib/nokogiri/xml/entity_decl.rb +1 -0
  81. data/lib/nokogiri/xml/entity_reference.rb +1 -0
  82. data/lib/nokogiri/xml/namespace.rb +1 -0
  83. data/lib/nokogiri/xml/node.rb +625 -290
  84. data/lib/nokogiri/xml/node/save_options.rb +1 -0
  85. data/lib/nokogiri/xml/node_set.rb +1 -0
  86. data/lib/nokogiri/xml/notation.rb +1 -0
  87. data/lib/nokogiri/xml/parse_options.rb +10 -3
  88. data/lib/nokogiri/xml/pp.rb +1 -0
  89. data/lib/nokogiri/xml/pp/character_data.rb +1 -0
  90. data/lib/nokogiri/xml/pp/node.rb +1 -0
  91. data/lib/nokogiri/xml/processing_instruction.rb +1 -0
  92. data/lib/nokogiri/xml/reader.rb +9 -12
  93. data/lib/nokogiri/xml/relax_ng.rb +7 -2
  94. data/lib/nokogiri/xml/sax.rb +1 -0
  95. data/lib/nokogiri/xml/sax/document.rb +1 -0
  96. data/lib/nokogiri/xml/sax/parser.rb +1 -0
  97. data/lib/nokogiri/xml/sax/parser_context.rb +1 -0
  98. data/lib/nokogiri/xml/sax/push_parser.rb +1 -0
  99. data/lib/nokogiri/xml/schema.rb +13 -4
  100. data/lib/nokogiri/xml/searchable.rb +25 -16
  101. data/lib/nokogiri/xml/syntax_error.rb +1 -0
  102. data/lib/nokogiri/xml/text.rb +1 -0
  103. data/lib/nokogiri/xml/xpath.rb +2 -3
  104. data/lib/nokogiri/xml/xpath/syntax_error.rb +2 -1
  105. data/lib/nokogiri/xml/xpath_context.rb +1 -0
  106. data/lib/nokogiri/xslt.rb +1 -0
  107. data/lib/nokogiri/xslt/stylesheet.rb +1 -0
  108. data/lib/xsd/xmlparser/nokogiri.rb +1 -0
  109. data/patches/libxml2/0006-htmlParseComment-treat-as-if-it-closed-the-comment.patch +73 -0
  110. data/patches/libxml2/0007-use-new-htmlParseLookupCommentEnd-to-find-comment-en.patch +103 -0
  111. data/patches/libxml2/0008-use-glibc-strlen.patch +53 -0
  112. data/patches/libxml2/0009-avoid-isnan-isinf.patch +81 -0
  113. data/patches/libxml2/0010-parser.c-shrink-the-input-buffer-when-appropriate.patch +70 -0
  114. data/patches/libxml2/0011-update-automake-files-for-arm64.patch +2511 -0
  115. data/patches/libxslt/0001-update-automake-files-for-arm64.patch +2511 -0
  116. metadata +88 -149
  117. data/ext/nokogiri/html_document.h +0 -10
  118. data/ext/nokogiri/html_element_description.h +0 -10
  119. data/ext/nokogiri/html_entity_lookup.h +0 -8
  120. data/ext/nokogiri/html_sax_parser_context.h +0 -11
  121. data/ext/nokogiri/html_sax_push_parser.h +0 -9
  122. data/ext/nokogiri/xml_attr.h +0 -9
  123. data/ext/nokogiri/xml_attribute_decl.h +0 -9
  124. data/ext/nokogiri/xml_cdata.h +0 -9
  125. data/ext/nokogiri/xml_comment.h +0 -9
  126. data/ext/nokogiri/xml_document.h +0 -23
  127. data/ext/nokogiri/xml_document_fragment.h +0 -10
  128. data/ext/nokogiri/xml_dtd.h +0 -10
  129. data/ext/nokogiri/xml_element_content.h +0 -10
  130. data/ext/nokogiri/xml_element_decl.h +0 -9
  131. data/ext/nokogiri/xml_encoding_handler.h +0 -8
  132. data/ext/nokogiri/xml_entity_decl.h +0 -10
  133. data/ext/nokogiri/xml_entity_reference.h +0 -9
  134. data/ext/nokogiri/xml_io.c +0 -61
  135. data/ext/nokogiri/xml_io.h +0 -11
  136. data/ext/nokogiri/xml_libxml2_hacks.c +0 -112
  137. data/ext/nokogiri/xml_libxml2_hacks.h +0 -12
  138. data/ext/nokogiri/xml_namespace.h +0 -14
  139. data/ext/nokogiri/xml_node.h +0 -13
  140. data/ext/nokogiri/xml_node_set.h +0 -12
  141. data/ext/nokogiri/xml_processing_instruction.h +0 -9
  142. data/ext/nokogiri/xml_reader.h +0 -10
  143. data/ext/nokogiri/xml_relax_ng.h +0 -9
  144. data/ext/nokogiri/xml_sax_parser.h +0 -39
  145. data/ext/nokogiri/xml_sax_parser_context.h +0 -10
  146. data/ext/nokogiri/xml_sax_push_parser.h +0 -9
  147. data/ext/nokogiri/xml_schema.h +0 -9
  148. data/ext/nokogiri/xml_syntax_error.h +0 -13
  149. data/ext/nokogiri/xml_text.h +0 -9
  150. data/ext/nokogiri/xml_xpath_context.h +0 -10
  151. data/ext/nokogiri/xslt_stylesheet.h +0 -14
data/lib/nokogiri/xml.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri/xml/pp'
2
3
  require 'nokogiri/xml/parse_options'
3
4
  require 'nokogiri/xml/sax'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class Attr < Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -244,8 +245,8 @@ module Nokogiri
244
245
  #
245
246
  # For example:
246
247
  #
247
- # doc = Nokogiri::XML(open('somedoc.xml'))
248
- # Nokogiri::XML::Builder.with(doc.at('some_tag')) do |xml|
248
+ # doc = Nokogiri::XML(File.read('somedoc.xml'))
249
+ # Nokogiri::XML::Builder.with(doc.at_css('some_tag')) do |xml|
249
250
  # # ... Use normal builder methods here ...
250
251
  # xml.awesome # add the "awesome" tag below "some_tag"
251
252
  # end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class CDATA < Nokogiri::XML::Text
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class CharacterData < Nokogiri::XML::Node
@@ -1,3 +1,8 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'pathname'
5
+
1
6
  module Nokogiri
2
7
  module XML
3
8
  ##
@@ -9,11 +14,12 @@ module Nokogiri
9
14
  # Nokogiri::XML::Searchable#xpath
10
15
  #
11
16
  class Document < Nokogiri::XML::Node
12
- # I'm ignoring unicode characters here.
13
- # See http://www.w3.org/TR/REC-xml-names/#ns-decl for more details.
17
+ # See http://www.w3.org/TR/REC-xml-names/#ns-decl for more details. Note that we're not
18
+ # attempting to handle unicode characters partly because libxml2 doesn't handle unicode
19
+ # characters in NCNAMEs.
14
20
  NCNAME_START_CHAR = "A-Za-z_"
15
- NCNAME_CHAR = NCNAME_START_CHAR + "\\-.0-9"
16
- NCNAME_RE = /^xmlns(:[#{NCNAME_START_CHAR}][#{NCNAME_CHAR}]*)?$/
21
+ NCNAME_CHAR = NCNAME_START_CHAR + "\\-\\.0-9"
22
+ NCNAME_RE = /^xmlns(?::([#{NCNAME_START_CHAR}][#{NCNAME_CHAR}]*))?$/
17
23
 
18
24
  ##
19
25
  # Parse an XML file.
@@ -43,9 +49,11 @@ module Nokogiri
43
49
  #
44
50
  def self.parse string_or_io, url = nil, encoding = nil, options = ParseOptions::DEFAULT_XML
45
51
  options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
46
- # Give the options to the user
52
+
47
53
  yield options if block_given?
48
54
 
55
+ url ||= string_or_io.respond_to?(:path) ? string_or_io.path : nil
56
+
49
57
  if empty_doc?(string_or_io)
50
58
  if options.strict?
51
59
  raise Nokogiri::XML::SyntaxError.new("Empty document")
@@ -55,12 +63,17 @@ module Nokogiri
55
63
  end
56
64
 
57
65
  doc = if string_or_io.respond_to?(:read)
58
- url ||= string_or_io.respond_to?(:path) ? string_or_io.path : nil
59
- read_io(string_or_io, url, encoding, options.to_i)
60
- else
61
- # read_memory pukes on empty docs
62
- read_memory(string_or_io, url, encoding, options.to_i)
63
- end
66
+ if string_or_io.is_a?(Pathname)
67
+ # resolve the Pathname to the file and open it as an IO object, see #2110
68
+ string_or_io = string_or_io.expand_path.open
69
+ url ||= string_or_io.path
70
+ end
71
+
72
+ read_io(string_or_io, url, encoding, options.to_i)
73
+ else
74
+ # read_memory pukes on empty docs
75
+ read_memory(string_or_io, url, encoding, options.to_i)
76
+ end
64
77
 
65
78
  # do xinclude processing
66
79
  doc.do_xinclude(options) if options.xinclude?
@@ -68,6 +81,35 @@ module Nokogiri
68
81
  return doc
69
82
  end
70
83
 
84
+ ##
85
+ # @!method wrap(java_document)
86
+ # @!scope class
87
+ #
88
+ # Create a {Document} using an existing Java DOM document object.
89
+ #
90
+ # The returned {Document} shares the same underlying data structure as the Java object, so
91
+ # changes in one are reflected in the other.
92
+ #
93
+ # @param java_document [Java::OrgW3cDom::Document]
94
+ # @return [Nokogiri::XML::Document]
95
+ # @note This method is only available when running JRuby.
96
+ # @note The class +Java::OrgW3cDom::Document+ is also accessible as +org.w3c.dom.Document+.
97
+ # @see #to_java
98
+
99
+ ##
100
+ # @!method to_java()
101
+ #
102
+ # Returns the underlying Java DOM document object for the {Document}.
103
+ #
104
+ # The returned Java object shares the same underlying data structure as the {Document}, so
105
+ # changes in one are reflected in the other.
106
+ #
107
+ # @return [Java::OrgW3cDom::Document]
108
+ # @note This method is only available when running JRuby.
109
+ # @note The class +Java::OrgW3cDom::Document+ is also accessible as +org.w3c.dom.Document+.
110
+ # @see .wrap
111
+
112
+
71
113
  # A list of Nokogiri::XML::SyntaxError found when parsing a document
72
114
  attr_accessor :errors
73
115
 
@@ -77,33 +119,58 @@ module Nokogiri
77
119
  end
78
120
 
79
121
  ##
80
- # Create an element with +name+, and optionally setting the content and attributes.
122
+ # Create a new +Element+ with +name+ sharing GC lifecycle with the document, optionally
123
+ # setting contents or attributes.
124
+ #
125
+ # Arguments may be passed to initialize the element:
126
+ # - a +Hash+ argument will be used to set attributes
127
+ # - a non-Hash object that responds to +#to_s+ will be used to set the new node's contents
128
+ #
129
+ # A block may be passed to mutate the node.
130
+ #
131
+ # @param name [String]
132
+ # @param contents_or_attrs [#to_s,Hash]
133
+ # @yieldparam node [Nokogiri::XML::Element]
134
+ # @return [Nokogiri::XML::Element]
135
+ #
136
+ # @example An empty element without attributes
137
+ # doc.create_element("div")
138
+ # # => <div></div>
139
+ #
140
+ # @example An element with contents
141
+ # doc.create_element("div", "contents")
142
+ # # => <div>contents</div>
143
+ #
144
+ # @example An element with attributes
145
+ # doc.create_element("div", {"class" => "container"})
146
+ # # => <div class='container'></div>
81
147
  #
82
- # doc.create_element "div" # <div></div>
83
- # doc.create_element "div", :class => "container" # <div class='container'></div>
84
- # doc.create_element "div", "contents" # <div>contents</div>
85
- # doc.create_element "div", "contents", :class => "container" # <div class='container'>contents</div>
86
- # doc.create_element "div" { |node| node['class'] = "container" } # <div class='container'></div>
148
+ # @example An element with contents and attributes
149
+ # doc.create_element("div", "contents", {"class" => "container"})
150
+ # # => <div class='container'>contents</div>
87
151
  #
88
- def create_element name, *args, &block
152
+ # @example Passing a block to mutate the element
153
+ # doc.create_element("div") { |node| node["class"] = "blue" if before_noon? }
154
+ #
155
+ def create_element(name, *contents_or_attrs, &block)
89
156
  elm = Nokogiri::XML::Element.new(name, self, &block)
90
- args.each do |arg|
157
+ contents_or_attrs.each do |arg|
91
158
  case arg
92
159
  when Hash
93
- arg.each { |k,v|
160
+ arg.each do |k, v|
94
161
  key = k.to_s
95
162
  if key =~ NCNAME_RE
96
- ns_name = key.split(":", 2)[1]
97
- elm.add_namespace_definition ns_name, v
163
+ ns_name = Regexp.last_match(1)
164
+ elm.add_namespace_definition(ns_name, v)
98
165
  else
99
166
  elm[k.to_s] = v.to_s
100
167
  end
101
- }
168
+ end
102
169
  else
103
170
  elm.content = arg
104
171
  end
105
172
  end
106
- if ns = elm.namespace_definitions.find { |n| n.prefix.nil? or n.prefix == '' }
173
+ if ns = elm.namespace_definitions.find { |n| n.prefix.nil? || (n.prefix == '') }
107
174
  elm.namespace = ns
108
175
  end
109
176
  elm
@@ -251,30 +318,14 @@ module Nokogiri
251
318
  end
252
319
  alias :<< :add_child
253
320
 
254
- ##
255
- # +JRuby+
256
- # Wraps Java's org.w3c.dom.document and returns Nokogiri::XML::Document
257
- def self.wrap document
258
- raise "JRuby only method" unless Nokogiri.jruby?
259
- return wrapJavaDocument(document)
260
- end
261
-
262
- ##
263
- # +JRuby+
264
- # Returns Java's org.w3c.dom.document of this Document.
265
- def to_java
266
- raise "JRuby only method" unless Nokogiri.jruby?
267
- return toJavaDocument()
268
- end
269
-
270
321
  private
322
+
271
323
  def self.empty_doc? string_or_io
272
324
  string_or_io.nil? ||
273
325
  (string_or_io.respond_to?(:empty?) && string_or_io.empty?) ||
274
326
  (string_or_io.respond_to?(:eof?) && string_or_io.eof?)
275
327
  end
276
328
 
277
- # @private
278
329
  IMPLIED_XPATH_CONTEXTS = [ '//'.freeze ].freeze # :nodoc:
279
330
 
280
331
  def inspect_attributes
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class DocumentFragment < Nokogiri::XML::Node
@@ -140,6 +141,10 @@ module Nokogiri
140
141
  document.errors = things
141
142
  end
142
143
 
144
+ def fragment(data)
145
+ document.fragment(data)
146
+ end
147
+
143
148
  private
144
149
 
145
150
  # fix for issue 770
@@ -149,12 +154,6 @@ module Nokogiri
149
154
  %Q{xmlns#{prefix}="#{namespace.href}"}
150
155
  end.join ' '
151
156
  end
152
-
153
- def coerce data
154
- return super unless String === data
155
-
156
- document.fragment(data).children
157
- end
158
157
  end
159
158
  end
160
159
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class DTD < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class ElementDecl < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class EntityDecl < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class EntityReference < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class Namespace
@@ -1,105 +1,103 @@
1
1
  # encoding: UTF-8
2
- require 'stringio'
3
- require 'nokogiri/xml/node/save_options'
2
+ # frozen_string_literal: true
3
+ require "stringio"
4
+ require "nokogiri/xml/node/save_options"
4
5
 
5
6
  module Nokogiri
6
7
  module XML
7
- ####
8
- # Nokogiri::XML::Node is your window to the fun filled world of dealing
9
- # with XML and HTML tags. A Nokogiri::XML::Node may be treated similarly
10
- # to a hash with regard to attributes. For example (from irb):
8
+ ##
9
+ # {Nokogiri::XML::Node} is your window to the fun filled world of dealing with XML and HTML
10
+ # tags. A {Nokogiri::XML::Node} may be treated similarly to a hash with regard to attributes. For
11
+ # example:
11
12
  #
12
- # irb(main):004:0> node
13
- # => <a href="#foo" id="link">link</a>
14
- # irb(main):005:0> node['href']
15
- # => "#foo"
16
- # irb(main):006:0> node.keys
17
- # => ["href", "id"]
18
- # irb(main):007:0> node.values
19
- # => ["#foo", "link"]
20
- # irb(main):008:0> node['class'] = 'green'
21
- # => "green"
22
- # irb(main):009:0> node
23
- # => <a href="#foo" id="link" class="green">link</a>
24
- # irb(main):010:0>
13
+ # node = Nokogiri::XML::DocumentFragment.parse("<a href='#foo' id='link'>link</a>").at_css("a")
14
+ # node.to_html # => "<a href=\"#foo\" id=\"link\">link</a>"
15
+ # node['href'] # => "#foo"
16
+ # node.keys # => ["href", "id"]
17
+ # node.values # => ["#foo", "link"]
18
+ # node['class'] = 'green' # => "green"
19
+ # node.to_html # => "<a href=\"#foo\" id=\"link\" class=\"green\">link</a>"
25
20
  #
26
- # See Nokogiri::XML::Node#[] and Nokogiri::XML#[]= for more information.
21
+ # See the method group entitled "Working With Node Attributes" for the full set of methods.
27
22
  #
28
- # Nokogiri::XML::Node also has methods that let you move around your
23
+ # {Nokogiri::XML::Node} also has methods that let you move around your
29
24
  # tree. For navigating your tree, see:
30
25
  #
31
- # * Nokogiri::XML::Node#parent
32
- # * Nokogiri::XML::Node#children
33
- # * Nokogiri::XML::Node#next
34
- # * Nokogiri::XML::Node#previous
35
- #
26
+ # * {#parent}
27
+ # * {#children}
28
+ # * {#next}
29
+ # * {#previous}
36
30
  #
37
31
  # When printing or otherwise emitting a document or a node (and
38
32
  # its subtree), there are a few methods you might want to use:
39
33
  #
40
- # * content, text, inner_text, to_str: emit plaintext
41
- #
42
- # These methods will all emit the plaintext version of your
43
- # document, meaning that entities will be replaced (e.g., "&lt;"
44
- # will be replaced with "<"), meaning that any sanitizing will
45
- # likely be un-done in the output.
34
+ # * {#content}, {#text}, {#inner_text}, {#to_str}: These methods will all <b>emit plaintext</b>,
35
+ # meaning that entities will be replaced (e.g., "&lt;" will be replaced with "<"), meaning
36
+ # that any sanitizing will likely be un-done in the output.
46
37
  #
47
- # * to_s, to_xml, to_html, inner_html: emit well-formed markup
38
+ # * {#to_s}, {#to_xml}, {#to_html}, {#inner_html}: These methods will all <b>emit
39
+ # properly-escaped markup</b>, meaning that it's suitable for consumption by browsers,
40
+ # parsers, etc.
48
41
  #
49
- # These methods will all emit properly-escaped markup, meaning
50
- # that it's suitable for consumption by browsers, parsers, etc.
42
+ # You may search this node's subtree using {#xpath} and {#css}
51
43
  #
52
- # You may search this node's subtree using Searchable#xpath and Searchable#css
53
44
  class Node
54
45
  include Nokogiri::XML::PP::Node
55
46
  include Nokogiri::XML::Searchable
56
47
  include Enumerable
57
48
 
58
- # Element node type, see Nokogiri::XML::Node#element?
59
- ELEMENT_NODE = 1
49
+ # Element node type, see {Nokogiri::XML::Node#element?}
50
+ ELEMENT_NODE = 1
60
51
  # Attribute node type
61
- ATTRIBUTE_NODE = 2
62
- # Text node type, see Nokogiri::XML::Node#text?
63
- TEXT_NODE = 3
64
- # CDATA node type, see Nokogiri::XML::Node#cdata?
52
+ ATTRIBUTE_NODE = 2
53
+ # Text node type, see {Nokogiri::XML::Node#text?}
54
+ TEXT_NODE = 3
55
+ # CDATA node type, see {Nokogiri::XML::Node#cdata?}
65
56
  CDATA_SECTION_NODE = 4
66
57
  # Entity reference node type
67
- ENTITY_REF_NODE = 5
58
+ ENTITY_REF_NODE = 5
68
59
  # Entity node type
69
- ENTITY_NODE = 6
60
+ ENTITY_NODE = 6
70
61
  # PI node type
71
- PI_NODE = 7
72
- # Comment node type, see Nokogiri::XML::Node#comment?
73
- COMMENT_NODE = 8
74
- # Document node type, see Nokogiri::XML::Node#xml?
75
- DOCUMENT_NODE = 9
62
+ PI_NODE = 7
63
+ # Comment node type, see {Nokogiri::XML::Node#comment?}
64
+ COMMENT_NODE = 8
65
+ # Document node type, see {Nokogiri::XML::Node#xml?}
66
+ DOCUMENT_NODE = 9
76
67
  # Document type node type
77
68
  DOCUMENT_TYPE_NODE = 10
78
69
  # Document fragment node type
79
70
  DOCUMENT_FRAG_NODE = 11
80
71
  # Notation node type
81
- NOTATION_NODE = 12
82
- # HTML document node type, see Nokogiri::XML::Node#html?
72
+ NOTATION_NODE = 12
73
+ # HTML document node type, see {Nokogiri::XML::Node#html?}
83
74
  HTML_DOCUMENT_NODE = 13
84
75
  # DTD node type
85
- DTD_NODE = 14
76
+ DTD_NODE = 14
86
77
  # Element declaration type
87
- ELEMENT_DECL = 15
78
+ ELEMENT_DECL = 15
88
79
  # Attribute declaration type
89
- ATTRIBUTE_DECL = 16
80
+ ATTRIBUTE_DECL = 16
90
81
  # Entity declaration type
91
- ENTITY_DECL = 17
82
+ ENTITY_DECL = 17
92
83
  # Namespace declaration type
93
- NAMESPACE_DECL = 18
84
+ NAMESPACE_DECL = 18
94
85
  # XInclude start type
95
- XINCLUDE_START = 19
86
+ XINCLUDE_START = 19
96
87
  # XInclude end type
97
- XINCLUDE_END = 20
88
+ XINCLUDE_END = 20
98
89
  # DOCB document node type
99
90
  DOCB_DOCUMENT_NODE = 21
100
91
 
101
- def initialize name, document # :nodoc:
102
- # ... Ya. This is empty on purpose.
92
+ ##
93
+ # Create a new node with +name+ sharing GC lifecycle with +document+.
94
+ # @param name [String]
95
+ # @param document [Nokogiri::XML::Document]
96
+ # @yieldparam node [Nokogiri::XML::Node]
97
+ # @return [Nokogiri::XML::Node]
98
+ # @see Nokogiri::XML::Node.new
99
+ def initialize(name, document)
100
+ # This is intentionally empty.
103
101
  end
104
102
 
105
103
  ###
@@ -108,24 +106,18 @@ module Nokogiri
108
106
  document.decorate(self)
109
107
  end
110
108
 
109
+ # @!group Searching via XPath or CSS Queries
110
+
111
111
  ###
112
112
  # Search this node's immediate children using CSS selector +selector+
113
- def > selector
113
+ def >(selector)
114
114
  ns = document.root.namespaces
115
115
  xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
116
116
  end
117
117
 
118
- ###
119
- # Get the attribute value for the attribute +name+
120
- def [] name
121
- get(name.to_s)
122
- end
118
+ # @!endgroup
123
119
 
124
- ###
125
- # Set the attribute value for the attribute +name+ to +value+
126
- def []= name, value
127
- set name.to_s, value.to_s
128
- end
120
+ # @!group Manipulating Document Structure
129
121
 
130
122
  ###
131
123
  # Add +node_or_tags+ as a child of this Node.
@@ -134,7 +126,7 @@ module Nokogiri
134
126
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
135
127
  #
136
128
  # Also see related method +<<+.
137
- def add_child node_or_tags
129
+ def add_child(node_or_tags)
138
130
  node_or_tags = coerce(node_or_tags)
139
131
  if node_or_tags.is_a?(XML::NodeSet)
140
132
  node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
@@ -151,7 +143,7 @@ module Nokogiri
151
143
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
152
144
  #
153
145
  # Also see related method +add_child+.
154
- def prepend_child node_or_tags
146
+ def prepend_child(node_or_tags)
155
147
  if first = children.first
156
148
  # Mimic the error add_child would raise.
157
149
  raise RuntimeError, "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -161,7 +153,6 @@ module Nokogiri
161
153
  end
162
154
  end
163
155
 
164
-
165
156
  ###
166
157
  # Add html around this node
167
158
  #
@@ -180,7 +171,7 @@ module Nokogiri
180
171
  # Returns self, to support chaining of calls (e.g., root << child1 << child2)
181
172
  #
182
173
  # Also see related method +add_child+.
183
- def << node_or_tags
174
+ def <<(node_or_tags)
184
175
  add_child node_or_tags
185
176
  self
186
177
  end
@@ -192,7 +183,7 @@ module Nokogiri
192
183
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
193
184
  #
194
185
  # Also see related method +before+.
195
- def add_previous_sibling node_or_tags
186
+ def add_previous_sibling(node_or_tags)
196
187
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
197
188
 
198
189
  add_sibling :previous, node_or_tags
@@ -205,7 +196,7 @@ module Nokogiri
205
196
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
206
197
  #
207
198
  # Also see related method +after+.
208
- def add_next_sibling node_or_tags
199
+ def add_next_sibling(node_or_tags)
209
200
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
210
201
 
211
202
  add_sibling :next, node_or_tags
@@ -218,7 +209,7 @@ module Nokogiri
218
209
  # Returns self, to support chaining of calls.
219
210
  #
220
211
  # Also see related method +add_previous_sibling+.
221
- def before node_or_tags
212
+ def before(node_or_tags)
222
213
  add_previous_sibling node_or_tags
223
214
  self
224
215
  end
@@ -230,7 +221,7 @@ module Nokogiri
230
221
  # Returns self, to support chaining of calls.
231
222
  #
232
223
  # Also see related method +add_next_sibling+.
233
- def after node_or_tags
224
+ def after(node_or_tags)
234
225
  add_next_sibling node_or_tags
235
226
  self
236
227
  end
@@ -242,7 +233,7 @@ module Nokogiri
242
233
  # Returns self.
243
234
  #
244
235
  # Also see related method +children=+
245
- def inner_html= node_or_tags
236
+ def inner_html=(node_or_tags)
246
237
  self.children = node_or_tags
247
238
  self
248
239
  end
@@ -254,7 +245,7 @@ module Nokogiri
254
245
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
255
246
  #
256
247
  # Also see related method +inner_html=+
257
- def children= node_or_tags
248
+ def children=(node_or_tags)
258
249
  node_or_tags = coerce(node_or_tags)
259
250
  children.unlink
260
251
  if node_or_tags.is_a?(XML::NodeSet)
@@ -272,19 +263,21 @@ module Nokogiri
272
263
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
273
264
  #
274
265
  # Also see related method +swap+.
275
- def replace node_or_tags
266
+ def replace(node_or_tags)
267
+ raise("Cannot replace a node with no parent") unless parent
268
+
276
269
  # We cannot replace a text node directly, otherwise libxml will return
277
270
  # an internal error at parser.c:13031, I don't know exactly why
278
271
  # libxml is trying to find a parent node that is an element or document
279
272
  # so I can't tell if this is bug in libxml or not. issue #775.
280
273
  if text?
281
- replacee = Nokogiri::XML::Node.new 'dummy', document
274
+ replacee = Nokogiri::XML::Node.new "dummy", document
282
275
  add_previous_sibling_node replacee
283
276
  unlink
284
277
  return replacee.replace node_or_tags
285
278
  end
286
279
 
287
- node_or_tags = coerce(node_or_tags)
280
+ node_or_tags = parent.coerce(node_or_tags)
288
281
 
289
282
  if node_or_tags.is_a?(XML::NodeSet)
290
283
  node_or_tags.each { |n| add_previous_sibling n }
@@ -302,33 +295,98 @@ module Nokogiri
302
295
  # Returns self, to support chaining of calls.
303
296
  #
304
297
  # Also see related method +replace+.
305
- def swap node_or_tags
298
+ def swap(node_or_tags)
306
299
  replace node_or_tags
307
300
  self
308
301
  end
309
302
 
310
- alias :next :next_sibling
311
- alias :previous :previous_sibling
303
+ ####
304
+ # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
305
+ def content=(string)
306
+ self.native_content = encode_special_chars(string.to_s)
307
+ end
312
308
 
313
- # :stopdoc:
314
- # HACK: This is to work around an RDoc bug
315
- alias :next= :add_next_sibling
316
- # :startdoc:
309
+ ###
310
+ # Set the parent Node for this Node
311
+ def parent=(parent_node)
312
+ parent_node.add_child(self)
313
+ parent_node
314
+ end
317
315
 
318
- alias :previous= :add_previous_sibling
319
- alias :remove :unlink
320
- alias :get_attribute :[]
321
- alias :attr :[]
322
- alias :set_attribute :[]=
323
- alias :text :content
324
- alias :inner_text :content
325
- alias :has_attribute? :key?
326
- alias :name :node_name
327
- alias :name= :node_name=
328
- alias :type :node_type
329
- alias :to_str :text
330
- alias :clone :dup
331
- alias :elements :element_children
316
+ ###
317
+ # Adds a default namespace supplied as a string +url+ href, to self.
318
+ # The consequence is as an xmlns attribute with supplied argument were
319
+ # present in parsed XML. A default namespace set with this method will
320
+ # now show up in #attributes, but when this node is serialized to XML an
321
+ # "xmlns" attribute will appear. See also #namespace and #namespace=
322
+ def default_namespace=(url)
323
+ add_namespace_definition(nil, url)
324
+ end
325
+
326
+ ###
327
+ # Set the default namespace on this node (as would be defined with an
328
+ # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
329
+ # a Namespace added this way will NOT be serialized as an xmlns attribute
330
+ # for this node. You probably want #default_namespace= instead, or perhaps
331
+ # #add_namespace_definition with a nil prefix argument.
332
+ def namespace=(ns)
333
+ return set_namespace(ns) unless ns
334
+
335
+ unless Nokogiri::XML::Namespace === ns
336
+ raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
337
+ end
338
+ if ns.document != document
339
+ raise ArgumentError, "namespace must be declared on the same document"
340
+ end
341
+
342
+ set_namespace ns
343
+ end
344
+
345
+ ###
346
+ # Do xinclude substitution on the subtree below node. If given a block, a
347
+ # Nokogiri::XML::ParseOptions object initialized from +options+, will be
348
+ # passed to it, allowing more convenient modification of the parser options.
349
+ def do_xinclude(options = XML::ParseOptions::DEFAULT_XML)
350
+ options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
351
+
352
+ # give options to user
353
+ yield options if block_given?
354
+
355
+ # call c extension
356
+ process_xincludes(options.to_i)
357
+ end
358
+
359
+ alias :next :next_sibling
360
+ alias :previous :previous_sibling
361
+ alias :next= :add_next_sibling
362
+ alias :previous= :add_previous_sibling
363
+ alias :remove :unlink
364
+ alias :name= :node_name=
365
+ alias :add_namespace :add_namespace_definition
366
+
367
+ # @!endgroup
368
+
369
+ alias :text :content
370
+ alias :inner_text :content
371
+ alias :name :node_name
372
+ alias :type :node_type
373
+ alias :to_str :text
374
+ alias :clone :dup
375
+ alias :elements :element_children
376
+
377
+ # @!group Working With Node Attributes
378
+
379
+ ###
380
+ # Get the attribute value for the attribute +name+
381
+ def [](name)
382
+ get(name.to_s)
383
+ end
384
+
385
+ ###
386
+ # Set the attribute value for the attribute +name+ to +value+
387
+ def []=(name, value)
388
+ set name.to_s, value.to_s
389
+ end
332
390
 
333
391
  ####
334
392
  # Returns a hash containing the node's attributes. The key is
@@ -337,9 +395,9 @@ module Nokogiri
337
395
  # If you need to distinguish attributes with the same name, with different namespaces
338
396
  # use #attribute_nodes instead.
339
397
  def attributes
340
- Hash[attribute_nodes.map { |node|
341
- [node.node_name, node]
342
- }]
398
+ attribute_nodes.each_with_object({}) do |node, hash|
399
+ hash[node.node_name] = node
400
+ end
343
401
  end
344
402
 
345
403
  ###
@@ -348,6 +406,12 @@ module Nokogiri
348
406
  attribute_nodes.map(&:value)
349
407
  end
350
408
 
409
+ ###
410
+ # Does this Node's attributes include <value>
411
+ def value?(value)
412
+ values.include? value
413
+ end
414
+
351
415
  ###
352
416
  # Get the attribute names for this Node.
353
417
  def keys
@@ -363,82 +427,366 @@ module Nokogiri
363
427
  end
364
428
 
365
429
  ###
366
- # Get the list of class names of this Node, without
367
- # deduplication or sorting.
430
+ # Remove the attribute named +name+
431
+ def remove_attribute(name)
432
+ attr = attributes[name].remove if key? name
433
+ clear_xpath_context if Nokogiri.jruby?
434
+ attr
435
+ end
436
+
437
+ # Get the CSS class names of a Node.
438
+ #
439
+ # This is a convenience function and is equivalent to:
440
+ # node.kwattr_values("class")
441
+ #
442
+ # @see #kwattr_values
443
+ # @see #add_class
444
+ # @see #append_class
445
+ # @see #remove_class
446
+ #
447
+ # @return [Array<String>]
448
+ #
449
+ # The CSS classes present in the Node's +class+ attribute. If
450
+ # the attribute is empty or non-existent, the return value is
451
+ # an empty array.
452
+ #
453
+ # @example
454
+ # node # => <div class="section title header"></div>
455
+ # node.classes # => ["section", "title", "header"]
456
+ #
368
457
  def classes
369
- self['class'].to_s.scan(/\S+/)
458
+ kwattr_values("class")
370
459
  end
371
460
 
372
- ###
373
- # Add +name+ to the "class" attribute value of this Node and
374
- # return self. If the value is already in the current value, it
375
- # is not added. If no "class" attribute exists yet, one is
376
- # created with the given value.
461
+ # Ensure HTML CSS classes are present on a +Node+. Any CSS
462
+ # classes in +names+ that already exist in the +Node+'s +class+
463
+ # attribute are _not_ added. Note that any existing duplicates
464
+ # in the +class+ attribute are not removed. Compare with
465
+ # {#append_class}.
466
+ #
467
+ # This is a convenience function and is equivalent to:
468
+ # node.kwattr_add("class", names)
469
+ #
470
+ # @see #kwattr_add
471
+ # @see #classes
472
+ # @see #append_class
473
+ # @see #remove_class
474
+ #
475
+ # @param names [String, Array<String>]
476
+ #
477
+ # CSS class names to be added to the Node's +class+
478
+ # attribute. May be a string containing whitespace-delimited
479
+ # names, or an Array of String names. Any class names already
480
+ # present will not be added. Any class names not present will
481
+ # be added. If no +class+ attribute exists, one is created.
482
+ #
483
+ # @return [Node] Returns +self+ for ease of chaining method calls.
484
+ #
485
+ # @example Ensure that a +Node+ has CSS class "section"
486
+ # node # => <div></div>
487
+ # node.add_class("section") # => <div class="section"></div>
488
+ # node.add_class("section") # => <div class="section"></div> # duplicate not added
489
+ #
490
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via a String argument.
491
+ # node # => <div class="section section"></div>
492
+ # node.add_class("section header") # => <div class="section section header"></div>
493
+ # # Note that the CSS class "section" is not added because it is already present.
494
+ # # Note also that the pre-existing duplicate CSS class "section" is not removed.
495
+ #
496
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via an Array argument.
497
+ # node # => <div></div>
498
+ # node.add_class(["section", "header"]) # => <div class="section header"></div>
499
+ #
500
+ def add_class(names)
501
+ kwattr_add("class", names)
502
+ end
503
+
504
+ # Add HTML CSS classes to a +Node+, regardless of
505
+ # duplication. Compare with {#add_class}.
506
+ #
507
+ # This is a convenience function and is equivalent to:
508
+ # node.kwattr_append("class", names)
509
+ #
510
+ # @see #kwattr_append
511
+ # @see #classes
512
+ # @see #add_class
513
+ # @see #remove_class
514
+ #
515
+ # @param names [String, Array<String>]
516
+ #
517
+ # CSS class names to be appended to the Node's +class+
518
+ # attribute. May be a string containing whitespace-delimited
519
+ # names, or an Array of String names. All class names passed
520
+ # in will be appended to the +class+ attribute even if they
521
+ # are already present in the attribute value. If no +class+
522
+ # attribute exists, one is created.
523
+ #
524
+ # @return [Node] Returns +self+ for ease of chaining method calls.
525
+ #
526
+ # @example Append "section" to a +Node+'s CSS +class+ attriubute
527
+ # node # => <div></div>
528
+ # node.append_class("section") # => <div class="section"></div>
529
+ # node.append_class("section") # => <div class="section section"></div> # duplicate added!
530
+ #
531
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via a String argument.
532
+ # node # => <div class="section section"></div>
533
+ # node.append_class("section header") # => <div class="section section section header"></div>
534
+ # # Note that the CSS class "section" is appended even though it is already present.
377
535
  #
378
- # More than one class may be added at a time, separated by a
379
- # space.
380
- def add_class name
381
- names = classes
382
- self['class'] = (names + (name.scan(/\S+/) - names)).join(' ')
536
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via an Array argument.
537
+ # node # => <div></div>
538
+ # node.append_class(["section", "header"]) # => <div class="section header"></div>
539
+ # node.append_class(["section", "header"]) # => <div class="section header section header"></div>
540
+ #
541
+ def append_class(names)
542
+ kwattr_append("class", names)
543
+ end
544
+
545
+ # Remove HTML CSS classes from a +Node+. Any CSS classes in +names+ that
546
+ # exist in the +Node+'s +class+ attribute are removed, including any
547
+ # multiple entries.
548
+ #
549
+ # If no CSS classes remain after this operation, or if +names+ is
550
+ # +nil+, the +class+ attribute is deleted from the node.
551
+ #
552
+ # This is a convenience function and is equivalent to:
553
+ # node.kwattr_remove("class", names)
554
+ #
555
+ # @see #kwattr_remove
556
+ # @see #classes
557
+ # @see #add_class
558
+ # @see #append_class
559
+ #
560
+ # @param names [String, Array<String>]
561
+ #
562
+ # CSS class names to be removed from the Node's +class+ attribute. May
563
+ # be a string containing whitespace-delimited names, or an Array of
564
+ # String names. Any class names already present will be removed. If no
565
+ # CSS classes remain, the +class+ attribute is deleted.
566
+ #
567
+ # @return [Node] Returns +self+ for ease of chaining method calls.
568
+ #
569
+ # @example
570
+ # node # => <div class="section header"></div>
571
+ # node.remove_class("section") # => <div class="header"></div>
572
+ # node.remove_class("header") # => <div></div> # attribute is deleted when empty
573
+ #
574
+ def remove_class(names = nil)
575
+ kwattr_remove("class", names)
576
+ end
577
+
578
+ # Retrieve values from a keyword attribute of a Node.
579
+ #
580
+ # A "keyword attribute" is a node attribute that contains a set
581
+ # of space-delimited values. Perhaps the most familiar example
582
+ # of this is the HTML +class+ attribute used to contain CSS
583
+ # classes. But other keyword attributes exist, for instance
584
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
585
+ #
586
+ # @see #classes
587
+ # @see #kwattr_add
588
+ # @see #kwattr_append
589
+ # @see #kwattr_remove
590
+ #
591
+ # @param attribute_name [String] The name of the keyword attribute to be inspected.
592
+ #
593
+ # @return [Array<String>]
594
+ #
595
+ # The values present in the Node's +attribute_name+
596
+ # attribute. If the attribute is empty or non-existent, the
597
+ # return value is an empty array.
598
+ #
599
+ # @example
600
+ # node # => <a rel="nofollow noopener external">link</a>
601
+ # node.kwattr_values("rel") # => ["nofollow", "noopener", "external"]
602
+ #
603
+ # @since v1.11.0
604
+ #
605
+ def kwattr_values(attribute_name)
606
+ keywordify(get_attribute(attribute_name) || [])
607
+ end
608
+
609
+ # Ensure that values are present in a keyword attribute.
610
+ #
611
+ # Any values in +keywords+ that already exist in the +Node+'s
612
+ # attribute values are _not_ added. Note that any existing
613
+ # duplicates in the attribute values are not removed. Compare
614
+ # with {#kwattr_append}.
615
+ #
616
+ # A "keyword attribute" is a node attribute that contains a set
617
+ # of space-delimited values. Perhaps the most familiar example
618
+ # of this is the HTML +class+ attribute used to contain CSS
619
+ # classes. But other keyword attributes exist, for instance
620
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
621
+ #
622
+ # @see #add_class
623
+ # @see #kwattr_values
624
+ # @see #kwattr_append
625
+ # @see #kwattr_remove
626
+ #
627
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
628
+ #
629
+ # @param keywords [String, Array<String>]
630
+ #
631
+ # Keywords to be added to the attribute named
632
+ # +attribute_name+. May be a string containing
633
+ # whitespace-delimited values, or an Array of String
634
+ # values. Any values already present will not be added. Any
635
+ # values not present will be added. If the named attribute
636
+ # does not exist, it is created.
637
+ #
638
+ # @return [Node] Returns +self+ for ease of chaining method calls.
639
+ #
640
+ # @example Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
641
+ # node # => <a></a>
642
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
643
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a> # duplicate not added
644
+ #
645
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a String argument.
646
+ # node # => <a rel="nofollow nofollow"></a>
647
+ # node.kwattr_add("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
648
+ # # Note that "nofollow" is not added because it is already present.
649
+ # # Note also that the pre-existing duplicate "nofollow" is not removed.
650
+ #
651
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via an Array argument.
652
+ # node # => <a></a>
653
+ # node.kwattr_add("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
654
+ #
655
+ # @since v1.11.0
656
+ #
657
+ def kwattr_add(attribute_name, keywords)
658
+ keywords = keywordify(keywords)
659
+ current_kws = kwattr_values(attribute_name)
660
+ new_kws = (current_kws + (keywords - current_kws)).join(" ")
661
+ set_attribute(attribute_name, new_kws)
383
662
  self
384
663
  end
385
664
 
386
- ###
387
- # Append +name+ to the "class" attribute value of this Node and
388
- # return self. The value is simply appended without checking if
389
- # it is already in the current value. If no "class" attribute
390
- # exists yet, one is created with the given value.
665
+ # Add keywords to a Node's keyword attribute, regardless of
666
+ # duplication. Compare with {#kwattr_add}.
667
+ #
668
+ # A "keyword attribute" is a node attribute that contains a set
669
+ # of space-delimited values. Perhaps the most familiar example
670
+ # of this is the HTML +class+ attribute used to contain CSS
671
+ # classes. But other keyword attributes exist, for instance
672
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
391
673
  #
392
- # More than one class may be appended at a time, separated by a
393
- # space.
394
- def append_class name
395
- self['class'] = (classes + name.scan(/\S+/)).join(' ')
674
+ # @see #append_class
675
+ # @see #kwattr_values
676
+ # @see #kwattr_add
677
+ # @see #kwattr_remove
678
+ #
679
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
680
+ #
681
+ # @param keywords [String, Array<String>]
682
+ #
683
+ # Keywords to be added to the attribute named
684
+ # +attribute_name+. May be a string containing
685
+ # whitespace-delimited values, or an Array of String
686
+ # values. All values passed in will be appended to the named
687
+ # attribute even if they are already present in the
688
+ # attribute. If the named attribute does not exist, it is
689
+ # created.
690
+ #
691
+ # @return [Node] Returns +self+ for ease of chaining method calls.
692
+ #
693
+ # @example Append "nofollow" to the +rel+ attribute.
694
+ # node # => <a></a>
695
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow"></a>
696
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a> # duplicate added!
697
+ #
698
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
699
+ # node # => <a rel="nofollow"></a>
700
+ # node.kwattr_append("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
701
+ # # Note that "nofollow" is appended even though it is already present.
702
+ #
703
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
704
+ # node # => <a></a>
705
+ # node.kwattr_append("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
706
+ #
707
+ # @since v1.11.0
708
+ #
709
+ def kwattr_append(attribute_name, keywords)
710
+ keywords = keywordify(keywords)
711
+ current_kws = kwattr_values(attribute_name)
712
+ new_kws = (current_kws + keywords).join(" ")
713
+ set_attribute(attribute_name, new_kws)
396
714
  self
397
715
  end
398
716
 
399
- ###
400
- # Remove +name+ from the "class" attribute value of this Node
401
- # and return self. If there are many occurrences of the name,
402
- # they are all removed.
717
+ # Remove keywords from a keyword attribute. Any matching
718
+ # keywords that exist in the named attribute are removed,
719
+ # including any multiple entries.
403
720
  #
404
- # More than one class may be removed at a time, separated by a
405
- # space.
721
+ # If no keywords remain after this operation, or if +keywords+
722
+ # is +nil+, the attribute is deleted from the node.
406
723
  #
407
- # If no class name is left after removal, or when +name+ is nil,
408
- # the "class" attribute is removed from this Node.
409
- def remove_class name = nil
410
- if name
411
- names = classes - name.scan(/\S+/)
412
- if names.empty?
413
- delete 'class'
414
- else
415
- self['class'] = names.join(' ')
416
- end
724
+ # A "keyword attribute" is a node attribute that contains a set
725
+ # of space-delimited values. Perhaps the most familiar example
726
+ # of this is the HTML +class+ attribute used to contain CSS
727
+ # classes. But other keyword attributes exist, for instance
728
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
729
+ #
730
+ # @see #remove_class
731
+ # @see #kwattr_values
732
+ # @see #kwattr_add
733
+ # @see #kwattr_append
734
+ #
735
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
736
+ #
737
+ # @param keywords [String, Array<String>]
738
+ #
739
+ # Keywords to be removed from the attribute named
740
+ # +attribute_name+. May be a string containing
741
+ # whitespace-delimited values, or an Array of String
742
+ # values. Any keywords present in the named attribute will be
743
+ # removed. If no keywords remain, or if +keywords+ is nil, the
744
+ # attribute is deleted.
745
+ #
746
+ # @return [Node] Returns +self+ for ease of chaining method calls.
747
+ #
748
+ # @example
749
+ # node # => <a rel="nofollow noreferrer">link</a>
750
+ # node.kwattr_remove("rel", "nofollow") # => <a rel="noreferrer">link</a>
751
+ # node.kwattr_remove("rel", "noreferrer") # => <a>link</a> # attribute is deleted when empty
752
+ #
753
+ # @since v1.11.0
754
+ #
755
+ def kwattr_remove(attribute_name, keywords)
756
+ if keywords.nil?
757
+ remove_attribute(attribute_name)
758
+ return self
759
+ end
760
+
761
+ keywords = keywordify(keywords)
762
+ current_kws = kwattr_values(attribute_name)
763
+ new_kws = current_kws - keywords
764
+ if new_kws.empty?
765
+ remove_attribute(attribute_name)
417
766
  else
418
- delete "class"
767
+ set_attribute(attribute_name, new_kws.join(" "))
419
768
  end
420
769
  self
421
770
  end
422
771
 
423
- ###
424
- # Remove the attribute named +name+
425
- def remove_attribute name
426
- attr = attributes[name].remove if key? name
427
- clear_xpath_context if Nokogiri.jruby?
428
- attr
429
- end
430
772
  alias :delete :remove_attribute
773
+ alias :get_attribute :[]
774
+ alias :attr :[]
775
+ alias :set_attribute :[]=
776
+ alias :has_attribute? :key?
777
+
778
+ # @!endgroup
431
779
 
432
780
  ###
433
781
  # Returns true if this Node matches +selector+
434
- def matches? selector
782
+ def matches?(selector)
435
783
  ancestors.last.search(selector).include?(self)
436
784
  end
437
785
 
438
786
  ###
439
787
  # Create a DocumentFragment containing +tags+ that is relative to _this_
440
788
  # context node.
441
- def fragment tags
789
+ def fragment(tags)
442
790
  type = document.html? ? Nokogiri::HTML : Nokogiri::XML
443
791
  type::DocumentFragment.new(document, tags, self)
444
792
  end
@@ -447,7 +795,7 @@ module Nokogiri
447
795
  # Parse +string_or_io+ as a document fragment within the context of
448
796
  # *this* node. Returns a XML::NodeSet containing the nodes parsed from
449
797
  # +string_or_io+.
450
- def parse string_or_io, options = nil
798
+ def parse(string_or_io, options = nil)
451
799
  ##
452
800
  # When the current node is unparented and not an element node, use the
453
801
  # document as the parsing context instead. Otherwise, the in-context
@@ -470,30 +818,34 @@ module Nokogiri
470
818
 
471
819
  return Nokogiri::XML::NodeSet.new(document) if contents.empty?
472
820
 
473
- ##
474
- # This is a horrible hack, but I don't care. See #313 for background.
821
+ # libxml2 does not obey the `recover` option after encountering errors during `in_context`
822
+ # parsing, and so this horrible hack is here to try to emulate recovery behavior.
823
+ #
824
+ # Unfortunately, this means we're no longer parsing "in context" and so namespaces that
825
+ # would have been inherited from the context node won't be handled correctly. This hack was
826
+ # written in 2010, and I regret it, because it's silently degrading functionality in a way
827
+ # that's not easily prevented (or even detected).
828
+ #
829
+ # I think preferable behavior would be to either:
830
+ #
831
+ # a. add an error noting that we "fell back" and pointing the user to turning off the `recover` option
832
+ # b. don't recover, but raise a sensible exception
833
+ #
834
+ # For context and background: https://github.com/sparklemotion/nokogiri/issues/313
835
+ # FIXME bug report: https://github.com/sparklemotion/nokogiri/issues/2092
475
836
  error_count = document.errors.length
476
837
  node_set = in_context(contents, options.to_i)
477
- if node_set.empty? and document.errors.length > error_count and options.recover?
478
- fragment = Nokogiri::HTML::DocumentFragment.parse contents
479
- node_set = fragment.children
838
+ if (node_set.empty? && (document.errors.length > error_count))
839
+ if options.recover?
840
+ fragment = Nokogiri::HTML::DocumentFragment.parse contents
841
+ node_set = fragment.children
842
+ else
843
+ raise document.errors[error_count]
844
+ end
480
845
  end
481
846
  node_set
482
847
  end
483
848
 
484
- ####
485
- # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
486
- def content= string
487
- self.native_content = encode_special_chars(string.to_s)
488
- end
489
-
490
- ###
491
- # Set the parent Node for this Node
492
- def parent= parent_node
493
- parent_node.add_child(self)
494
- parent_node
495
- end
496
-
497
849
  ###
498
850
  # Returns a Hash of +{prefix => value}+ for all namespaces on this
499
851
  # node and its ancestors.
@@ -509,10 +861,11 @@ module Nokogiri
509
861
  # default namespaces set on ancestor will NOT be, even if self
510
862
  # has no explicit default namespace.
511
863
  def namespaces
512
- Hash[namespace_scopes.map { |nd|
513
- key = ['xmlns', nd.prefix].compact.join(':')
514
- [key, nd.href]
515
- }]
864
+ namespace_scopes.each_with_object({}) do |ns, hash|
865
+ prefix = ns.prefix
866
+ key = prefix ? "xmlns:#{prefix}" : "xmlns"
867
+ hash[key] = ns.href
868
+ end
516
869
  end
517
870
 
518
871
  # Returns true if this is a Comment
@@ -574,6 +927,7 @@ module Nokogiri
574
927
  def element?
575
928
  type == ELEMENT_NODE
576
929
  end
930
+
577
931
  alias :elem? :element?
578
932
 
579
933
  ###
@@ -584,7 +938,7 @@ module Nokogiri
584
938
  end
585
939
 
586
940
  # Get the inner_html for this node's Node#children
587
- def inner_html *args
941
+ def inner_html(*args)
588
942
  children.map { |x| x.to_html(*args) }.join
589
943
  end
590
944
 
@@ -592,13 +946,13 @@ module Nokogiri
592
946
  def css_path
593
947
  path.split(/\//).map { |part|
594
948
  part.length == 0 ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
595
- }.compact.join(' > ')
949
+ }.compact.join(" > ")
596
950
  end
597
951
 
598
952
  ###
599
953
  # Get a list of ancestor Node for this Node. If +selector+ is given,
600
954
  # the ancestors must match +selector+
601
- def ancestors selector = nil
955
+ def ancestors(selector = nil)
602
956
  return NodeSet.new(document) unless respond_to?(:parent)
603
957
  return NodeSet.new(document) unless parent
604
958
 
@@ -619,57 +973,38 @@ module Nokogiri
619
973
  })
620
974
  end
621
975
 
622
- ###
623
- # Adds a default namespace supplied as a string +url+ href, to self.
624
- # The consequence is as an xmlns attribute with supplied argument were
625
- # present in parsed XML. A default namespace set with this method will
626
- # now show up in #attributes, but when this node is serialized to XML an
627
- # "xmlns" attribute will appear. See also #namespace and #namespace=
628
- def default_namespace= url
629
- add_namespace_definition(nil, url)
630
- end
631
- alias :add_namespace :add_namespace_definition
632
-
633
- ###
634
- # Set the default namespace on this node (as would be defined with an
635
- # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
636
- # a Namespace added this way will NOT be serialized as an xmlns attribute
637
- # for this node. You probably want #default_namespace= instead, or perhaps
638
- # #add_namespace_definition with a nil prefix argument.
639
- def namespace= ns
640
- return set_namespace(ns) unless ns
641
-
642
- unless Nokogiri::XML::Namespace === ns
643
- raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
644
- end
645
- if ns.document != document
646
- raise ArgumentError, 'namespace must be declared on the same document'
647
- end
648
-
649
- set_namespace ns
650
- end
651
-
652
976
  ####
653
977
  # Yields self and all children to +block+ recursively.
654
- def traverse &block
655
- children.each{|j| j.traverse(&block) }
978
+ def traverse(&block)
979
+ children.each { |j| j.traverse(&block) }
656
980
  block.call(self)
657
981
  end
658
982
 
659
983
  ###
660
984
  # Accept a visitor. This method calls "visit" on +visitor+ with self.
661
- def accept visitor
985
+ def accept(visitor)
662
986
  visitor.visit(self)
663
987
  end
664
988
 
665
989
  ###
666
990
  # Test to see if this Node is equal to +other+
667
- def == other
991
+ def ==(other)
668
992
  return false unless other
669
993
  return false unless other.respond_to?(:pointer_id)
670
994
  pointer_id == other.pointer_id
671
995
  end
672
996
 
997
+ ###
998
+ # Compare two Node objects with respect to their Document. Nodes from
999
+ # different documents cannot be compared.
1000
+ def <=>(other)
1001
+ return nil unless other.is_a?(Nokogiri::XML::Node)
1002
+ return nil unless document == other.document
1003
+ compare other
1004
+ end
1005
+
1006
+ # @!group Serialization and Generating Output
1007
+
673
1008
  ###
674
1009
  # Serialize Node using +options+. Save options can also be set using a
675
1010
  # block. See SaveOptions.
@@ -684,17 +1019,17 @@ module Nokogiri
684
1019
  # config.format.as_xml
685
1020
  # end
686
1021
  #
687
- def serialize *args, &block
1022
+ def serialize(*args, &block)
688
1023
  options = args.first.is_a?(Hash) ? args.shift : {
689
- :encoding => args[0],
690
- :save_with => args[1]
1024
+ :encoding => args[0],
1025
+ :save_with => args[1],
691
1026
  }
692
1027
 
693
1028
  encoding = options[:encoding] || document.encoding
694
1029
  options[:encoding] = encoding
695
1030
 
696
1031
  outstring = String.new
697
- outstring.force_encoding(Encoding.find(encoding || 'utf-8'))
1032
+ outstring.force_encoding(Encoding.find(encoding || "utf-8"))
698
1033
  io = StringIO.new(outstring)
699
1034
  write_to io, options, &block
700
1035
  io.string
@@ -707,7 +1042,7 @@ module Nokogiri
707
1042
  #
708
1043
  # See Node#write_to for a list of +options+. For formatted output,
709
1044
  # use Node#to_xhtml instead.
710
- def to_html options = {}
1045
+ def to_html(options = {})
711
1046
  to_format SaveOptions::DEFAULT_HTML, options
712
1047
  end
713
1048
 
@@ -717,7 +1052,7 @@ module Nokogiri
717
1052
  # doc.to_xml(:indent => 5, :encoding => 'UTF-8')
718
1053
  #
719
1054
  # See Node#write_to for a list of +options+
720
- def to_xml options = {}
1055
+ def to_xml(options = {})
721
1056
  options[:save_with] ||= SaveOptions::DEFAULT_XML
722
1057
  serialize(options)
723
1058
  end
@@ -728,7 +1063,7 @@ module Nokogiri
728
1063
  # doc.to_xhtml(:indent => 5, :encoding => 'UTF-8')
729
1064
  #
730
1065
  # See Node#write_to for a list of +options+
731
- def to_xhtml options = {}
1066
+ def to_xhtml(options = {})
732
1067
  to_format SaveOptions::DEFAULT_XHTML, options
733
1068
  end
734
1069
 
@@ -749,29 +1084,34 @@ module Nokogiri
749
1084
  #
750
1085
  # node.write_to(io, :indent_text => '-', :indent => 2)
751
1086
  #
752
- def write_to io, *options
753
- options = options.first.is_a?(Hash) ? options.shift : {}
754
- encoding = options[:encoding] || options[0]
1087
+ def write_to(io, *options)
1088
+ options = options.first.is_a?(Hash) ? options.shift : {}
1089
+ encoding = options[:encoding] || options[0]
755
1090
  if Nokogiri.jruby?
756
- save_options = options[:save_with] || options[1]
757
- indent_times = options[:indent] || 0
1091
+ save_options = options[:save_with] || options[1]
1092
+ indent_times = options[:indent] || 0
758
1093
  else
759
- save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
760
- indent_times = options[:indent] || 2
1094
+ save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
1095
+ indent_times = options[:indent] || 2
761
1096
  end
762
- indent_text = options[:indent_text] || ' '
1097
+ indent_text = options[:indent_text] || " "
1098
+
1099
+ # Any string times 0 returns an empty string. Therefore, use the same
1100
+ # string instead of generating a new empty string for every node with
1101
+ # zero indentation.
1102
+ indentation = indent_times.zero? ? "" : (indent_text * indent_times)
763
1103
 
764
1104
  config = SaveOptions.new(save_options.to_i)
765
1105
  yield config if block_given?
766
1106
 
767
- native_write_to(io, encoding, indent_text * indent_times, config.options)
1107
+ native_write_to(io, encoding, indentation, config.options)
768
1108
  end
769
1109
 
770
1110
  ###
771
1111
  # Write Node as HTML to +io+ with +options+
772
1112
  #
773
1113
  # See Node#write_to for a list of +options+
774
- def write_html_to io, options = {}
1114
+ def write_html_to(io, options = {})
775
1115
  write_format_to SaveOptions::DEFAULT_HTML, io, options
776
1116
  end
777
1117
 
@@ -779,7 +1119,7 @@ module Nokogiri
779
1119
  # Write Node as XHTML to +io+ with +options+
780
1120
  #
781
1121
  # See Node#write_to for a list of +options+
782
- def write_xhtml_to io, options = {}
1122
+ def write_xhtml_to(io, options = {})
783
1123
  write_format_to SaveOptions::DEFAULT_XHTML, io, options
784
1124
  end
785
1125
 
@@ -789,52 +1129,66 @@ module Nokogiri
789
1129
  # doc.write_xml_to io, :encoding => 'UTF-8'
790
1130
  #
791
1131
  # See Node#write_to for a list of options
792
- def write_xml_to io, options = {}
1132
+ def write_xml_to(io, options = {})
793
1133
  options[:save_with] ||= SaveOptions::DEFAULT_XML
794
1134
  write_to io, options
795
1135
  end
796
1136
 
797
- ###
798
- # Compare two Node objects with respect to their Document. Nodes from
799
- # different documents cannot be compared.
800
- def <=> other
801
- return nil unless other.is_a?(Nokogiri::XML::Node)
802
- return nil unless document == other.document
803
- compare other
1137
+ def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comments = false)
1138
+ c14n_root = self
1139
+ document.canonicalize(mode, inclusive_namespaces, with_comments) do |node, parent|
1140
+ tn = node.is_a?(XML::Node) ? node : parent
1141
+ tn == c14n_root || tn.ancestors.include?(c14n_root)
1142
+ end
804
1143
  end
805
1144
 
806
- ###
807
- # Do xinclude substitution on the subtree below node. If given a block, a
808
- # Nokogiri::XML::ParseOptions object initialized from +options+, will be
809
- # passed to it, allowing more convenient modification of the parser options.
810
- def do_xinclude options = XML::ParseOptions::DEFAULT_XML
811
- options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
1145
+ # @!endgroup
812
1146
 
813
- # give options to user
814
- yield options if block_given?
1147
+ protected
815
1148
 
816
- # call c extension
817
- process_xincludes(options.to_i)
1149
+ def coerce(data)
1150
+ case data
1151
+ when XML::NodeSet
1152
+ return data
1153
+ when XML::DocumentFragment
1154
+ return data.children
1155
+ when String
1156
+ return fragment(data).children
1157
+ when Document, XML::Attr
1158
+ # unacceptable
1159
+ when XML::Node
1160
+ return data
1161
+ end
1162
+
1163
+ raise ArgumentError, <<-EOERR
1164
+ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1165
+ (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
1166
+ EOERR
818
1167
  end
819
1168
 
820
- def canonicalize(mode=XML::XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false)
821
- c14n_root = self
822
- document.canonicalize(mode, inclusive_namespaces, with_comments) do |node, parent|
823
- tn = node.is_a?(XML::Node) ? node : parent
824
- tn == c14n_root || tn.ancestors.include?(c14n_root)
1169
+ private
1170
+
1171
+ def keywordify(keywords)
1172
+ case keywords
1173
+ when Enumerable
1174
+ return keywords
1175
+ when String
1176
+ return keywords.scan(/\S+/)
1177
+ else
1178
+ raise ArgumentError.new("Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}")
825
1179
  end
826
1180
  end
827
1181
 
828
- private
1182
+ def add_sibling(next_or_previous, node_or_tags)
1183
+ raise("Cannot add sibling to a node with no parent") unless parent
829
1184
 
830
- def add_sibling next_or_previous, node_or_tags
831
1185
  impl = (next_or_previous == :next) ? :add_next_sibling_node : :add_previous_sibling_node
832
- iter = (next_or_previous == :next) ? :reverse_each : :each
1186
+ iter = (next_or_previous == :next) ? :reverse_each : :each
833
1187
 
834
- node_or_tags = coerce node_or_tags
1188
+ node_or_tags = parent.coerce(node_or_tags)
835
1189
  if node_or_tags.is_a?(XML::NodeSet)
836
1190
  if text?
837
- pivot = Nokogiri::XML::Node.new 'dummy', document
1191
+ pivot = Nokogiri::XML::Node.new "dummy", document
838
1192
  send impl, pivot
839
1193
  else
840
1194
  pivot = self
@@ -847,17 +1201,18 @@ module Nokogiri
847
1201
  node_or_tags
848
1202
  end
849
1203
 
850
- def to_format save_option, options
851
- # FIXME: this is a hack around broken libxml versions
852
- return dump_html if Nokogiri.uses_libxml? && %w[2 6] === LIBXML_VERSION.split('.')[0..1]
1204
+ USING_LIBXML_WITH_BROKEN_SERIALIZATION = Nokogiri.uses_libxml?("~> 2.6.0").freeze
1205
+ private_constant :USING_LIBXML_WITH_BROKEN_SERIALIZATION
1206
+
1207
+ def to_format(save_option, options)
1208
+ return dump_html if USING_LIBXML_WITH_BROKEN_SERIALIZATION
853
1209
 
854
1210
  options[:save_with] = save_option unless options[:save_with]
855
1211
  serialize(options)
856
1212
  end
857
1213
 
858
- def write_format_to save_option, io, options
859
- # FIXME: this is a hack around broken libxml versions
860
- return (io << dump_html) if Nokogiri.uses_libxml? && %w[2 6] === LIBXML_VERSION.split('.')[0..1]
1214
+ def write_format_to(save_option, io, options)
1215
+ return (io << dump_html) if USING_LIBXML_WITH_BROKEN_SERIALIZATION
861
1216
 
862
1217
  options[:save_with] ||= save_option
863
1218
  write_to io, options
@@ -867,30 +1222,10 @@ module Nokogiri
867
1222
  [:name, :namespace, :attribute_nodes, :children]
868
1223
  end
869
1224
 
870
- def coerce data # :nodoc:
871
- case data
872
- when XML::NodeSet
873
- return data
874
- when XML::DocumentFragment
875
- return data.children
876
- when String
877
- return fragment(data).children
878
- when Document, XML::Attr
879
- # unacceptable
880
- when XML::Node
881
- return data
882
- end
883
-
884
- raise ArgumentError, <<-EOERR
885
- Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
886
- (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
887
- EOERR
888
- end
889
-
890
1225
  # @private
891
- IMPLIED_XPATH_CONTEXTS = [ './/'.freeze ].freeze # :nodoc:
1226
+ IMPLIED_XPATH_CONTEXTS = [".//".freeze].freeze
892
1227
 
893
- def add_child_node_and_reparent_attrs node # :nodoc:
1228
+ def add_child_node_and_reparent_attrs(node)
894
1229
  add_child_node node
895
1230
  node.attribute_nodes.find_all { |a| a.name =~ /:/ }.each do |attr_node|
896
1231
  attr_node.remove