nokogiri 1.11.0.rc1 → 1.11.0.rc2

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a539f5ce4cf20408c97d0ef0eee4d0e480238353d5ff7395a238f6840e02d643
4
- data.tar.gz: bd65eabb6b6039f05fc0c23deced15312c9ad2f08a6cfc1410bf85547400663c
3
+ metadata.gz: 77ee40d690f611a7b66592c4a78669bae74bc1c537cf80bb05e1e04866aa29fa
4
+ data.tar.gz: 3f9db94eaafc3f6b9b07c79ef0ec1269c7966fae49b4361c37faf99a29f42ca3
5
5
  SHA512:
6
- metadata.gz: 4f84caee429c713403ecad29afe7610469208bef7051df8eaf01777fe13d2da5116fb0eb51ed91143ce8fd762045922ec917704fdeee0bb3ffcaed459881f566
7
- data.tar.gz: 4c490990fdd707ee868d85431bc0d6c16b14db23831417c017bff346e3f7787b4e2f30d8112f8a6962e6f341d57cff8616c0c5e50bfe09c79e7b08b5ed89ac86
6
+ metadata.gz: c6084a63e17b6f40029f882083b2e18661debd905e0513f9a93fe082e09b5dee61827afd328059d2cac5e3eb54d333601fabd2072c579e8748d4e95da7a634e0
7
+ data.tar.gz: 99177bed46c8867d114fbbf52ea3699d734d47d8b08b2f877dfa10f2bf6851cbf331930e7bbe34ee3714760bf9cee6d99ea35d25f17c793e544943f1c4f59a7c
data/README.md CHANGED
@@ -11,7 +11,7 @@ or CSS3 selectors.
11
11
 
12
12
  * https://nokogiri.org
13
13
  * [Installation Help](https://nokogiri.org/tutorials/installing_nokogiri.html)
14
- * [Tutorials](https://nokogiri.org)
14
+ * [Tutorials](https://nokogiri.org/tutorials/toc.html)
15
15
  * [Cheat Sheet](https://github.com/sparklemotion/nokogiri/wiki/Cheat-sheet)
16
16
  * [GitHub](https://github.com/sparklemotion/nokogiri)
17
17
  * [Mailing List](https://groups.google.com/group/nokogiri-talk)
@@ -24,6 +24,10 @@ def openbsd?
24
24
  RbConfig::CONFIG['target_os'] =~ /openbsd/
25
25
  end
26
26
 
27
+ def aix?
28
+ RbConfig::CONFIG["target_os"] =~ /aix/
29
+ end
30
+
27
31
  def nix?
28
32
  ! (windows? || solaris? || darwin?)
29
33
  end
@@ -419,7 +423,7 @@ if windows?
419
423
  $CFLAGS << " -DXP_WIN -DXP_WIN32 -DUSE_INCLUDED_VASPRINTF"
420
424
  end
421
425
 
422
- if solaris?
426
+ if solaris? || aix?
423
427
  $CFLAGS << " -DUSE_INCLUDED_VASPRINTF"
424
428
  end
425
429
 
@@ -466,7 +470,7 @@ else
466
470
  # The gem version constraint in the Rakefile is not respected at install time.
467
471
  # Keep this version in sync with the one in the Rakefile !
468
472
  require 'rubygems'
469
- gem 'mini_portile2', '~> 2.4.0'
473
+ gem 'mini_portile2', '~> 2.5.0'
470
474
  require 'mini_portile2'
471
475
  message "Using mini_portile version #{MiniPortile::VERSION}\n"
472
476
 
@@ -1,6 +1,18 @@
1
1
  #ifndef NOKOGIRI_NATIVE
2
2
  #define NOKOGIRI_NATIVE
3
3
 
4
+ #if _MSC_VER
5
+ #ifndef WIN32_LEAN_AND_MEAN
6
+ #define WIN32_LEAN_AND_MEAN
7
+ #endif /* WIN32_LEAN_AND_MEAN */
8
+ #ifndef WIN32
9
+ #define WIN32
10
+ #endif /* WIN32 */
11
+ #include <winsock2.h>
12
+ #include <ws2tcpip.h>
13
+ #include <windows.h>
14
+ #endif
15
+
4
16
  #include <stdlib.h>
5
17
  #include <string.h>
6
18
  #include <assert.h>
@@ -28,35 +28,24 @@ static int has_attributes(xmlTextReaderPtr reader)
28
28
  static void Nokogiri_xml_node_namespaces(xmlNodePtr node, VALUE attr_hash)
29
29
  {
30
30
  xmlNsPtr ns;
31
- static char buffer[XMLNS_BUFFER_LEN] ;
32
- char *key ;
33
- size_t keylen ;
31
+ VALUE key;
34
32
 
35
33
  if (node->type != XML_ELEMENT_NODE) return ;
36
34
 
37
35
  ns = node->nsDef;
38
36
  while (ns != NULL) {
39
37
 
40
- keylen = XMLNS_PREFIX_LEN + (ns->prefix ? (strlen((const char*)ns->prefix) + 1) : 0) ;
41
- if (keylen > XMLNS_BUFFER_LEN) {
42
- key = (char*)malloc(keylen) ;
43
- } else {
44
- key = buffer ;
45
- }
46
-
38
+ key = rb_enc_str_new_cstr(XMLNS_PREFIX, rb_utf8_encoding());
47
39
  if (ns->prefix) {
48
- sprintf(key, "%s:%s", XMLNS_PREFIX, ns->prefix);
49
- } else {
50
- sprintf(key, "%s", XMLNS_PREFIX);
40
+ rb_str_cat_cstr(key, ":");
41
+ rb_str_cat_cstr(key, (const char*)ns->prefix);
51
42
  }
52
43
 
44
+ key = rb_str_conv_enc(key, rb_utf8_encoding(), rb_default_internal_encoding());
53
45
  rb_hash_aset(attr_hash,
54
- NOKOGIRI_STR_NEW2(key),
46
+ key,
55
47
  (ns->href ? NOKOGIRI_STR_NEW2(ns->href) : Qnil)
56
48
  );
57
- if (key != buffer) {
58
- free(key);
59
- }
60
49
  ns = ns->next ;
61
50
  }
62
51
  }
@@ -133,6 +133,31 @@ static VALUE read_memory(VALUE klass, VALUE content)
133
133
  return rb_schema;
134
134
  }
135
135
 
136
+ /* Schema creation will remove and deallocate "blank" nodes.
137
+ * If those blank nodes have been exposed to Ruby, they could get freed
138
+ * out from under the VALUE pointer. This function checks to see if any of
139
+ * those nodes have been exposed to Ruby, and if so we should raise an exception.
140
+ */
141
+ static int has_blank_nodes_p(VALUE cache)
142
+ {
143
+ long i;
144
+
145
+ if (NIL_P(cache)) {
146
+ return 0;
147
+ }
148
+
149
+ for (i = 0; i < RARRAY_LEN(cache); i++) {
150
+ xmlNodePtr node;
151
+ VALUE element = rb_ary_entry(cache, i);
152
+ Data_Get_Struct(element, xmlNode, node);
153
+ if (xmlIsBlankNode(node)) {
154
+ return 1;
155
+ }
156
+ }
157
+
158
+ return 0;
159
+ }
160
+
136
161
  /*
137
162
  * call-seq:
138
163
  * from_document(doc)
@@ -152,6 +177,10 @@ static VALUE from_document(VALUE klass, VALUE document)
152
177
  /* In case someone passes us a node. ugh. */
153
178
  doc = doc->doc;
154
179
 
180
+ if (has_blank_nodes_p(DOC_NODE_CACHE(doc))) {
181
+ rb_raise(rb_eArgError, "Creating a schema from a document that has blank nodes exposed to Ruby is dangerous");
182
+ }
183
+
155
184
  ctx = xmlSchemaNewDocParserCtxt(doc);
156
185
 
157
186
  errors = rb_ary_new();
@@ -1,64 +1,66 @@
1
1
  # frozen_string_literal: true
2
- require 'thread'
2
+ require "thread"
3
3
 
4
4
  module Nokogiri
5
5
  module CSS
6
6
  class Parser < Racc::Parser
7
- @cache_on = true
8
- @cache = {}
9
- @mutex = Mutex.new
7
+ CACHE_SWITCH_NAME = :nokogiri_css_parser_cache_is_off
8
+
9
+ @cache = {}
10
+ @mutex = Mutex.new
10
11
 
11
12
  class << self
12
- # Turn on CSS parse caching
13
- attr_accessor :cache_on
14
- alias :cache_on? :cache_on
15
- alias :set_cache :cache_on=
13
+ # Return a thread-local boolean indicating whether the CSS-to-XPath cache is active. (Default is `true`.)
14
+ def cache_on?
15
+ !Thread.current[CACHE_SWITCH_NAME]
16
+ end
17
+
18
+ # Set a thread-local boolean to turn cacheing on and off. Truthy values turn the cache on, falsey values turn the cache off.
19
+ def set_cache(value)
20
+ Thread.current[CACHE_SWITCH_NAME] = !value
21
+ end
16
22
 
17
23
  # Get the css selector in +string+ from the cache
18
- def [] string
19
- return unless @cache_on
24
+ def [](string)
25
+ return unless cache_on?
20
26
  @mutex.synchronize { @cache[string] }
21
27
  end
22
28
 
23
29
  # Set the css selector in +string+ in the cache to +value+
24
- def []= string, value
25
- return value unless @cache_on
30
+ def []=(string, value)
31
+ return value unless cache_on?
26
32
  @mutex.synchronize { @cache[string] = value }
27
33
  end
28
34
 
29
35
  # Clear the cache
30
- def clear_cache
31
- @mutex.synchronize { @cache = {} }
36
+ def clear_cache(create_new_object = false)
37
+ @mutex.synchronize do
38
+ if create_new_object
39
+ @cache = {}
40
+ else
41
+ @cache.clear
42
+ end
43
+ end
32
44
  end
33
45
 
34
46
  # Execute +block+ without cache
35
- def without_cache &block
36
- tmp = @cache_on
37
- @cache_on = false
47
+ def without_cache(&block)
48
+ original_cache_setting = cache_on?
49
+ set_cache false
38
50
  block.call
39
- @cache_on = tmp
40
- end
41
-
42
- ###
43
- # Parse this CSS selector in +selector+. Returns an AST.
44
- def parse selector
45
- @warned ||= false
46
- unless @warned
47
- $stderr.puts('Nokogiri::CSS::Parser.parse is deprecated, call Nokogiri::CSS.parse(), this will be removed August 1st or version 1.4.0 (whichever is first)')
48
- @warned = true
49
- end
50
- new.parse selector
51
+ ensure
52
+ set_cache original_cache_setting
51
53
  end
52
54
  end
53
55
 
54
56
  # Create a new CSS parser with respect to +namespaces+
55
- def initialize namespaces = {}
56
- @tokenizer = Tokenizer.new
57
+ def initialize(namespaces = {})
58
+ @tokenizer = Tokenizer.new
57
59
  @namespaces = namespaces
58
60
  super()
59
61
  end
60
62
 
61
- def parse string
63
+ def parse(string)
62
64
  @tokenizer.scan_setup string
63
65
  do_parse
64
66
  end
@@ -68,14 +70,14 @@ module Nokogiri
68
70
  end
69
71
 
70
72
  # Get the xpath for +string+ using +options+
71
- def xpath_for string, options={}
73
+ def xpath_for(string, options = {})
72
74
  key = "#{string}#{options[:ns]}#{options[:prefix]}"
73
75
  v = self.class[key]
74
76
  return v if v
75
77
 
76
78
  args = [
77
- options[:prefix] || '//',
78
- options[:visitor] || XPathVisitor.new
79
+ options[:prefix] || "//",
80
+ options[:visitor] || XPathVisitor.new,
79
81
  ]
80
82
  self.class[key] = parse(string).map { |ast|
81
83
  ast.to_xpath(*args)
@@ -83,7 +85,7 @@ module Nokogiri
83
85
  end
84
86
 
85
87
  # On CSS parser error, raise an exception
86
- def on_error error_token_id, error_value, value_stack
88
+ def on_error(error_token_id, error_value, value_stack)
87
89
  after = value_stack.compact.last
88
90
  raise SyntaxError.new("unexpected '#{error_value}' after '#{after}'")
89
91
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Nokogiri
3
3
  # The version of Nokogiri you are using
4
- VERSION = "1.11.0.rc1"
4
+ VERSION = "1.11.0.rc2"
5
5
 
6
6
  class VersionInfo # :nodoc:
7
7
  def jruby?
@@ -133,7 +133,7 @@ module Nokogiri
133
133
  VersionInfo.instance.jruby?
134
134
  end
135
135
 
136
- # Ensure constants used in this file are loaded
136
+ # Ensure constants used in this file are loaded - see #1896
137
137
  if Nokogiri.jruby?
138
138
  require "nokogiri/jruby/dependencies"
139
139
  end
@@ -1,7 +1,7 @@
1
1
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
- require 'stringio'
4
- require 'nokogiri/xml/node/save_options'
3
+ require "stringio"
4
+ require "nokogiri/xml/node/save_options"
5
5
 
6
6
  module Nokogiri
7
7
  module XML
@@ -57,49 +57,49 @@ module Nokogiri
57
57
  include Enumerable
58
58
 
59
59
  # Element node type, see Nokogiri::XML::Node#element?
60
- ELEMENT_NODE = 1
60
+ ELEMENT_NODE = 1
61
61
  # Attribute node type
62
- ATTRIBUTE_NODE = 2
62
+ ATTRIBUTE_NODE = 2
63
63
  # Text node type, see Nokogiri::XML::Node#text?
64
- TEXT_NODE = 3
64
+ TEXT_NODE = 3
65
65
  # CDATA node type, see Nokogiri::XML::Node#cdata?
66
66
  CDATA_SECTION_NODE = 4
67
67
  # Entity reference node type
68
- ENTITY_REF_NODE = 5
68
+ ENTITY_REF_NODE = 5
69
69
  # Entity node type
70
- ENTITY_NODE = 6
70
+ ENTITY_NODE = 6
71
71
  # PI node type
72
- PI_NODE = 7
72
+ PI_NODE = 7
73
73
  # Comment node type, see Nokogiri::XML::Node#comment?
74
- COMMENT_NODE = 8
74
+ COMMENT_NODE = 8
75
75
  # Document node type, see Nokogiri::XML::Node#xml?
76
- DOCUMENT_NODE = 9
76
+ DOCUMENT_NODE = 9
77
77
  # Document type node type
78
78
  DOCUMENT_TYPE_NODE = 10
79
79
  # Document fragment node type
80
80
  DOCUMENT_FRAG_NODE = 11
81
81
  # Notation node type
82
- NOTATION_NODE = 12
82
+ NOTATION_NODE = 12
83
83
  # HTML document node type, see Nokogiri::XML::Node#html?
84
84
  HTML_DOCUMENT_NODE = 13
85
85
  # DTD node type
86
- DTD_NODE = 14
86
+ DTD_NODE = 14
87
87
  # Element declaration type
88
- ELEMENT_DECL = 15
88
+ ELEMENT_DECL = 15
89
89
  # Attribute declaration type
90
- ATTRIBUTE_DECL = 16
90
+ ATTRIBUTE_DECL = 16
91
91
  # Entity declaration type
92
- ENTITY_DECL = 17
92
+ ENTITY_DECL = 17
93
93
  # Namespace declaration type
94
- NAMESPACE_DECL = 18
94
+ NAMESPACE_DECL = 18
95
95
  # XInclude start type
96
- XINCLUDE_START = 19
96
+ XINCLUDE_START = 19
97
97
  # XInclude end type
98
- XINCLUDE_END = 20
98
+ XINCLUDE_END = 20
99
99
  # DOCB document node type
100
100
  DOCB_DOCUMENT_NODE = 21
101
101
 
102
- def initialize name, document # :nodoc:
102
+ def initialize(name, document) # :nodoc:
103
103
  # ... Ya. This is empty on purpose.
104
104
  end
105
105
 
@@ -109,24 +109,18 @@ module Nokogiri
109
109
  document.decorate(self)
110
110
  end
111
111
 
112
+ # @!group Searching via XPath or CSS Queries
113
+
112
114
  ###
113
115
  # Search this node's immediate children using CSS selector +selector+
114
- def > selector
116
+ def >(selector)
115
117
  ns = document.root.namespaces
116
118
  xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
117
119
  end
118
120
 
119
- ###
120
- # Get the attribute value for the attribute +name+
121
- def [] name
122
- get(name.to_s)
123
- end
121
+ # @!endgroup
124
122
 
125
- ###
126
- # Set the attribute value for the attribute +name+ to +value+
127
- def []= name, value
128
- set name.to_s, value.to_s
129
- end
123
+ # @!group Manipulating Document Structure
130
124
 
131
125
  ###
132
126
  # Add +node_or_tags+ as a child of this Node.
@@ -135,7 +129,7 @@ module Nokogiri
135
129
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
136
130
  #
137
131
  # Also see related method +<<+.
138
- def add_child node_or_tags
132
+ def add_child(node_or_tags)
139
133
  node_or_tags = coerce(node_or_tags)
140
134
  if node_or_tags.is_a?(XML::NodeSet)
141
135
  node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
@@ -152,7 +146,7 @@ module Nokogiri
152
146
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
153
147
  #
154
148
  # Also see related method +add_child+.
155
- def prepend_child node_or_tags
149
+ def prepend_child(node_or_tags)
156
150
  if first = children.first
157
151
  # Mimic the error add_child would raise.
158
152
  raise RuntimeError, "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -162,7 +156,6 @@ module Nokogiri
162
156
  end
163
157
  end
164
158
 
165
-
166
159
  ###
167
160
  # Add html around this node
168
161
  #
@@ -181,7 +174,7 @@ module Nokogiri
181
174
  # Returns self, to support chaining of calls (e.g., root << child1 << child2)
182
175
  #
183
176
  # Also see related method +add_child+.
184
- def << node_or_tags
177
+ def <<(node_or_tags)
185
178
  add_child node_or_tags
186
179
  self
187
180
  end
@@ -193,7 +186,7 @@ module Nokogiri
193
186
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
194
187
  #
195
188
  # Also see related method +before+.
196
- def add_previous_sibling node_or_tags
189
+ def add_previous_sibling(node_or_tags)
197
190
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
198
191
 
199
192
  add_sibling :previous, node_or_tags
@@ -206,7 +199,7 @@ module Nokogiri
206
199
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
207
200
  #
208
201
  # Also see related method +after+.
209
- def add_next_sibling node_or_tags
202
+ def add_next_sibling(node_or_tags)
210
203
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
211
204
 
212
205
  add_sibling :next, node_or_tags
@@ -219,7 +212,7 @@ module Nokogiri
219
212
  # Returns self, to support chaining of calls.
220
213
  #
221
214
  # Also see related method +add_previous_sibling+.
222
- def before node_or_tags
215
+ def before(node_or_tags)
223
216
  add_previous_sibling node_or_tags
224
217
  self
225
218
  end
@@ -231,7 +224,7 @@ module Nokogiri
231
224
  # Returns self, to support chaining of calls.
232
225
  #
233
226
  # Also see related method +add_next_sibling+.
234
- def after node_or_tags
227
+ def after(node_or_tags)
235
228
  add_next_sibling node_or_tags
236
229
  self
237
230
  end
@@ -243,7 +236,7 @@ module Nokogiri
243
236
  # Returns self.
244
237
  #
245
238
  # Also see related method +children=+
246
- def inner_html= node_or_tags
239
+ def inner_html=(node_or_tags)
247
240
  self.children = node_or_tags
248
241
  self
249
242
  end
@@ -255,7 +248,7 @@ module Nokogiri
255
248
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
256
249
  #
257
250
  # Also see related method +inner_html=+
258
- def children= node_or_tags
251
+ def children=(node_or_tags)
259
252
  node_or_tags = coerce(node_or_tags)
260
253
  children.unlink
261
254
  if node_or_tags.is_a?(XML::NodeSet)
@@ -273,13 +266,13 @@ module Nokogiri
273
266
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
274
267
  #
275
268
  # Also see related method +swap+.
276
- def replace node_or_tags
269
+ def replace(node_or_tags)
277
270
  # We cannot replace a text node directly, otherwise libxml will return
278
271
  # an internal error at parser.c:13031, I don't know exactly why
279
272
  # libxml is trying to find a parent node that is an element or document
280
273
  # so I can't tell if this is bug in libxml or not. issue #775.
281
274
  if text?
282
- replacee = Nokogiri::XML::Node.new 'dummy', document
275
+ replacee = Nokogiri::XML::Node.new "dummy", document
283
276
  add_previous_sibling_node replacee
284
277
  unlink
285
278
  return replacee.replace node_or_tags
@@ -303,33 +296,98 @@ module Nokogiri
303
296
  # Returns self, to support chaining of calls.
304
297
  #
305
298
  # Also see related method +replace+.
306
- def swap node_or_tags
299
+ def swap(node_or_tags)
307
300
  replace node_or_tags
308
301
  self
309
302
  end
310
303
 
311
- alias :next :next_sibling
312
- alias :previous :previous_sibling
304
+ ####
305
+ # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
306
+ def content=(string)
307
+ self.native_content = encode_special_chars(string.to_s)
308
+ end
313
309
 
314
- # :stopdoc:
315
- # HACK: This is to work around an RDoc bug
316
- alias :next= :add_next_sibling
317
- # :startdoc:
310
+ ###
311
+ # Set the parent Node for this Node
312
+ def parent=(parent_node)
313
+ parent_node.add_child(self)
314
+ parent_node
315
+ end
318
316
 
319
- alias :previous= :add_previous_sibling
320
- alias :remove :unlink
321
- alias :get_attribute :[]
322
- alias :attr :[]
323
- alias :set_attribute :[]=
324
- alias :text :content
325
- alias :inner_text :content
326
- alias :has_attribute? :key?
327
- alias :name :node_name
328
- alias :name= :node_name=
329
- alias :type :node_type
330
- alias :to_str :text
331
- alias :clone :dup
332
- alias :elements :element_children
317
+ ###
318
+ # Adds a default namespace supplied as a string +url+ href, to self.
319
+ # The consequence is as an xmlns attribute with supplied argument were
320
+ # present in parsed XML. A default namespace set with this method will
321
+ # now show up in #attributes, but when this node is serialized to XML an
322
+ # "xmlns" attribute will appear. See also #namespace and #namespace=
323
+ def default_namespace=(url)
324
+ add_namespace_definition(nil, url)
325
+ end
326
+
327
+ ###
328
+ # Set the default namespace on this node (as would be defined with an
329
+ # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
330
+ # a Namespace added this way will NOT be serialized as an xmlns attribute
331
+ # for this node. You probably want #default_namespace= instead, or perhaps
332
+ # #add_namespace_definition with a nil prefix argument.
333
+ def namespace=(ns)
334
+ return set_namespace(ns) unless ns
335
+
336
+ unless Nokogiri::XML::Namespace === ns
337
+ raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
338
+ end
339
+ if ns.document != document
340
+ raise ArgumentError, "namespace must be declared on the same document"
341
+ end
342
+
343
+ set_namespace ns
344
+ end
345
+
346
+ ###
347
+ # Do xinclude substitution on the subtree below node. If given a block, a
348
+ # Nokogiri::XML::ParseOptions object initialized from +options+, will be
349
+ # passed to it, allowing more convenient modification of the parser options.
350
+ def do_xinclude(options = XML::ParseOptions::DEFAULT_XML)
351
+ options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
352
+
353
+ # give options to user
354
+ yield options if block_given?
355
+
356
+ # call c extension
357
+ process_xincludes(options.to_i)
358
+ end
359
+
360
+ alias :next :next_sibling
361
+ alias :previous :previous_sibling
362
+ alias :next= :add_next_sibling
363
+ alias :previous= :add_previous_sibling
364
+ alias :remove :unlink
365
+ alias :name= :node_name=
366
+ alias :add_namespace :add_namespace_definition
367
+
368
+ # @!endgroup
369
+
370
+ alias :text :content
371
+ alias :inner_text :content
372
+ alias :name :node_name
373
+ alias :type :node_type
374
+ alias :to_str :text
375
+ alias :clone :dup
376
+ alias :elements :element_children
377
+
378
+ # @!group Working With Node Attributes
379
+
380
+ ###
381
+ # Get the attribute value for the attribute +name+
382
+ def [](name)
383
+ get(name.to_s)
384
+ end
385
+
386
+ ###
387
+ # Set the attribute value for the attribute +name+ to +value+
388
+ def []=(name, value)
389
+ set name.to_s, value.to_s
390
+ end
333
391
 
334
392
  ####
335
393
  # Returns a hash containing the node's attributes. The key is
@@ -370,82 +428,366 @@ module Nokogiri
370
428
  end
371
429
 
372
430
  ###
373
- # Get the list of class names of this Node, without
374
- # deduplication or sorting.
431
+ # Remove the attribute named +name+
432
+ def remove_attribute(name)
433
+ attr = attributes[name].remove if key? name
434
+ clear_xpath_context if Nokogiri.jruby?
435
+ attr
436
+ end
437
+
438
+ # Get the CSS class names of a Node.
439
+ #
440
+ # This is a convenience function and is equivalent to:
441
+ # node.kwattr_values("class")
442
+ #
443
+ # @see #kwattr_values
444
+ # @see #add_class
445
+ # @see #append_class
446
+ # @see #remove_class
447
+ #
448
+ # @return [Array<String>]
449
+ #
450
+ # The CSS classes present in the Node's +class+ attribute. If
451
+ # the attribute is empty or non-existent, the return value is
452
+ # an empty array.
453
+ #
454
+ # @example
455
+ # node # => <div class="section title header"></div>
456
+ # node.classes # => ["section", "title", "header"]
457
+ #
375
458
  def classes
376
- self['class'].to_s.scan(/\S+/)
459
+ kwattr_values("class")
377
460
  end
378
461
 
379
- ###
380
- # Add +name+ to the "class" attribute value of this Node and
381
- # return self. If the value is already in the current value, it
382
- # is not added. If no "class" attribute exists yet, one is
383
- # created with the given value.
462
+ # Ensure HTML CSS classes are present on a +Node+. Any CSS
463
+ # classes in +names+ that already exist in the +Node+'s +class+
464
+ # attribute are _not_ added. Note that any existing duplicates
465
+ # in the +class+ attribute are not removed. Compare with
466
+ # {#append_class}.
467
+ #
468
+ # This is a convenience function and is equivalent to:
469
+ # node.kwattr_add("class", names)
470
+ #
471
+ # @see #kwattr_add
472
+ # @see #classes
473
+ # @see #append_class
474
+ # @see #remove_class
475
+ #
476
+ # @param names [String, Array<String>]
477
+ #
478
+ # CSS class names to be added to the Node's +class+
479
+ # attribute. May be a string containing whitespace-delimited
480
+ # names, or an Array of String names. Any class names already
481
+ # present will not be added. Any class names not present will
482
+ # be added. If no +class+ attribute exists, one is created.
483
+ #
484
+ # @return [Node] Returns +self+ for ease of chaining method calls.
485
+ #
486
+ # @example Ensure that a +Node+ has CSS class "section"
487
+ # node # => <div></div>
488
+ # node.add_class("section") # => <div class="section"></div>
489
+ # node.add_class("section") # => <div class="section"></div> # duplicate not added
490
+ #
491
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via a String argument.
492
+ # node # => <div class="section section"></div>
493
+ # node.add_class("section header") # => <div class="section section header"></div>
494
+ # # Note that the CSS class "section" is not added because it is already present.
495
+ # # Note also that the pre-existing duplicate CSS class "section" is not removed.
496
+ #
497
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via an Array argument.
498
+ # node # => <div></div>
499
+ # node.add_class(["section", "header"]) # => <div class="section header"></div>
500
+ #
501
+ def add_class(names)
502
+ kwattr_add("class", names)
503
+ end
504
+
505
+ # Add HTML CSS classes to a +Node+, regardless of
506
+ # duplication. Compare with {#add_class}.
507
+ #
508
+ # This is a convenience function and is equivalent to:
509
+ # node.kwattr_append("class", names)
510
+ #
511
+ # @see #kwattr_append
512
+ # @see #classes
513
+ # @see #add_class
514
+ # @see #remove_class
515
+ #
516
+ # @param names [String, Array<String>]
517
+ #
518
+ # CSS class names to be appended to the Node's +class+
519
+ # attribute. May be a string containing whitespace-delimited
520
+ # names, or an Array of String names. All class names passed
521
+ # in will be appended to the +class+ attribute even if they
522
+ # are already present in the attribute value. If no +class+
523
+ # attribute exists, one is created.
524
+ #
525
+ # @return [Node] Returns +self+ for ease of chaining method calls.
526
+ #
527
+ # @example Append "section" to a +Node+'s CSS +class+ attriubute
528
+ # node # => <div></div>
529
+ # node.append_class("section") # => <div class="section"></div>
530
+ # node.append_class("section") # => <div class="section section"></div> # duplicate added!
531
+ #
532
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via a String argument.
533
+ # node # => <div class="section section"></div>
534
+ # node.append_class("section header") # => <div class="section section section header"></div>
535
+ # # Note that the CSS class "section" is appended even though it is already present.
536
+ #
537
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via an Array argument.
538
+ # node # => <div></div>
539
+ # node.append_class(["section", "header"]) # => <div class="section header"></div>
540
+ # node.append_class(["section", "header"]) # => <div class="section header section header"></div>
541
+ #
542
+ def append_class(names)
543
+ kwattr_append("class", names)
544
+ end
545
+
546
+ # Remove HTML CSS classes from a +Node+. Any CSS classes in +names+ that
547
+ # exist in the +Node+'s +class+ attribute are removed, including any
548
+ # multiple entries.
549
+ #
550
+ # If no CSS classes remain after this operation, or if +names+ is
551
+ # +nil+, the +class+ attribute is deleted from the node.
552
+ #
553
+ # This is a convenience function and is equivalent to:
554
+ # node.kwattr_remove("class", names)
555
+ #
556
+ # @see #kwattr_remove
557
+ # @see #classes
558
+ # @see #add_class
559
+ # @see #append_class
560
+ #
561
+ # @param names [String, Array<String>]
562
+ #
563
+ # CSS class names to be removed from the Node's +class+ attribute. May
564
+ # be a string containing whitespace-delimited names, or an Array of
565
+ # String names. Any class names already present will be removed. If no
566
+ # CSS classes remain, the +class+ attribute is deleted.
567
+ #
568
+ # @return [Node] Returns +self+ for ease of chaining method calls.
569
+ #
570
+ # @example
571
+ # node # => <div class="section header"></div>
572
+ # node.remove_class("section") # => <div class="header"></div>
573
+ # node.remove_class("header") # => <div></div> # attribute is deleted when empty
574
+ #
575
+ def remove_class(names = nil)
576
+ kwattr_remove("class", names)
577
+ end
578
+
579
+ # Retrieve values from a keyword attribute of a Node.
580
+ #
581
+ # A "keyword attribute" is a node attribute that contains a set
582
+ # of space-delimited values. Perhaps the most familiar example
583
+ # of this is the HTML +class+ attribute used to contain CSS
584
+ # classes. But other keyword attributes exist, for instance
585
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
586
+ #
587
+ # @see #classes
588
+ # @see #kwattr_add
589
+ # @see #kwattr_append
590
+ # @see #kwattr_remove
591
+ #
592
+ # @param attribute_name [String] The name of the keyword attribute to be inspected.
593
+ #
594
+ # @return [Array<String>]
595
+ #
596
+ # The values present in the Node's +attribute_name+
597
+ # attribute. If the attribute is empty or non-existent, the
598
+ # return value is an empty array.
384
599
  #
385
- # More than one class may be added at a time, separated by a
386
- # space.
387
- def add_class name
388
- names = classes
389
- self['class'] = (names + (name.scan(/\S+/) - names)).join(' ')
600
+ # @example
601
+ # node # => <a rel="nofollow noopener external">link</a>
602
+ # node.kwattr_values("rel") # => ["nofollow", "noopener", "external"]
603
+ #
604
+ # @since v1.11.0
605
+ #
606
+ def kwattr_values(attribute_name)
607
+ keywordify(get_attribute(attribute_name) || [])
608
+ end
609
+
610
+ # Ensure that values are present in a keyword attribute.
611
+ #
612
+ # Any values in +keywords+ that already exist in the +Node+'s
613
+ # attribute values are _not_ added. Note that any existing
614
+ # duplicates in the attribute values are not removed. Compare
615
+ # with {#kwattr_append}.
616
+ #
617
+ # A "keyword attribute" is a node attribute that contains a set
618
+ # of space-delimited values. Perhaps the most familiar example
619
+ # of this is the HTML +class+ attribute used to contain CSS
620
+ # classes. But other keyword attributes exist, for instance
621
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
622
+ #
623
+ # @see #add_class
624
+ # @see #kwattr_values
625
+ # @see #kwattr_append
626
+ # @see #kwattr_remove
627
+ #
628
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
629
+ #
630
+ # @param keywords [String, Array<String>]
631
+ #
632
+ # Keywords to be added to the attribute named
633
+ # +attribute_name+. May be a string containing
634
+ # whitespace-delimited values, or an Array of String
635
+ # values. Any values already present will not be added. Any
636
+ # values not present will be added. If the named attribute
637
+ # does not exist, it is created.
638
+ #
639
+ # @return [Node] Returns +self+ for ease of chaining method calls.
640
+ #
641
+ # @example Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
642
+ # node # => <a></a>
643
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
644
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a> # duplicate not added
645
+ #
646
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a String argument.
647
+ # node # => <a rel="nofollow nofollow"></a>
648
+ # node.kwattr_add("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
649
+ # # Note that "nofollow" is not added because it is already present.
650
+ # # Note also that the pre-existing duplicate "nofollow" is not removed.
651
+ #
652
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via an Array argument.
653
+ # node # => <a></a>
654
+ # node.kwattr_add("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
655
+ #
656
+ # @since v1.11.0
657
+ #
658
+ def kwattr_add(attribute_name, keywords)
659
+ keywords = keywordify(keywords)
660
+ current_kws = kwattr_values(attribute_name)
661
+ new_kws = (current_kws + (keywords - current_kws)).join(" ")
662
+ set_attribute(attribute_name, new_kws)
390
663
  self
391
664
  end
392
665
 
393
- ###
394
- # Append +name+ to the "class" attribute value of this Node and
395
- # return self. The value is simply appended without checking if
396
- # it is already in the current value. If no "class" attribute
397
- # exists yet, one is created with the given value.
666
+ # Add keywords to a Node's keyword attribute, regardless of
667
+ # duplication. Compare with {#kwattr_add}.
668
+ #
669
+ # A "keyword attribute" is a node attribute that contains a set
670
+ # of space-delimited values. Perhaps the most familiar example
671
+ # of this is the HTML +class+ attribute used to contain CSS
672
+ # classes. But other keyword attributes exist, for instance
673
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
674
+ #
675
+ # @see #append_class
676
+ # @see #kwattr_values
677
+ # @see #kwattr_add
678
+ # @see #kwattr_remove
679
+ #
680
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
681
+ #
682
+ # @param keywords [String, Array<String>]
683
+ #
684
+ # Keywords to be added to the attribute named
685
+ # +attribute_name+. May be a string containing
686
+ # whitespace-delimited values, or an Array of String
687
+ # values. All values passed in will be appended to the named
688
+ # attribute even if they are already present in the
689
+ # attribute. If the named attribute does not exist, it is
690
+ # created.
691
+ #
692
+ # @return [Node] Returns +self+ for ease of chaining method calls.
693
+ #
694
+ # @example Append "nofollow" to the +rel+ attribute.
695
+ # node # => <a></a>
696
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow"></a>
697
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a> # duplicate added!
398
698
  #
399
- # More than one class may be appended at a time, separated by a
400
- # space.
401
- def append_class name
402
- self['class'] = (classes + name.scan(/\S+/)).join(' ')
699
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
700
+ # node # => <a rel="nofollow"></a>
701
+ # node.kwattr_append("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
702
+ # # Note that "nofollow" is appended even though it is already present.
703
+ #
704
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
705
+ # node # => <a></a>
706
+ # node.kwattr_append("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
707
+ #
708
+ # @since v1.11.0
709
+ #
710
+ def kwattr_append(attribute_name, keywords)
711
+ keywords = keywordify(keywords)
712
+ current_kws = kwattr_values(attribute_name)
713
+ new_kws = (current_kws + keywords).join(" ")
714
+ set_attribute(attribute_name, new_kws)
403
715
  self
404
716
  end
405
717
 
406
- ###
407
- # Remove +name+ from the "class" attribute value of this Node
408
- # and return self. If there are many occurrences of the name,
409
- # they are all removed.
718
+ # Remove keywords from a keyword attribute. Any matching
719
+ # keywords that exist in the named attribute are removed,
720
+ # including any multiple entries.
410
721
  #
411
- # More than one class may be removed at a time, separated by a
412
- # space.
722
+ # If no keywords remain after this operation, or if +keywords+
723
+ # is +nil+, the attribute is deleted from the node.
413
724
  #
414
- # If no class name is left after removal, or when +name+ is nil,
415
- # the "class" attribute is removed from this Node.
416
- def remove_class name = nil
417
- if name
418
- names = classes - name.scan(/\S+/)
419
- if names.empty?
420
- delete 'class'
421
- else
422
- self['class'] = names.join(' ')
423
- end
725
+ # A "keyword attribute" is a node attribute that contains a set
726
+ # of space-delimited values. Perhaps the most familiar example
727
+ # of this is the HTML +class+ attribute used to contain CSS
728
+ # classes. But other keyword attributes exist, for instance
729
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
730
+ #
731
+ # @see #remove_class
732
+ # @see #kwattr_values
733
+ # @see #kwattr_add
734
+ # @see #kwattr_append
735
+ #
736
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
737
+ #
738
+ # @param keywords [String, Array<String>]
739
+ #
740
+ # Keywords to be removed from the attribute named
741
+ # +attribute_name+. May be a string containing
742
+ # whitespace-delimited values, or an Array of String
743
+ # values. Any keywords present in the named attribute will be
744
+ # removed. If no keywords remain, or if +keywords+ is nil, the
745
+ # attribute is deleted.
746
+ #
747
+ # @return [Node] Returns +self+ for ease of chaining method calls.
748
+ #
749
+ # @example
750
+ # node # => <a rel="nofollow noreferrer">link</a>
751
+ # node.kwattr_remove("rel", "nofollow") # => <a rel="noreferrer">link</a>
752
+ # node.kwattr_remove("rel", "noreferrer") # => <a>link</a> # attribute is deleted when empty
753
+ #
754
+ # @since v1.11.0
755
+ #
756
+ def kwattr_remove(attribute_name, keywords)
757
+ if keywords.nil?
758
+ remove_attribute(attribute_name)
759
+ return self
760
+ end
761
+
762
+ keywords = keywordify(keywords)
763
+ current_kws = kwattr_values(attribute_name)
764
+ new_kws = current_kws - keywords
765
+ if new_kws.empty?
766
+ remove_attribute(attribute_name)
424
767
  else
425
- delete "class"
768
+ set_attribute(attribute_name, new_kws.join(" "))
426
769
  end
427
770
  self
428
771
  end
429
772
 
430
- ###
431
- # Remove the attribute named +name+
432
- def remove_attribute name
433
- attr = attributes[name].remove if key? name
434
- clear_xpath_context if Nokogiri.jruby?
435
- attr
436
- end
437
773
  alias :delete :remove_attribute
774
+ alias :get_attribute :[]
775
+ alias :attr :[]
776
+ alias :set_attribute :[]=
777
+ alias :has_attribute? :key?
778
+
779
+ # @!endgroup
438
780
 
439
781
  ###
440
782
  # Returns true if this Node matches +selector+
441
- def matches? selector
783
+ def matches?(selector)
442
784
  ancestors.last.search(selector).include?(self)
443
785
  end
444
786
 
445
787
  ###
446
788
  # Create a DocumentFragment containing +tags+ that is relative to _this_
447
789
  # context node.
448
- def fragment tags
790
+ def fragment(tags)
449
791
  type = document.html? ? Nokogiri::HTML : Nokogiri::XML
450
792
  type::DocumentFragment.new(document, tags, self)
451
793
  end
@@ -454,7 +796,7 @@ module Nokogiri
454
796
  # Parse +string_or_io+ as a document fragment within the context of
455
797
  # *this* node. Returns a XML::NodeSet containing the nodes parsed from
456
798
  # +string_or_io+.
457
- def parse string_or_io, options = nil
799
+ def parse(string_or_io, options = nil)
458
800
  ##
459
801
  # When the current node is unparented and not an element node, use the
460
802
  # document as the parsing context instead. Otherwise, the in-context
@@ -488,19 +830,6 @@ module Nokogiri
488
830
  node_set
489
831
  end
490
832
 
491
- ####
492
- # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
493
- def content= string
494
- self.native_content = encode_special_chars(string.to_s)
495
- end
496
-
497
- ###
498
- # Set the parent Node for this Node
499
- def parent= parent_node
500
- parent_node.add_child(self)
501
- parent_node
502
- end
503
-
504
833
  ###
505
834
  # Returns a Hash of +{prefix => value}+ for all namespaces on this
506
835
  # node and its ancestors.
@@ -582,6 +911,7 @@ module Nokogiri
582
911
  def element?
583
912
  type == ELEMENT_NODE
584
913
  end
914
+
585
915
  alias :elem? :element?
586
916
 
587
917
  ###
@@ -592,7 +922,7 @@ module Nokogiri
592
922
  end
593
923
 
594
924
  # Get the inner_html for this node's Node#children
595
- def inner_html *args
925
+ def inner_html(*args)
596
926
  children.map { |x| x.to_html(*args) }.join
597
927
  end
598
928
 
@@ -600,13 +930,13 @@ module Nokogiri
600
930
  def css_path
601
931
  path.split(/\//).map { |part|
602
932
  part.length == 0 ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
603
- }.compact.join(' > ')
933
+ }.compact.join(" > ")
604
934
  end
605
935
 
606
936
  ###
607
937
  # Get a list of ancestor Node for this Node. If +selector+ is given,
608
938
  # the ancestors must match +selector+
609
- def ancestors selector = nil
939
+ def ancestors(selector = nil)
610
940
  return NodeSet.new(document) unless respond_to?(:parent)
611
941
  return NodeSet.new(document) unless parent
612
942
 
@@ -627,57 +957,38 @@ module Nokogiri
627
957
  })
628
958
  end
629
959
 
630
- ###
631
- # Adds a default namespace supplied as a string +url+ href, to self.
632
- # The consequence is as an xmlns attribute with supplied argument were
633
- # present in parsed XML. A default namespace set with this method will
634
- # now show up in #attributes, but when this node is serialized to XML an
635
- # "xmlns" attribute will appear. See also #namespace and #namespace=
636
- def default_namespace= url
637
- add_namespace_definition(nil, url)
638
- end
639
- alias :add_namespace :add_namespace_definition
640
-
641
- ###
642
- # Set the default namespace on this node (as would be defined with an
643
- # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
644
- # a Namespace added this way will NOT be serialized as an xmlns attribute
645
- # for this node. You probably want #default_namespace= instead, or perhaps
646
- # #add_namespace_definition with a nil prefix argument.
647
- def namespace= ns
648
- return set_namespace(ns) unless ns
649
-
650
- unless Nokogiri::XML::Namespace === ns
651
- raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
652
- end
653
- if ns.document != document
654
- raise ArgumentError, 'namespace must be declared on the same document'
655
- end
656
-
657
- set_namespace ns
658
- end
659
-
660
960
  ####
661
961
  # Yields self and all children to +block+ recursively.
662
- def traverse &block
663
- children.each{|j| j.traverse(&block) }
962
+ def traverse(&block)
963
+ children.each { |j| j.traverse(&block) }
664
964
  block.call(self)
665
965
  end
666
966
 
667
967
  ###
668
968
  # Accept a visitor. This method calls "visit" on +visitor+ with self.
669
- def accept visitor
969
+ def accept(visitor)
670
970
  visitor.visit(self)
671
971
  end
672
972
 
673
973
  ###
674
974
  # Test to see if this Node is equal to +other+
675
- def == other
975
+ def ==(other)
676
976
  return false unless other
677
977
  return false unless other.respond_to?(:pointer_id)
678
978
  pointer_id == other.pointer_id
679
979
  end
680
980
 
981
+ ###
982
+ # Compare two Node objects with respect to their Document. Nodes from
983
+ # different documents cannot be compared.
984
+ def <=>(other)
985
+ return nil unless other.is_a?(Nokogiri::XML::Node)
986
+ return nil unless document == other.document
987
+ compare other
988
+ end
989
+
990
+ # @!group Serialization and Generating Output
991
+
681
992
  ###
682
993
  # Serialize Node using +options+. Save options can also be set using a
683
994
  # block. See SaveOptions.
@@ -692,17 +1003,17 @@ module Nokogiri
692
1003
  # config.format.as_xml
693
1004
  # end
694
1005
  #
695
- def serialize *args, &block
1006
+ def serialize(*args, &block)
696
1007
  options = args.first.is_a?(Hash) ? args.shift : {
697
- :encoding => args[0],
698
- :save_with => args[1]
1008
+ :encoding => args[0],
1009
+ :save_with => args[1],
699
1010
  }
700
1011
 
701
1012
  encoding = options[:encoding] || document.encoding
702
1013
  options[:encoding] = encoding
703
1014
 
704
1015
  outstring = String.new
705
- outstring.force_encoding(Encoding.find(encoding || 'utf-8'))
1016
+ outstring.force_encoding(Encoding.find(encoding || "utf-8"))
706
1017
  io = StringIO.new(outstring)
707
1018
  write_to io, options, &block
708
1019
  io.string
@@ -715,7 +1026,7 @@ module Nokogiri
715
1026
  #
716
1027
  # See Node#write_to for a list of +options+. For formatted output,
717
1028
  # use Node#to_xhtml instead.
718
- def to_html options = {}
1029
+ def to_html(options = {})
719
1030
  to_format SaveOptions::DEFAULT_HTML, options
720
1031
  end
721
1032
 
@@ -725,7 +1036,7 @@ module Nokogiri
725
1036
  # doc.to_xml(:indent => 5, :encoding => 'UTF-8')
726
1037
  #
727
1038
  # See Node#write_to for a list of +options+
728
- def to_xml options = {}
1039
+ def to_xml(options = {})
729
1040
  options[:save_with] ||= SaveOptions::DEFAULT_XML
730
1041
  serialize(options)
731
1042
  end
@@ -736,7 +1047,7 @@ module Nokogiri
736
1047
  # doc.to_xhtml(:indent => 5, :encoding => 'UTF-8')
737
1048
  #
738
1049
  # See Node#write_to for a list of +options+
739
- def to_xhtml options = {}
1050
+ def to_xhtml(options = {})
740
1051
  to_format SaveOptions::DEFAULT_XHTML, options
741
1052
  end
742
1053
 
@@ -757,22 +1068,22 @@ module Nokogiri
757
1068
  #
758
1069
  # node.write_to(io, :indent_text => '-', :indent => 2)
759
1070
  #
760
- def write_to io, *options
761
- options = options.first.is_a?(Hash) ? options.shift : {}
762
- encoding = options[:encoding] || options[0]
1071
+ def write_to(io, *options)
1072
+ options = options.first.is_a?(Hash) ? options.shift : {}
1073
+ encoding = options[:encoding] || options[0]
763
1074
  if Nokogiri.jruby?
764
- save_options = options[:save_with] || options[1]
765
- indent_times = options[:indent] || 0
1075
+ save_options = options[:save_with] || options[1]
1076
+ indent_times = options[:indent] || 0
766
1077
  else
767
- save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
768
- indent_times = options[:indent] || 2
1078
+ save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
1079
+ indent_times = options[:indent] || 2
769
1080
  end
770
- indent_text = options[:indent_text] || ' '
1081
+ indent_text = options[:indent_text] || " "
771
1082
 
772
1083
  # Any string times 0 returns an empty string. Therefore, use the same
773
1084
  # string instead of generating a new empty string for every node with
774
1085
  # zero indentation.
775
- indentation = indent_times.zero? ? '' : (indent_text * indent_times)
1086
+ indentation = indent_times.zero? ? "" : (indent_text * indent_times)
776
1087
 
777
1088
  config = SaveOptions.new(save_options.to_i)
778
1089
  yield config if block_given?
@@ -784,7 +1095,7 @@ module Nokogiri
784
1095
  # Write Node as HTML to +io+ with +options+
785
1096
  #
786
1097
  # See Node#write_to for a list of +options+
787
- def write_html_to io, options = {}
1098
+ def write_html_to(io, options = {})
788
1099
  write_format_to SaveOptions::DEFAULT_HTML, io, options
789
1100
  end
790
1101
 
@@ -792,7 +1103,7 @@ module Nokogiri
792
1103
  # Write Node as XHTML to +io+ with +options+
793
1104
  #
794
1105
  # See Node#write_to for a list of +options+
795
- def write_xhtml_to io, options = {}
1106
+ def write_xhtml_to(io, options = {})
796
1107
  write_format_to SaveOptions::DEFAULT_XHTML, io, options
797
1108
  end
798
1109
 
@@ -802,35 +1113,12 @@ module Nokogiri
802
1113
  # doc.write_xml_to io, :encoding => 'UTF-8'
803
1114
  #
804
1115
  # See Node#write_to for a list of options
805
- def write_xml_to io, options = {}
1116
+ def write_xml_to(io, options = {})
806
1117
  options[:save_with] ||= SaveOptions::DEFAULT_XML
807
1118
  write_to io, options
808
1119
  end
809
1120
 
810
- ###
811
- # Compare two Node objects with respect to their Document. Nodes from
812
- # different documents cannot be compared.
813
- def <=> other
814
- return nil unless other.is_a?(Nokogiri::XML::Node)
815
- return nil unless document == other.document
816
- compare other
817
- end
818
-
819
- ###
820
- # Do xinclude substitution on the subtree below node. If given a block, a
821
- # Nokogiri::XML::ParseOptions object initialized from +options+, will be
822
- # passed to it, allowing more convenient modification of the parser options.
823
- def do_xinclude options = XML::ParseOptions::DEFAULT_XML
824
- options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
825
-
826
- # give options to user
827
- yield options if block_given?
828
-
829
- # call c extension
830
- process_xincludes(options.to_i)
831
- end
832
-
833
- def canonicalize(mode=XML::XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false)
1121
+ def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comments = false)
834
1122
  c14n_root = self
835
1123
  document.canonicalize(mode, inclusive_namespaces, with_comments) do |node, parent|
836
1124
  tn = node.is_a?(XML::Node) ? node : parent
@@ -838,16 +1126,29 @@ module Nokogiri
838
1126
  end
839
1127
  end
840
1128
 
1129
+ # @!endgroup
1130
+
841
1131
  private
842
1132
 
843
- def add_sibling next_or_previous, node_or_tags
1133
+ def keywordify(keywords)
1134
+ case keywords
1135
+ when Enumerable
1136
+ return keywords
1137
+ when String
1138
+ return keywords.scan(/\S+/)
1139
+ else
1140
+ raise ArgumentError.new("Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}")
1141
+ end
1142
+ end
1143
+
1144
+ def add_sibling(next_or_previous, node_or_tags)
844
1145
  impl = (next_or_previous == :next) ? :add_next_sibling_node : :add_previous_sibling_node
845
- iter = (next_or_previous == :next) ? :reverse_each : :each
1146
+ iter = (next_or_previous == :next) ? :reverse_each : :each
846
1147
 
847
1148
  node_or_tags = coerce node_or_tags
848
1149
  if node_or_tags.is_a?(XML::NodeSet)
849
1150
  if text?
850
- pivot = Nokogiri::XML::Node.new 'dummy', document
1151
+ pivot = Nokogiri::XML::Node.new "dummy", document
851
1152
  send impl, pivot
852
1153
  else
853
1154
  pivot = self
@@ -863,14 +1164,14 @@ module Nokogiri
863
1164
  USING_LIBXML_WITH_BROKEN_SERIALIZATION = Nokogiri.uses_libxml?("~> 2.6.0").freeze
864
1165
  private_constant :USING_LIBXML_WITH_BROKEN_SERIALIZATION
865
1166
 
866
- def to_format save_option, options
1167
+ def to_format(save_option, options)
867
1168
  return dump_html if USING_LIBXML_WITH_BROKEN_SERIALIZATION
868
1169
 
869
1170
  options[:save_with] = save_option unless options[:save_with]
870
1171
  serialize(options)
871
1172
  end
872
1173
 
873
- def write_format_to save_option, io, options
1174
+ def write_format_to(save_option, io, options)
874
1175
  return (io << dump_html) if USING_LIBXML_WITH_BROKEN_SERIALIZATION
875
1176
 
876
1177
  options[:save_with] ||= save_option
@@ -881,7 +1182,7 @@ module Nokogiri
881
1182
  [:name, :namespace, :attribute_nodes, :children]
882
1183
  end
883
1184
 
884
- def coerce data # :nodoc:
1185
+ def coerce(data)
885
1186
  case data
886
1187
  when XML::NodeSet
887
1188
  return data
@@ -902,9 +1203,9 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
902
1203
  end
903
1204
 
904
1205
  # @private
905
- IMPLIED_XPATH_CONTEXTS = [ './/'.freeze ].freeze # :nodoc:
1206
+ IMPLIED_XPATH_CONTEXTS = [".//".freeze].freeze
906
1207
 
907
- def add_child_node_and_reparent_attrs node # :nodoc:
1208
+ def add_child_node_and_reparent_attrs(node)
908
1209
  add_child_node node
909
1210
  node.attribute_nodes.find_all { |a| a.name =~ /:/ }.each do |attr_node|
910
1211
  attr_node.remove