nokogiri 1.12.5 → 1.14.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +41 -0
  3. data/LICENSE-DEPENDENCIES.md +830 -509
  4. data/LICENSE.md +1 -1
  5. data/README.md +23 -14
  6. data/bin/nokogiri +63 -50
  7. data/dependencies.yml +33 -66
  8. data/ext/nokogiri/extconf.rb +159 -63
  9. data/ext/nokogiri/gumbo.c +21 -11
  10. data/ext/nokogiri/html4_document.c +2 -2
  11. data/ext/nokogiri/html4_element_description.c +1 -1
  12. data/ext/nokogiri/html4_entity_lookup.c +2 -2
  13. data/ext/nokogiri/html4_sax_parser_context.c +3 -9
  14. data/ext/nokogiri/html4_sax_push_parser.c +1 -1
  15. data/ext/nokogiri/nokogiri.c +38 -51
  16. data/ext/nokogiri/nokogiri.h +26 -14
  17. data/ext/nokogiri/test_global_handlers.c +1 -1
  18. data/ext/nokogiri/xml_attr.c +3 -3
  19. data/ext/nokogiri/xml_attribute_decl.c +5 -5
  20. data/ext/nokogiri/xml_cdata.c +3 -3
  21. data/ext/nokogiri/xml_comment.c +1 -1
  22. data/ext/nokogiri/xml_document.c +53 -44
  23. data/ext/nokogiri/xml_document_fragment.c +1 -3
  24. data/ext/nokogiri/xml_dtd.c +11 -11
  25. data/ext/nokogiri/xml_element_content.c +3 -3
  26. data/ext/nokogiri/xml_element_decl.c +5 -5
  27. data/ext/nokogiri/xml_encoding_handler.c +28 -14
  28. data/ext/nokogiri/xml_entity_decl.c +6 -6
  29. data/ext/nokogiri/xml_entity_reference.c +1 -1
  30. data/ext/nokogiri/xml_namespace.c +80 -14
  31. data/ext/nokogiri/xml_node.c +982 -396
  32. data/ext/nokogiri/xml_node_set.c +4 -6
  33. data/ext/nokogiri/xml_processing_instruction.c +1 -1
  34. data/ext/nokogiri/xml_reader.c +133 -32
  35. data/ext/nokogiri/xml_relax_ng.c +1 -3
  36. data/ext/nokogiri/xml_sax_parser.c +23 -17
  37. data/ext/nokogiri/xml_sax_parser_context.c +11 -9
  38. data/ext/nokogiri/xml_sax_push_parser.c +1 -3
  39. data/ext/nokogiri/xml_schema.c +4 -6
  40. data/ext/nokogiri/xml_syntax_error.c +1 -1
  41. data/ext/nokogiri/xml_text.c +2 -2
  42. data/ext/nokogiri/xml_xpath_context.c +144 -114
  43. data/ext/nokogiri/xslt_stylesheet.c +122 -23
  44. data/gumbo-parser/Makefile +10 -0
  45. data/gumbo-parser/src/attribute.h +1 -1
  46. data/gumbo-parser/src/error.c +2 -2
  47. data/gumbo-parser/src/error.h +1 -1
  48. data/gumbo-parser/src/foreign_attrs.c +2 -2
  49. data/gumbo-parser/src/{gumbo.h → nokogiri_gumbo.h} +1 -0
  50. data/gumbo-parser/src/parser.c +8 -16
  51. data/gumbo-parser/src/replacement.h +1 -1
  52. data/gumbo-parser/src/string_buffer.h +1 -1
  53. data/gumbo-parser/src/string_piece.c +1 -1
  54. data/gumbo-parser/src/svg_attrs.c +2 -2
  55. data/gumbo-parser/src/svg_tags.c +2 -2
  56. data/gumbo-parser/src/tag.c +2 -1
  57. data/gumbo-parser/src/tag_lookup.c +7 -7
  58. data/gumbo-parser/src/tag_lookup.gperf +1 -0
  59. data/gumbo-parser/src/tag_lookup.h +1 -1
  60. data/gumbo-parser/src/token_buffer.h +1 -1
  61. data/gumbo-parser/src/tokenizer.c +1 -1
  62. data/gumbo-parser/src/tokenizer.h +1 -1
  63. data/gumbo-parser/src/utf8.c +1 -1
  64. data/gumbo-parser/src/utf8.h +1 -1
  65. data/gumbo-parser/src/util.c +1 -3
  66. data/gumbo-parser/src/util.h +4 -0
  67. data/gumbo-parser/src/vector.h +1 -1
  68. data/lib/nokogiri/class_resolver.rb +67 -0
  69. data/lib/nokogiri/css/node.rb +9 -8
  70. data/lib/nokogiri/css/parser.rb +360 -341
  71. data/lib/nokogiri/css/parser.y +249 -244
  72. data/lib/nokogiri/css/parser_extras.rb +22 -20
  73. data/lib/nokogiri/css/syntax_error.rb +1 -0
  74. data/lib/nokogiri/css/tokenizer.rb +4 -3
  75. data/lib/nokogiri/css/tokenizer.rex +3 -2
  76. data/lib/nokogiri/css/xpath_visitor.rb +184 -85
  77. data/lib/nokogiri/css.rb +44 -6
  78. data/lib/nokogiri/decorators/slop.rb +8 -7
  79. data/lib/nokogiri/encoding_handler.rb +57 -0
  80. data/lib/nokogiri/extension.rb +4 -3
  81. data/lib/nokogiri/gumbo.rb +1 -0
  82. data/lib/nokogiri/html.rb +16 -10
  83. data/lib/nokogiri/html4/builder.rb +1 -0
  84. data/lib/nokogiri/html4/document.rb +56 -164
  85. data/lib/nokogiri/html4/document_fragment.rb +11 -7
  86. data/lib/nokogiri/html4/element_description.rb +1 -0
  87. data/lib/nokogiri/html4/element_description_defaults.rb +432 -532
  88. data/lib/nokogiri/html4/encoding_reader.rb +121 -0
  89. data/lib/nokogiri/html4/entity_lookup.rb +2 -1
  90. data/lib/nokogiri/html4/sax/parser.rb +5 -2
  91. data/lib/nokogiri/html4/sax/parser_context.rb +1 -0
  92. data/lib/nokogiri/html4/sax/push_parser.rb +7 -7
  93. data/lib/nokogiri/html4.rb +12 -5
  94. data/lib/nokogiri/html5/document.rb +126 -32
  95. data/lib/nokogiri/html5/document_fragment.rb +14 -4
  96. data/lib/nokogiri/html5/node.rb +12 -7
  97. data/lib/nokogiri/html5.rb +138 -222
  98. data/lib/nokogiri/jruby/dependencies.rb +2 -19
  99. data/lib/nokogiri/jruby/nokogiri_jars.rb +43 -0
  100. data/lib/nokogiri/syntax_error.rb +1 -0
  101. data/lib/nokogiri/version/constant.rb +2 -1
  102. data/lib/nokogiri/version/info.rb +32 -24
  103. data/lib/nokogiri/version.rb +1 -0
  104. data/lib/nokogiri/xml/attr.rb +54 -3
  105. data/lib/nokogiri/xml/attribute_decl.rb +2 -1
  106. data/lib/nokogiri/xml/builder.rb +35 -33
  107. data/lib/nokogiri/xml/cdata.rb +2 -1
  108. data/lib/nokogiri/xml/character_data.rb +1 -0
  109. data/lib/nokogiri/xml/document.rb +232 -143
  110. data/lib/nokogiri/xml/document_fragment.rb +88 -42
  111. data/lib/nokogiri/xml/dtd.rb +3 -2
  112. data/lib/nokogiri/xml/element_content.rb +1 -0
  113. data/lib/nokogiri/xml/element_decl.rb +2 -1
  114. data/lib/nokogiri/xml/entity_decl.rb +3 -2
  115. data/lib/nokogiri/xml/entity_reference.rb +1 -0
  116. data/lib/nokogiri/xml/namespace.rb +44 -0
  117. data/lib/nokogiri/xml/node/save_options.rb +14 -8
  118. data/lib/nokogiri/xml/node.rb +708 -383
  119. data/lib/nokogiri/xml/node_set.rb +134 -59
  120. data/lib/nokogiri/xml/notation.rb +12 -0
  121. data/lib/nokogiri/xml/parse_options.rb +140 -56
  122. data/lib/nokogiri/xml/pp/character_data.rb +8 -6
  123. data/lib/nokogiri/xml/pp/node.rb +26 -26
  124. data/lib/nokogiri/xml/pp.rb +1 -0
  125. data/lib/nokogiri/xml/processing_instruction.rb +3 -1
  126. data/lib/nokogiri/xml/reader.rb +20 -24
  127. data/lib/nokogiri/xml/relax_ng.rb +1 -0
  128. data/lib/nokogiri/xml/sax/document.rb +20 -19
  129. data/lib/nokogiri/xml/sax/parser.rb +38 -36
  130. data/lib/nokogiri/xml/sax/parser_context.rb +7 -3
  131. data/lib/nokogiri/xml/sax/push_parser.rb +5 -5
  132. data/lib/nokogiri/xml/sax.rb +1 -0
  133. data/lib/nokogiri/xml/schema.rb +7 -6
  134. data/lib/nokogiri/xml/searchable.rb +93 -62
  135. data/lib/nokogiri/xml/syntax_error.rb +5 -4
  136. data/lib/nokogiri/xml/text.rb +1 -0
  137. data/lib/nokogiri/xml/xpath/syntax_error.rb +2 -1
  138. data/lib/nokogiri/xml/xpath.rb +12 -0
  139. data/lib/nokogiri/xml/xpath_context.rb +2 -3
  140. data/lib/nokogiri/xml.rb +4 -3
  141. data/lib/nokogiri/xslt/stylesheet.rb +1 -0
  142. data/lib/nokogiri/xslt.rb +21 -13
  143. data/lib/nokogiri.rb +22 -27
  144. data/lib/xsd/xmlparser/nokogiri.rb +28 -25
  145. data/patches/libxml2/0009-allow-wildcard-namespaces.patch +77 -0
  146. data/patches/libxslt/0001-update-automake-files-for-arm64.patch +2445 -1919
  147. data/ports/archives/libxml2-2.10.4.tar.xz +0 -0
  148. data/ports/archives/libxslt-1.1.37.tar.xz +0 -0
  149. metadata +20 -171
  150. data/patches/libxml2/0004-use-glibc-strlen.patch +0 -53
  151. data/patches/libxml2/0005-avoid-isnan-isinf.patch +0 -81
  152. data/patches/libxml2/0006-update-automake-files-for-arm64.patch +0 -2511
  153. data/patches/libxml2/0007-Fix-XPath-recursion-limit.patch +0 -31
  154. data/patches/libxslt/0002-Fix-xml2-config-check-in-configure-script.patch +0 -19
  155. data/ports/archives/libxml2-2.9.12.tar.gz +0 -0
  156. data/ports/archives/libxslt-1.1.34.tar.gz +0 -0
@@ -1,12 +1,15 @@
1
- # encoding: UTF-8
1
+ # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+
3
4
  require "stringio"
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 with XML and HTML
9
- # tags. A {Nokogiri::XML::Node} may be treated similarly to a hash with regard to attributes. For
8
+ # Nokogiri::XML::Node is the primary API you'll use to interact with your Document.
9
+ #
10
+ # == Attributes
11
+ #
12
+ # A Nokogiri::XML::Node may be treated similarly to a hash with regard to attributes. For
10
13
  # example:
11
14
  #
12
15
  # node = Nokogiri::XML::DocumentFragment.parse("<a href='#foo' id='link'>link</a>").at_css("a")
@@ -17,41 +20,52 @@ module Nokogiri
17
20
  # node['class'] = 'green' # => "green"
18
21
  # node.to_html # => "<a href=\"#foo\" id=\"link\" class=\"green\">link</a>"
19
22
  #
20
- # See the method group entitled "Working With Node Attributes" for the full set of methods.
23
+ # See the method group entitled Node@Working+With+Node+Attributes for the full set of methods.
24
+ #
25
+ # == Navigation
26
+ #
27
+ # Nokogiri::XML::Node also has methods that let you move around your tree:
21
28
  #
22
- # {Nokogiri::XML::Node} also has methods that let you move around your
23
- # tree. For navigating your tree, see:
29
+ # [#parent, #children, #next, #previous]
30
+ # Navigate up, down, or through siblings.
24
31
  #
25
- # * {#parent}
26
- # * {#children}
27
- # * {#next}
28
- # * {#previous}
32
+ # See the method group entitled Node@Traversing+Document+Structure for the full set of methods.
29
33
  #
30
- # When printing or otherwise emitting a document or a node (and
31
- # its subtree), there are a few methods you might want to use:
34
+ # == Serialization
32
35
  #
33
- # * {#content}, {#text}, {#inner_text}, {#to_str}: These methods will all <b>emit plaintext</b>,
34
- # meaning that entities will be replaced (e.g., "&lt;" will be replaced with "<"), meaning
36
+ # When printing or otherwise emitting a document or a node (and its subtree), there are a few
37
+ # methods you might want to use:
38
+ #
39
+ # [#content, #text, #inner_text, #to_str]
40
+ # These methods will all **emit plaintext**,
41
+ # meaning that entities will be replaced (e.g., +&lt;+ will be replaced with +<+), meaning
35
42
  # that any sanitizing will likely be un-done in the output.
36
43
  #
37
- # * {#to_s}, {#to_xml}, {#to_html}, {#inner_html}: These methods will all <b>emit
38
- # properly-escaped markup</b>, meaning that it's suitable for consumption by browsers,
39
- # parsers, etc.
44
+ # [#to_s, #to_xml, #to_html, #inner_html]
45
+ # These methods will all **emit properly-escaped markup**, meaning that it's suitable for
46
+ # consumption by browsers, parsers, etc.
47
+ #
48
+ # See the method group entitled Node@Serialization+and+Generating+Output for the full set of methods.
49
+ #
50
+ # == Searching
40
51
  #
41
- # You may search this node's subtree using {#xpath} and {#css}
52
+ # You may search this node's subtree using methods like #xpath and #css.
53
+ #
54
+ # See the method group entitled Node@Searching+via+XPath+or+CSS+Queries for the full set of methods.
42
55
  #
43
56
  class Node
44
57
  include Nokogiri::XML::PP::Node
45
58
  include Nokogiri::XML::Searchable
59
+ include Nokogiri::ClassResolver
46
60
  include Enumerable
47
61
 
48
- # Element node type, see {Nokogiri::XML::Node#element?}
62
+ # Element node type, see Nokogiri::XML::Node#element?
49
63
  ELEMENT_NODE = 1
50
64
  # Attribute node type
51
65
  ATTRIBUTE_NODE = 2
52
- # Text node type, see {Nokogiri::XML::Node#text?}
66
+ # Text node type, see Nokogiri::XML::Node#text?
53
67
  TEXT_NODE = 3
54
- # CDATA node type, see {Nokogiri::XML::Node#cdata?}
68
+ # CDATA node type, see Nokogiri::XML::Node#cdata?
55
69
  CDATA_SECTION_NODE = 4
56
70
  # Entity reference node type
57
71
  ENTITY_REF_NODE = 5
@@ -59,9 +73,9 @@ module Nokogiri
59
73
  ENTITY_NODE = 6
60
74
  # PI node type
61
75
  PI_NODE = 7
62
- # Comment node type, see {Nokogiri::XML::Node#comment?}
76
+ # Comment node type, see Nokogiri::XML::Node#comment?
63
77
  COMMENT_NODE = 8
64
- # Document node type, see {Nokogiri::XML::Node#xml?}
78
+ # Document node type, see Nokogiri::XML::Node#xml?
65
79
  DOCUMENT_NODE = 9
66
80
  # Document type node type
67
81
  DOCUMENT_TYPE_NODE = 10
@@ -69,7 +83,7 @@ module Nokogiri
69
83
  DOCUMENT_FRAG_NODE = 11
70
84
  # Notation node type
71
85
  NOTATION_NODE = 12
72
- # HTML document node type, see {Nokogiri::XML::Node#html?}
86
+ # HTML document node type, see Nokogiri::XML::Node#html?
73
87
  HTML_DOCUMENT_NODE = 13
74
88
  # DTD node type
75
89
  DTD_NODE = 14
@@ -88,15 +102,29 @@ module Nokogiri
88
102
  # DOCB document node type
89
103
  DOCB_DOCUMENT_NODE = 21
90
104
 
91
- ##
92
- # Create a new node with +name+ sharing GC lifecycle with +document+.
93
- # @param name [String]
94
- # @param document [Nokogiri::XML::Document]
95
- # @yieldparam node [Nokogiri::XML::Node]
96
- # @return [Nokogiri::XML::Node]
97
- # @see Nokogiri::XML::Node.new
105
+ #
106
+ # :call-seq:
107
+ # new(name, document) -> Nokogiri::XML::Node
108
+ # new(name, document) { |node| ... } -> Nokogiri::XML::Node
109
+ #
110
+ # Create a new node with +name+ that belongs to +document+.
111
+ #
112
+ # If you intend to add a node to a document tree, it's likely that you will prefer one of the
113
+ # Nokogiri::XML::Node methods like #add_child, #add_next_sibling, #replace, etc. which will
114
+ # both create an element (or subtree) and place it in the document tree.
115
+ #
116
+ # Another alternative, if you are concerned about performance, is
117
+ # Nokogiri::XML::Document#create_element which accepts additional arguments for contents or
118
+ # attributes but (like this method) avoids parsing markup.
119
+ #
120
+ # [Parameters]
121
+ # - +name+ (String)
122
+ # - +document+ (Nokogiri::XML::Document) The document to which the the returned node will belong.
123
+ # [Yields] Nokogiri::XML::Node
124
+ # [Returns] Nokogiri::XML::Node
125
+ #
98
126
  def initialize(name, document)
99
- # This is intentionally empty.
127
+ # This is intentionally empty, and sets the method signature for subclasses.
100
128
  end
101
129
 
102
130
  ###
@@ -105,161 +133,237 @@ module Nokogiri
105
133
  document.decorate(self)
106
134
  end
107
135
 
108
- # @!group Searching via XPath or CSS Queries
109
-
110
- ###
111
- # Search this node's immediate children using CSS selector +selector+
112
- def >(selector)
113
- ns = document.root.namespaces
114
- xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
115
- end
116
-
117
- # @!endgroup
118
-
119
- # @!group Manipulating Document Structure
136
+ # :section: Manipulating Document Structure
120
137
 
121
138
  ###
122
139
  # Add +node_or_tags+ as a child of this Node.
123
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
124
140
  #
125
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
141
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
142
+ # containing markup.
143
+ #
144
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
145
+ # a DocumentFragment, NodeSet, or String).
126
146
  #
127
147
  # Also see related method +<<+.
128
148
  def add_child(node_or_tags)
129
149
  node_or_tags = coerce(node_or_tags)
130
150
  if node_or_tags.is_a?(XML::NodeSet)
131
- node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
151
+ node_or_tags.each { |n| add_child_node_and_reparent_attrs(n) }
132
152
  else
133
- add_child_node_and_reparent_attrs node_or_tags
153
+ add_child_node_and_reparent_attrs(node_or_tags)
134
154
  end
135
155
  node_or_tags
136
156
  end
137
157
 
138
158
  ###
139
159
  # Add +node_or_tags+ as the first child of this Node.
140
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
141
160
  #
142
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
161
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
162
+ # containing markup.
163
+ #
164
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
165
+ # a DocumentFragment, NodeSet, or String).
143
166
  #
144
167
  # Also see related method +add_child+.
145
168
  def prepend_child(node_or_tags)
146
- if first = children.first
169
+ if (first = children.first)
147
170
  # Mimic the error add_child would raise.
148
- raise RuntimeError, "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
171
+ raise "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
172
+
149
173
  first.__send__(:add_sibling, :previous, node_or_tags)
150
174
  else
151
175
  add_child(node_or_tags)
152
176
  end
153
177
  end
154
178
 
155
- ###
156
- # Add html around this node
179
+ # :call-seq:
180
+ # wrap(markup) -> self
181
+ # wrap(node) -> self
182
+ #
183
+ # Wrap this Node with the node parsed from +markup+ or a dup of the +node+.
184
+ #
185
+ # [Parameters]
186
+ # - *markup* (String)
187
+ # Markup that is parsed and used as the wrapper. This node's parent, if it exists, is used
188
+ # as the context node for parsing; otherwise the associated document is used. If the parsed
189
+ # fragment has multiple roots, the first root node is used as the wrapper.
190
+ # - *node* (Nokogiri::XML::Node)
191
+ # An element that is `#dup`ed and used as the wrapper.
192
+ #
193
+ # [Returns] +self+, to support chaining.
194
+ #
195
+ # Also see NodeSet#wrap
196
+ #
197
+ # *Example* with a +String+ argument:
198
+ #
199
+ # doc = Nokogiri::HTML5(<<~HTML)
200
+ # <html><body>
201
+ # <a>asdf</a>
202
+ # </body></html>
203
+ # HTML
204
+ # doc.at_css("a").wrap("<div></div>")
205
+ # doc.to_html
206
+ # # => <html><head></head><body>
207
+ # # <div><a>asdf</a></div>
208
+ # # </body></html>
209
+ #
210
+ # *Example* with a +Node+ argument:
211
+ #
212
+ # doc = Nokogiri::HTML5(<<~HTML)
213
+ # <html><body>
214
+ # <a>asdf</a>
215
+ # </body></html>
216
+ # HTML
217
+ # doc.at_css("a").wrap(doc.create_element("div"))
218
+ # doc.to_html
219
+ # # <html><head></head><body>
220
+ # # <div><a>asdf</a></div>
221
+ # # </body></html>
157
222
  #
158
- # Returns self
159
- def wrap(html)
160
- new_parent = document.parse(html).first
161
- add_next_sibling(new_parent)
223
+ def wrap(node_or_tags)
224
+ case node_or_tags
225
+ when String
226
+ context_node = parent || document
227
+ new_parent = context_node.coerce(node_or_tags).first
228
+ if new_parent.nil?
229
+ raise "Failed to parse '#{node_or_tags}' in the context of a '#{context_node.name}' element"
230
+ end
231
+ when XML::Node
232
+ new_parent = node_or_tags.dup
233
+ else
234
+ raise ArgumentError, "Requires a String or Node argument, and cannot accept a #{node_or_tags.class}"
235
+ end
236
+
237
+ if parent
238
+ add_next_sibling(new_parent)
239
+ else
240
+ new_parent.unlink
241
+ end
162
242
  new_parent.add_child(self)
243
+
163
244
  self
164
245
  end
165
246
 
166
247
  ###
167
248
  # Add +node_or_tags+ as a child of this Node.
168
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
169
249
  #
170
- # Returns self, to support chaining of calls (e.g., root << child1 << child2)
250
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
251
+ # containing markup.
252
+ #
253
+ # Returns +self+, to support chaining of calls (e.g., root << child1 << child2)
171
254
  #
172
255
  # Also see related method +add_child+.
173
256
  def <<(node_or_tags)
174
- add_child node_or_tags
257
+ add_child(node_or_tags)
175
258
  self
176
259
  end
177
260
 
178
261
  ###
179
262
  # Insert +node_or_tags+ before this Node (as a sibling).
180
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
181
263
  #
182
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
264
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
265
+ # containing markup.
266
+ #
267
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
268
+ # a DocumentFragment, NodeSet, or String).
183
269
  #
184
270
  # Also see related method +before+.
185
271
  def add_previous_sibling(node_or_tags)
186
- raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
272
+ raise ArgumentError,
273
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
187
274
 
188
- add_sibling :previous, node_or_tags
275
+ add_sibling(:previous, node_or_tags)
189
276
  end
190
277
 
191
278
  ###
192
279
  # Insert +node_or_tags+ after this Node (as a sibling).
193
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
194
280
  #
195
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
281
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
282
+ # containing markup.
283
+ #
284
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
285
+ # a DocumentFragment, NodeSet, or String).
196
286
  #
197
287
  # Also see related method +after+.
198
288
  def add_next_sibling(node_or_tags)
199
- raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
289
+ raise ArgumentError,
290
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
200
291
 
201
- add_sibling :next, node_or_tags
292
+ add_sibling(:next, node_or_tags)
202
293
  end
203
294
 
204
295
  ####
205
296
  # Insert +node_or_tags+ before this node (as a sibling).
206
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
207
297
  #
208
- # Returns self, to support chaining of calls.
298
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
299
+ # containing markup.
300
+ #
301
+ # Returns +self+, to support chaining of calls.
209
302
  #
210
303
  # Also see related method +add_previous_sibling+.
211
304
  def before(node_or_tags)
212
- add_previous_sibling node_or_tags
305
+ add_previous_sibling(node_or_tags)
213
306
  self
214
307
  end
215
308
 
216
309
  ####
217
310
  # Insert +node_or_tags+ after this node (as a sibling).
218
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
219
311
  #
220
- # Returns self, to support chaining of calls.
312
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
313
+ # containing markup.
314
+ #
315
+ # Returns +self+, to support chaining of calls.
221
316
  #
222
317
  # Also see related method +add_next_sibling+.
223
318
  def after(node_or_tags)
224
- add_next_sibling node_or_tags
319
+ add_next_sibling(node_or_tags)
225
320
  self
226
321
  end
227
322
 
228
323
  ####
229
- # Set the inner html for this Node to +node_or_tags+
230
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
324
+ # Set the content for this Node to +node_or_tags+.
325
+ #
326
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
327
+ # containing markup.
328
+ #
329
+ # ⚠ Please note that despite the name, this method will *not* always parse a String argument
330
+ # as HTML. A String argument will be parsed with the +DocumentFragment+ parser related to this
331
+ # node's document.
231
332
  #
232
- # Returns self.
333
+ # For example, if the document is an HTML4::Document then the string will be parsed as HTML4
334
+ # using HTML4::DocumentFragment; but if the document is an XML::Document then it will
335
+ # parse the string as XML using XML::DocumentFragment.
233
336
  #
234
337
  # Also see related method +children=+
235
338
  def inner_html=(node_or_tags)
236
339
  self.children = node_or_tags
237
- self
238
340
  end
239
341
 
240
342
  ####
241
- # Set the inner html for this Node +node_or_tags+
242
- # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a string containing markup.
343
+ # Set the content for this Node +node_or_tags+
243
344
  #
244
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
345
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a Nokogiri::XML::DocumentFragment, or a String
346
+ # containing markup.
245
347
  #
246
348
  # Also see related method +inner_html=+
247
349
  def children=(node_or_tags)
248
350
  node_or_tags = coerce(node_or_tags)
249
351
  children.unlink
250
352
  if node_or_tags.is_a?(XML::NodeSet)
251
- node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
353
+ node_or_tags.each { |n| add_child_node_and_reparent_attrs(n) }
252
354
  else
253
- add_child_node_and_reparent_attrs node_or_tags
355
+ add_child_node_and_reparent_attrs(node_or_tags)
254
356
  end
255
- node_or_tags
256
357
  end
257
358
 
258
359
  ####
259
360
  # Replace this Node with +node_or_tags+.
260
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
261
361
  #
262
- # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
362
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
363
+ # containing markup.
364
+ #
365
+ # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is
366
+ # a DocumentFragment, NodeSet, or String).
263
367
  #
264
368
  # Also see related method +swap+.
265
369
  def replace(node_or_tags)
@@ -270,37 +374,40 @@ module Nokogiri
270
374
  # libxml is trying to find a parent node that is an element or document
271
375
  # so I can't tell if this is bug in libxml or not. issue #775.
272
376
  if text?
273
- replacee = Nokogiri::XML::Node.new "dummy", document
274
- add_previous_sibling_node replacee
377
+ replacee = Nokogiri::XML::Node.new("dummy", document)
378
+ add_previous_sibling_node(replacee)
275
379
  unlink
276
- return replacee.replace node_or_tags
380
+ return replacee.replace(node_or_tags)
277
381
  end
278
382
 
279
383
  node_or_tags = parent.coerce(node_or_tags)
280
384
 
281
385
  if node_or_tags.is_a?(XML::NodeSet)
282
- node_or_tags.each { |n| add_previous_sibling n }
386
+ node_or_tags.each { |n| add_previous_sibling(n) }
283
387
  unlink
284
388
  else
285
- replace_node node_or_tags
389
+ replace_node(node_or_tags)
286
390
  end
287
391
  node_or_tags
288
392
  end
289
393
 
290
394
  ####
291
395
  # Swap this Node for +node_or_tags+
292
- # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.
396
+ #
397
+ # +node_or_tags+ can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a String
398
+ # Containing markup.
293
399
  #
294
400
  # Returns self, to support chaining of calls.
295
401
  #
296
402
  # Also see related method +replace+.
297
403
  def swap(node_or_tags)
298
- replace node_or_tags
404
+ replace(node_or_tags)
299
405
  self
300
406
  end
301
407
 
302
408
  ####
303
- # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
409
+ # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not
410
+ # interpreted as markup.
304
411
  def content=(string)
305
412
  self.native_content = encode_special_chars(string.to_s)
306
413
  end
@@ -309,7 +416,6 @@ module Nokogiri
309
416
  # Set the parent Node for this Node
310
417
  def parent=(parent_node)
311
418
  parent_node.add_child(self)
312
- parent_node
313
419
  end
314
420
 
315
421
  ###
@@ -338,7 +444,7 @@ module Nokogiri
338
444
  raise ArgumentError, "namespace must be declared on the same document"
339
445
  end
340
446
 
341
- set_namespace ns
447
+ set_namespace(ns)
342
448
  end
343
449
 
344
450
  ###
@@ -347,52 +453,159 @@ module Nokogiri
347
453
  # passed to it, allowing more convenient modification of the parser options.
348
454
  def do_xinclude(options = XML::ParseOptions::DEFAULT_XML)
349
455
  options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
350
-
351
- # give options to user
352
456
  yield options if block_given?
353
457
 
354
458
  # call c extension
355
459
  process_xincludes(options.to_i)
356
460
  end
357
461
 
358
- alias :next :next_sibling
359
- alias :previous :previous_sibling
360
- alias :next= :add_next_sibling
361
- alias :previous= :add_previous_sibling
362
- alias :remove :unlink
363
- alias :name= :node_name=
364
- alias :add_namespace :add_namespace_definition
462
+ alias_method :next, :next_sibling
463
+ alias_method :previous, :previous_sibling
464
+ alias_method :next=, :add_next_sibling
465
+ alias_method :previous=, :add_previous_sibling
466
+ alias_method :remove, :unlink
467
+ alias_method :name=, :node_name=
468
+ alias_method :add_namespace, :add_namespace_definition
365
469
 
366
- # @!endgroup
470
+ # :section:
367
471
 
368
- alias :text :content
369
- alias :inner_text :content
370
- alias :name :node_name
371
- alias :type :node_type
372
- alias :to_str :text
373
- alias :clone :dup
374
- alias :elements :element_children
472
+ alias_method :inner_text, :content
473
+ alias_method :text, :content
474
+ alias_method :to_str, :content
475
+ alias_method :name, :node_name
476
+ alias_method :type, :node_type
477
+ alias_method :clone, :dup
478
+ alias_method :elements, :element_children
375
479
 
376
- # @!group Working With Node Attributes
480
+ # :section: Working With Node Attributes
377
481
 
378
- ###
379
- # Get the attribute value for the attribute +name+
482
+ # :call-seq: [](name) → (String, nil)
483
+ #
484
+ # Fetch an attribute from this node.
485
+ #
486
+ # ⚠ Note that attributes with namespaces cannot be accessed with this method. To access
487
+ # namespaced attributes, use #attribute_with_ns.
488
+ #
489
+ # [Returns] (String, nil) value of the attribute +name+, or +nil+ if no matching attribute exists
490
+ #
491
+ # *Example*
492
+ #
493
+ # doc = Nokogiri::XML("<root><child size='large' class='big wide tall'/></root>")
494
+ # child = doc.at_css("child")
495
+ # child["size"] # => "large"
496
+ # child["class"] # => "big wide tall"
497
+ #
498
+ # *Example:* Namespaced attributes will not be returned.
499
+ #
500
+ # ⚠ Note namespaced attributes may be accessed with #attribute or #attribute_with_ns
501
+ #
502
+ # doc = Nokogiri::XML(<<~EOF)
503
+ # <root xmlns:width='http://example.com/widths'>
504
+ # <child width:size='broad'/>
505
+ # </root>
506
+ # EOF
507
+ # doc.at_css("child")["size"] # => nil
508
+ # doc.at_css("child").attribute("size").value # => "broad"
509
+ # doc.at_css("child").attribute_with_ns("size", "http://example.com/widths").value
510
+ # # => "broad"
511
+ #
380
512
  def [](name)
381
513
  get(name.to_s)
382
514
  end
383
515
 
384
- ###
385
- # Set the attribute value for the attribute +name+ to +value+
516
+ # :call-seq: []=(name, value) → value
517
+ #
518
+ # Update the attribute +name+ to +value+, or create the attribute if it does not exist.
519
+ #
520
+ # ⚠ Note that attributes with namespaces cannot be accessed with this method. To access
521
+ # namespaced attributes for update, use #attribute_with_ns. To add a namespaced attribute,
522
+ # see the example below.
523
+ #
524
+ # [Returns] +value+
525
+ #
526
+ # *Example*
527
+ #
528
+ # doc = Nokogiri::XML("<root><child/></root>")
529
+ # child = doc.at_css("child")
530
+ # child["size"] = "broad"
531
+ # child.to_html
532
+ # # => "<child size=\"broad\"></child>"
533
+ #
534
+ # *Example:* Add a namespaced attribute.
535
+ #
536
+ # doc = Nokogiri::XML(<<~EOF)
537
+ # <root xmlns:width='http://example.com/widths'>
538
+ # <child/>
539
+ # </root>
540
+ # EOF
541
+ # child = doc.at_css("child")
542
+ # child["size"] = "broad"
543
+ # ns = doc.root.namespace_definitions.find { |ns| ns.prefix == "width" }
544
+ # child.attribute("size").namespace = ns
545
+ # doc.to_html
546
+ # # => "<root xmlns:width=\"http://example.com/widths\">\n" +
547
+ # # " <child width:size=\"broad\"></child>\n" +
548
+ # # "</root>\n"
549
+ #
386
550
  def []=(name, value)
387
- set name.to_s, value.to_s
551
+ set(name.to_s, value.to_s)
388
552
  end
389
553
 
390
- ####
391
- # Returns a hash containing the node's attributes. The key is
392
- # the attribute name without any namespace, the value is a Nokogiri::XML::Attr
393
- # representing the attribute.
394
- # If you need to distinguish attributes with the same name, with different namespaces
395
- # use #attribute_nodes instead.
554
+ #
555
+ # :call-seq: attributes() Hash<String Nokogiri::XML::Attr>
556
+ #
557
+ # Fetch this node's attributes.
558
+ #
559
+ # Because the keys do not include any namespace information for the attribute, in case of a
560
+ # simple name collision, not all attributes will be returned. In this case, you will need to
561
+ # use #attribute_nodes.
562
+ #
563
+ # [Returns]
564
+ # Hash containing attributes belonging to +self+. The hash keys are String attribute
565
+ # names (without the namespace), and the hash values are Nokogiri::XML::Attr.
566
+ #
567
+ # *Example* with no namespaces:
568
+ #
569
+ # doc = Nokogiri::XML("<root><child size='large' class='big wide tall'/></root>")
570
+ # doc.at_css("child").attributes
571
+ # # => {"size"=>#(Attr:0x550 { name = "size", value = "large" }),
572
+ # # "class"=>#(Attr:0x564 { name = "class", value = "big wide tall" })}
573
+ #
574
+ # *Example* with a namespace:
575
+ #
576
+ # doc = Nokogiri::XML("<root xmlns:desc='http://example.com/sizes'><child desc:size='large'/></root>")
577
+ # doc.at_css("child").attributes
578
+ # # => {"size"=>
579
+ # # #(Attr:0x550 {
580
+ # # name = "size",
581
+ # # namespace = #(Namespace:0x564 {
582
+ # # prefix = "desc",
583
+ # # href = "http://example.com/sizes"
584
+ # # }),
585
+ # # value = "large"
586
+ # # })}
587
+ #
588
+ # *Example* with an attribute name collision:
589
+ #
590
+ # ⚠ Note that only one of the attributes is returned in the Hash.
591
+ #
592
+ # doc = Nokogiri::XML(<<~EOF)
593
+ # <root xmlns:width='http://example.com/widths'
594
+ # xmlns:height='http://example.com/heights'>
595
+ # <child width:size='broad' height:size='tall'/>
596
+ # </root>
597
+ # EOF
598
+ # doc.at_css("child").attributes
599
+ # # => {"size"=>
600
+ # # #(Attr:0x550 {
601
+ # # name = "size",
602
+ # # namespace = #(Namespace:0x564 {
603
+ # # prefix = "height",
604
+ # # href = "http://example.com/heights"
605
+ # # }),
606
+ # # value = "tall"
607
+ # # })}
608
+ #
396
609
  def attributes
397
610
  attribute_nodes.each_with_object({}) do |node, hash|
398
611
  hash[node.node_name] = node
@@ -408,7 +621,7 @@ module Nokogiri
408
621
  ###
409
622
  # Does this Node's attributes include <value>
410
623
  def value?(value)
411
- values.include? value
624
+ values.include?(value)
412
625
  end
413
626
 
414
627
  ###
@@ -420,36 +633,36 @@ module Nokogiri
420
633
  ###
421
634
  # Iterate over each attribute name and value pair for this Node.
422
635
  def each
423
- attribute_nodes.each { |node|
636
+ attribute_nodes.each do |node|
424
637
  yield [node.node_name, node.value]
425
- }
638
+ end
426
639
  end
427
640
 
428
641
  ###
429
642
  # Remove the attribute named +name+
430
643
  def remove_attribute(name)
431
- attr = attributes[name].remove if key? name
644
+ attr = attributes[name].remove if key?(name)
432
645
  clear_xpath_context if Nokogiri.jruby?
433
646
  attr
434
647
  end
435
648
 
436
- # Get the CSS class names of a Node.
649
+ #
650
+ # :call-seq: classes() → Array<String>
651
+ #
652
+ # Fetch CSS class names of a Node.
437
653
  #
438
654
  # This is a convenience function and is equivalent to:
655
+ #
439
656
  # node.kwattr_values("class")
440
657
  #
441
- # @see #kwattr_values
442
- # @see #add_class
443
- # @see #append_class
444
- # @see #remove_class
658
+ # See related: #kwattr_values, #add_class, #append_class, #remove_class
445
659
  #
446
- # @return [Array<String>]
660
+ # [Returns]
661
+ # The CSS classes (Array of String) present in the Node's "class" attribute. If the
662
+ # attribute is empty or non-existent, the return value is an empty array.
447
663
  #
448
- # The CSS classes present in the Node's +class+ attribute. If
449
- # the attribute is empty or non-existent, the return value is
450
- # an empty array.
664
+ # *Example*
451
665
  #
452
- # @example
453
666
  # node # => <div class="section title header"></div>
454
667
  # node.classes # => ["section", "title", "header"]
455
668
  #
@@ -457,42 +670,45 @@ module Nokogiri
457
670
  kwattr_values("class")
458
671
  end
459
672
 
460
- # Ensure HTML CSS classes are present on a +Node+. Any CSS
461
- # classes in +names+ that already exist in the +Node+'s +class+
462
- # attribute are _not_ added. Note that any existing duplicates
463
- # in the +class+ attribute are not removed. Compare with
464
- # {#append_class}.
673
+ #
674
+ # :call-seq: add_class(names) self
675
+ #
676
+ # Ensure HTML CSS classes are present on +self+. Any CSS classes in +names+ that already exist
677
+ # in the "class" attribute are _not_ added. Note that any existing duplicates in the
678
+ # "class" attribute are not removed. Compare with #append_class.
465
679
  #
466
680
  # This is a convenience function and is equivalent to:
681
+ #
467
682
  # node.kwattr_add("class", names)
468
683
  #
469
- # @see #kwattr_add
470
- # @see #classes
471
- # @see #append_class
472
- # @see #remove_class
684
+ # See related: #kwattr_add, #classes, #append_class, #remove_class
473
685
  #
474
- # @param names [String, Array<String>]
686
+ # [Parameters]
687
+ # - +names+ (String, Array<String>)
475
688
  #
476
- # CSS class names to be added to the Node's +class+
477
- # attribute. May be a string containing whitespace-delimited
478
- # names, or an Array of String names. Any class names already
479
- # present will not be added. Any class names not present will
480
- # be added. If no +class+ attribute exists, one is created.
689
+ # CSS class names to be added to the Node's "class" attribute. May be a string containing
690
+ # whitespace-delimited names, or an Array of String names. Any class names already present
691
+ # will not be added. Any class names not present will be added. If no "class" attribute
692
+ # exists, one is created.
481
693
  #
482
- # @return [Node] Returns +self+ for ease of chaining method calls.
694
+ # [Returns] +self+ (Node) for ease of chaining method calls.
695
+ #
696
+ # *Example:* Ensure that the node has CSS class "section"
483
697
  #
484
- # @example Ensure that a +Node+ has CSS class "section"
485
698
  # node # => <div></div>
486
699
  # node.add_class("section") # => <div class="section"></div>
487
700
  # node.add_class("section") # => <div class="section"></div> # duplicate not added
488
701
  #
489
- # @example Ensure that a +Node+ has CSS classes "section" and "header", via a String argument.
702
+ # *Example:* Ensure that the node has CSS classes "section" and "header", via a String argument
703
+ #
704
+ # Note that the CSS class "section" is not added because it is already present.
705
+ # Note also that the pre-existing duplicate CSS class "section" is not removed.
706
+ #
490
707
  # node # => <div class="section section"></div>
491
708
  # node.add_class("section header") # => <div class="section section header"></div>
492
- # # Note that the CSS class "section" is not added because it is already present.
493
- # # Note also that the pre-existing duplicate CSS class "section" is not removed.
494
709
  #
495
- # @example Ensure that a +Node+ has CSS classes "section" and "header", via an Array argument.
710
+ # *Example:* Ensure that the node has CSS classes "section" and "header", via an Array argument
711
+ #
496
712
  # node # => <div></div>
497
713
  # node.add_class(["section", "header"]) # => <div class="section header"></div>
498
714
  #
@@ -500,39 +716,42 @@ module Nokogiri
500
716
  kwattr_add("class", names)
501
717
  end
502
718
 
503
- # Add HTML CSS classes to a +Node+, regardless of
504
- # duplication. Compare with {#add_class}.
719
+ #
720
+ # :call-seq: append_class(names) self
721
+ #
722
+ # Add HTML CSS classes to +self+, regardless of duplication. Compare with #add_class.
505
723
  #
506
724
  # This is a convenience function and is equivalent to:
725
+ #
507
726
  # node.kwattr_append("class", names)
508
727
  #
509
- # @see #kwattr_append
510
- # @see #classes
511
- # @see #add_class
512
- # @see #remove_class
728
+ # See related: #kwattr_append, #classes, #add_class, #remove_class
729
+ #
730
+ # [Parameters]
731
+ # - +names+ (String, Array<String>)
513
732
  #
514
- # @param names [String, Array<String>]
733
+ # CSS class names to be appended to the Node's "class" attribute. May be a string containing
734
+ # whitespace-delimited names, or an Array of String names. All class names passed in will be
735
+ # appended to the "class" attribute even if they are already present in the attribute
736
+ # value. If no "class" attribute exists, one is created.
515
737
  #
516
- # CSS class names to be appended to the Node's +class+
517
- # attribute. May be a string containing whitespace-delimited
518
- # names, or an Array of String names. All class names passed
519
- # in will be appended to the +class+ attribute even if they
520
- # are already present in the attribute value. If no +class+
521
- # attribute exists, one is created.
738
+ # [Returns] +self+ (Node) for ease of chaining method calls.
522
739
  #
523
- # @return [Node] Returns +self+ for ease of chaining method calls.
740
+ # *Example:* Append "section" to the node's CSS "class" attribute
524
741
  #
525
- # @example Append "section" to a +Node+'s CSS +class+ attriubute
526
742
  # node # => <div></div>
527
743
  # node.append_class("section") # => <div class="section"></div>
528
744
  # node.append_class("section") # => <div class="section section"></div> # duplicate added!
529
745
  #
530
- # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via a String argument.
746
+ # *Example:* Append "section" and "header" to the noded's CSS "class" attribute, via a String argument
747
+ #
748
+ # Note that the CSS class "section" is appended even though it is already present
749
+ #
531
750
  # node # => <div class="section section"></div>
532
751
  # node.append_class("section header") # => <div class="section section section header"></div>
533
- # # Note that the CSS class "section" is appended even though it is already present.
534
752
  #
535
- # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via an Array argument.
753
+ # *Example:* Append "section" and "header" to the node's CSS "class" attribute, via an Array argument
754
+ #
536
755
  # node # => <div></div>
537
756
  # node.append_class(["section", "header"]) # => <div class="section header"></div>
538
757
  # node.append_class(["section", "header"]) # => <div class="section header section header"></div>
@@ -541,118 +760,135 @@ module Nokogiri
541
760
  kwattr_append("class", names)
542
761
  end
543
762
 
544
- # Remove HTML CSS classes from a +Node+. Any CSS classes in +names+ that
545
- # exist in the +Node+'s +class+ attribute are removed, including any
546
- # multiple entries.
763
+ # :call-seq:
764
+ # remove_class(css_classes) self
765
+ #
766
+ # Remove HTML CSS classes from this node. Any CSS class names in +css_classes+ that exist in
767
+ # this node's "class" attribute are removed, including any multiple entries.
547
768
  #
548
- # If no CSS classes remain after this operation, or if +names+ is
549
- # +nil+, the +class+ attribute is deleted from the node.
769
+ # If no CSS classes remain after this operation, or if +css_classes+ is +nil+, the "class"
770
+ # attribute is deleted from the node.
550
771
  #
551
772
  # This is a convenience function and is equivalent to:
552
- # node.kwattr_remove("class", names)
553
773
  #
554
- # @see #kwattr_remove
555
- # @see #classes
556
- # @see #add_class
557
- # @see #append_class
774
+ # node.kwattr_remove("class", css_classes)
775
+ #
776
+ # Also see #kwattr_remove, #classes, #add_class, #append_class
777
+ #
778
+ # [Parameters]
779
+ # - +css_classes+ (String, Array<String>)
780
+ #
781
+ # CSS class names to be removed from the Node's
782
+ # "class" attribute. May be a string containing whitespace-delimited names, or an Array of
783
+ # String names. Any class names already present will be removed. If no CSS classes remain,
784
+ # the "class" attribute is deleted.
558
785
  #
559
- # @param names [String, Array<String>]
786
+ # [Returns] +self+ (Nokogiri::XML::Node) for ease of chaining method calls.
560
787
  #
561
- # CSS class names to be removed from the Node's +class+ attribute. May
562
- # be a string containing whitespace-delimited names, or an Array of
563
- # String names. Any class names already present will be removed. If no
564
- # CSS classes remain, the +class+ attribute is deleted.
788
+ # *Example*: Deleting a CSS class
565
789
  #
566
- # @return [Node] Returns +self+ for ease of chaining method calls.
790
+ # Note that all instances of the class "section" are removed from the "class" attribute.
567
791
  #
568
- # @example
569
- # node # => <div class="section header"></div>
792
+ # node # => <div class="section header section"></div>
570
793
  # node.remove_class("section") # => <div class="header"></div>
571
- # node.remove_class("header") # => <div></div> # attribute is deleted when empty
794
+ #
795
+ # *Example*: Deleting the only remaining CSS class
796
+ #
797
+ # Note that the attribute is removed once there are no remaining classes.
798
+ #
799
+ # node # => <div class="section"></div>
800
+ # node.remove_class("section") # => <div></div>
801
+ #
802
+ # *Example*: Deleting multiple CSS classes
803
+ #
804
+ # Note that the "class" attribute is deleted once it's empty.
805
+ #
806
+ # node # => <div class="section header float"></div>
807
+ # node.remove_class(["section", "float"]) # => <div class="header"></div>
572
808
  #
573
809
  def remove_class(names = nil)
574
810
  kwattr_remove("class", names)
575
811
  end
576
812
 
577
- # Retrieve values from a keyword attribute of a Node.
813
+ # :call-seq:
814
+ # kwattr_values(attribute_name) → Array<String>
815
+ #
816
+ # Fetch values from a keyword attribute of a Node.
578
817
  #
579
- # A "keyword attribute" is a node attribute that contains a set
580
- # of space-delimited values. Perhaps the most familiar example
581
- # of this is the HTML +class+ attribute used to contain CSS
582
- # classes. But other keyword attributes exist, for instance
583
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
818
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
819
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
820
+ # contain CSS classes. But other keyword attributes exist, for instance
821
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
584
822
  #
585
- # @see #classes
586
- # @see #kwattr_add
587
- # @see #kwattr_append
588
- # @see #kwattr_remove
823
+ # See also #classes, #kwattr_add, #kwattr_append, #kwattr_remove
589
824
  #
590
- # @param attribute_name [String] The name of the keyword attribute to be inspected.
825
+ # [Parameters]
826
+ # - +attribute_name+ (String) The name of the keyword attribute to be inspected.
591
827
  #
592
- # @return [Array<String>]
828
+ # [Returns]
829
+ # (Array<String>) The values present in the Node's +attribute_name+ attribute. If the
830
+ # attribute is empty or non-existent, the return value is an empty array.
593
831
  #
594
- # The values present in the Node's +attribute_name+
595
- # attribute. If the attribute is empty or non-existent, the
596
- # return value is an empty array.
832
+ # *Example:*
597
833
  #
598
- # @example
599
834
  # node # => <a rel="nofollow noopener external">link</a>
600
835
  # node.kwattr_values("rel") # => ["nofollow", "noopener", "external"]
601
836
  #
602
- # @since v1.11.0
603
- #
837
+ # Since v1.11.0
604
838
  def kwattr_values(attribute_name)
605
839
  keywordify(get_attribute(attribute_name) || [])
606
840
  end
607
841
 
842
+ # :call-seq:
843
+ # kwattr_add(attribute_name, keywords) → self
844
+ #
608
845
  # Ensure that values are present in a keyword attribute.
609
846
  #
610
- # Any values in +keywords+ that already exist in the +Node+'s
611
- # attribute values are _not_ added. Note that any existing
612
- # duplicates in the attribute values are not removed. Compare
613
- # with {#kwattr_append}.
847
+ # Any values in +keywords+ that already exist in the Node's attribute values are _not_
848
+ # added. Note that any existing duplicates in the attribute values are not removed. Compare
849
+ # with #kwattr_append.
614
850
  #
615
- # A "keyword attribute" is a node attribute that contains a set
616
- # of space-delimited values. Perhaps the most familiar example
617
- # of this is the HTML +class+ attribute used to contain CSS
618
- # classes. But other keyword attributes exist, for instance
619
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
851
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
852
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
853
+ # contain CSS classes. But other keyword attributes exist, for instance
854
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
620
855
  #
621
- # @see #add_class
622
- # @see #kwattr_values
623
- # @see #kwattr_append
624
- # @see #kwattr_remove
856
+ # See also #add_class, #kwattr_values, #kwattr_append, #kwattr_remove
625
857
  #
626
- # @param attribute_name [String] The name of the keyword attribute to be modified.
858
+ # [Parameters]
859
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
860
+ # - +keywords+ (String, Array<String>)
861
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
862
+ # whitespace-delimited values, or an Array of String values. Any values already present will
863
+ # not be added. Any values not present will be added. If the named attribute does not exist,
864
+ # it is created.
627
865
  #
628
- # @param keywords [String, Array<String>]
866
+ # [Returns] +self+ (Nokogiri::XML::Node) for ease of chaining method calls.
629
867
  #
630
- # Keywords to be added to the attribute named
631
- # +attribute_name+. May be a string containing
632
- # whitespace-delimited values, or an Array of String
633
- # values. Any values already present will not be added. Any
634
- # values not present will be added. If the named attribute
635
- # does not exist, it is created.
868
+ # *Example:* Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
636
869
  #
637
- # @return [Node] Returns +self+ for ease of chaining method calls.
870
+ # Note that duplicates are not added.
638
871
  #
639
- # @example Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
640
872
  # node # => <a></a>
641
873
  # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
642
- # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a> # duplicate not added
874
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
875
+ #
876
+ # *Example:* Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a
877
+ # String argument.
878
+ #
879
+ # Note that "nofollow" is not added because it is already present. Note also that the
880
+ # pre-existing duplicate "nofollow" is not removed.
643
881
  #
644
- # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a String argument.
645
882
  # node # => <a rel="nofollow nofollow"></a>
646
883
  # node.kwattr_add("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
647
- # # Note that "nofollow" is not added because it is already present.
648
- # # Note also that the pre-existing duplicate "nofollow" is not removed.
649
884
  #
650
- # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via an Array argument.
885
+ # *Example:* Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via
886
+ # an Array argument.
887
+ #
651
888
  # node # => <a></a>
652
889
  # node.kwattr_add("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
653
890
  #
654
- # @since v1.11.0
655
- #
891
+ # Since v1.11.0
656
892
  def kwattr_add(attribute_name, keywords)
657
893
  keywords = keywordify(keywords)
658
894
  current_kws = kwattr_values(attribute_name)
@@ -661,50 +897,51 @@ module Nokogiri
661
897
  self
662
898
  end
663
899
 
664
- # Add keywords to a Node's keyword attribute, regardless of
665
- # duplication. Compare with {#kwattr_add}.
900
+ # :call-seq:
901
+ # kwattr_append(attribute_name, keywords) self
902
+ #
903
+ # Add keywords to a Node's keyword attribute, regardless of duplication. Compare with
904
+ # #kwattr_add.
666
905
  #
667
- # A "keyword attribute" is a node attribute that contains a set
668
- # of space-delimited values. Perhaps the most familiar example
669
- # of this is the HTML +class+ attribute used to contain CSS
670
- # classes. But other keyword attributes exist, for instance
671
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
906
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
907
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
908
+ # contain CSS classes. But other keyword attributes exist, for instance
909
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
672
910
  #
673
- # @see #append_class
674
- # @see #kwattr_values
675
- # @see #kwattr_add
676
- # @see #kwattr_remove
911
+ # See also #append_class, #kwattr_values, #kwattr_add, #kwattr_remove
677
912
  #
678
- # @param attribute_name [String] The name of the keyword attribute to be modified.
913
+ # [Parameters]
914
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
915
+ # - +keywords+ (String, Array<String>)
916
+ # Keywords to be added to the attribute named +attribute_name+. May be a string containing
917
+ # whitespace-delimited values, or an Array of String values. All values passed in will be
918
+ # appended to the named attribute even if they are already present in the attribute. If the
919
+ # named attribute does not exist, it is created.
679
920
  #
680
- # @param keywords [String, Array<String>]
921
+ # [Returns] +self+ (Node) for ease of chaining method calls.
681
922
  #
682
- # Keywords to be added to the attribute named
683
- # +attribute_name+. May be a string containing
684
- # whitespace-delimited values, or an Array of String
685
- # values. All values passed in will be appended to the named
686
- # attribute even if they are already present in the
687
- # attribute. If the named attribute does not exist, it is
688
- # created.
923
+ # *Example:* Append "nofollow" to the +rel+ attribute.
689
924
  #
690
- # @return [Node] Returns +self+ for ease of chaining method calls.
925
+ # Note that duplicates are added.
691
926
  #
692
- # @example Append "nofollow" to the +rel+ attribute.
693
927
  # node # => <a></a>
694
928
  # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow"></a>
695
- # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a> # duplicate added!
929
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a>
930
+ #
931
+ # *Example:* Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
932
+ #
933
+ # Note that "nofollow" is appended even though it is already present.
696
934
  #
697
- # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
698
935
  # node # => <a rel="nofollow"></a>
699
936
  # node.kwattr_append("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
700
- # # Note that "nofollow" is appended even though it is already present.
701
937
  #
702
- # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
938
+ #
939
+ # *Example:* Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
940
+ #
703
941
  # node # => <a></a>
704
942
  # node.kwattr_append("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
705
943
  #
706
- # @since v1.11.0
707
- #
944
+ # Since v1.11.0
708
945
  def kwattr_append(attribute_name, keywords)
709
946
  keywords = keywordify(keywords)
710
947
  current_kws = kwattr_values(attribute_name)
@@ -713,44 +950,41 @@ module Nokogiri
713
950
  self
714
951
  end
715
952
 
716
- # Remove keywords from a keyword attribute. Any matching
717
- # keywords that exist in the named attribute are removed,
718
- # including any multiple entries.
953
+ # :call-seq:
954
+ # kwattr_remove(attribute_name, keywords) self
719
955
  #
720
- # If no keywords remain after this operation, or if +keywords+
721
- # is +nil+, the attribute is deleted from the node.
956
+ # Remove keywords from a keyword attribute. Any matching keywords that exist in the named
957
+ # attribute are removed, including any multiple entries.
722
958
  #
723
- # A "keyword attribute" is a node attribute that contains a set
724
- # of space-delimited values. Perhaps the most familiar example
725
- # of this is the HTML +class+ attribute used to contain CSS
726
- # classes. But other keyword attributes exist, for instance
727
- # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
959
+ # If no keywords remain after this operation, or if +keywords+ is +nil+, the attribute is
960
+ # deleted from the node.
728
961
  #
729
- # @see #remove_class
730
- # @see #kwattr_values
731
- # @see #kwattr_add
732
- # @see #kwattr_append
962
+ # A "keyword attribute" is a node attribute that contains a set of space-delimited
963
+ # values. Perhaps the most familiar example of this is the HTML "class" attribute used to
964
+ # contain CSS classes. But other keyword attributes exist, for instance
965
+ # {the "rel" attribute}[https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel].
733
966
  #
734
- # @param attribute_name [String] The name of the keyword attribute to be modified.
967
+ # See also #remove_class, #kwattr_values, #kwattr_add, #kwattr_append
735
968
  #
736
- # @param keywords [String, Array<String>]
969
+ # [Parameters]
970
+ # - +attribute_name+ (String) The name of the keyword attribute to be modified.
971
+ # - +keywords+ (String, Array<String>)
972
+ # Keywords to be removed from the attribute named +attribute_name+. May be a string
973
+ # containing whitespace-delimited values, or an Array of String values. Any keywords present
974
+ # in the named attribute will be removed. If no keywords remain, or if +keywords+ is nil,
975
+ # the attribute is deleted.
737
976
  #
738
- # Keywords to be removed from the attribute named
739
- # +attribute_name+. May be a string containing
740
- # whitespace-delimited values, or an Array of String
741
- # values. Any keywords present in the named attribute will be
742
- # removed. If no keywords remain, or if +keywords+ is nil, the
743
- # attribute is deleted.
977
+ # [Returns] +self+ (Node) for ease of chaining method calls.
744
978
  #
745
- # @return [Node] Returns +self+ for ease of chaining method calls.
979
+ # *Example:*
980
+ #
981
+ # Note that the +rel+ attribute is deleted when empty.
746
982
  #
747
- # @example
748
983
  # node # => <a rel="nofollow noreferrer">link</a>
749
984
  # node.kwattr_remove("rel", "nofollow") # => <a rel="noreferrer">link</a>
750
- # node.kwattr_remove("rel", "noreferrer") # => <a>link</a> # attribute is deleted when empty
751
- #
752
- # @since v1.11.0
985
+ # node.kwattr_remove("rel", "noreferrer") # => <a>link</a>
753
986
  #
987
+ # Since v1.11.0
754
988
  def kwattr_remove(attribute_name, keywords)
755
989
  if keywords.nil?
756
990
  remove_attribute(attribute_name)
@@ -768,13 +1002,13 @@ module Nokogiri
768
1002
  self
769
1003
  end
770
1004
 
771
- alias :delete :remove_attribute
772
- alias :get_attribute :[]
773
- alias :attr :[]
774
- alias :set_attribute :[]=
775
- alias :has_attribute? :key?
1005
+ alias_method :delete, :remove_attribute
1006
+ alias_method :get_attribute, :[]
1007
+ alias_method :attr, :[]
1008
+ alias_method :set_attribute, :[]=
1009
+ alias_method :has_attribute?, :key?
776
1010
 
777
- # @!endgroup
1011
+ # :section:
778
1012
 
779
1013
  ###
780
1014
  # Returns true if this Node matches +selector+
@@ -786,8 +1020,7 @@ module Nokogiri
786
1020
  # Create a DocumentFragment containing +tags+ that is relative to _this_
787
1021
  # context node.
788
1022
  def fragment(tags)
789
- type = document.html? ? Nokogiri::HTML : Nokogiri::XML
790
- type::DocumentFragment.new(document, tags, self)
1023
+ document.related_class("DocumentFragment").new(document, tags, self)
791
1024
  end
792
1025
 
793
1026
  ###
@@ -805,19 +1038,18 @@ module Nokogiri
805
1038
  end
806
1039
 
807
1040
  options ||= (document.html? ? ParseOptions::DEFAULT_HTML : ParseOptions::DEFAULT_XML)
808
- if Integer === options
809
- options = Nokogiri::XML::ParseOptions.new(options)
810
- end
811
- # Give the options to the user
1041
+ options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
812
1042
  yield options if block_given?
813
1043
 
814
- contents = string_or_io.respond_to?(:read) ?
815
- string_or_io.read :
1044
+ contents = if string_or_io.respond_to?(:read)
1045
+ string_or_io.read
1046
+ else
816
1047
  string_or_io
1048
+ end
817
1049
 
818
1050
  return Nokogiri::XML::NodeSet.new(document) if contents.empty?
819
1051
 
820
- # libxml2 does not obey the `recover` option after encountering errors during `in_context`
1052
+ # libxml2 does not obey the +recover+ option after encountering errors during +in_context+
821
1053
  # parsing, and so this horrible hack is here to try to emulate recovery behavior.
822
1054
  #
823
1055
  # Unfortunately, this means we're no longer parsing "in context" and so namespaces that
@@ -827,16 +1059,16 @@ module Nokogiri
827
1059
  #
828
1060
  # I think preferable behavior would be to either:
829
1061
  #
830
- # a. add an error noting that we "fell back" and pointing the user to turning off the `recover` option
1062
+ # a. add an error noting that we "fell back" and pointing the user to turning off the +recover+ option
831
1063
  # b. don't recover, but raise a sensible exception
832
1064
  #
833
1065
  # For context and background: https://github.com/sparklemotion/nokogiri/issues/313
834
1066
  # FIXME bug report: https://github.com/sparklemotion/nokogiri/issues/2092
835
1067
  error_count = document.errors.length
836
1068
  node_set = in_context(contents, options.to_i)
837
- if (node_set.empty? && (document.errors.length > error_count))
1069
+ if node_set.empty? && (document.errors.length > error_count)
838
1070
  if options.recover?
839
- fragment = Nokogiri::HTML4::DocumentFragment.parse contents
1071
+ fragment = document.related_class("DocumentFragment").parse(contents)
840
1072
  node_set = fragment.children
841
1073
  else
842
1074
  raise document.errors[error_count]
@@ -845,20 +1077,42 @@ module Nokogiri
845
1077
  node_set
846
1078
  end
847
1079
 
848
- ###
849
- # Returns a Hash of +{prefix => value}+ for all namespaces on this
850
- # node and its ancestors.
851
- #
852
- # This method returns the same namespaces as #namespace_scopes.
853
- #
854
- # Returns namespaces in scope for self -- those defined on self
855
- # element directly or any ancestor node -- as a Hash of
856
- # attribute-name/value pairs. Note that the keys in this hash
857
- # XML attributes that would be used to define this namespace,
858
- # such as "xmlns:prefix", not just the prefix. Default namespace
859
- # set on self will be included with key "xmlns". However,
860
- # default namespaces set on ancestor will NOT be, even if self
861
- # has no explicit default namespace.
1080
+ # :call-seq:
1081
+ # namespaces() Hash<String(Namespace#prefix) String(Namespace#href)>
1082
+ #
1083
+ # Fetch all the namespaces on this node and its ancestors.
1084
+ #
1085
+ # Note that the keys in this hash XML attributes that would be used to define this namespace,
1086
+ # such as "xmlns:prefix", not just the prefix.
1087
+ #
1088
+ # The default namespace for this node will be included with key "xmlns".
1089
+ #
1090
+ # See also #namespace_scopes
1091
+ #
1092
+ # [Returns]
1093
+ # Hash containing all the namespaces on this node and its ancestors. The hash keys are the
1094
+ # namespace prefix, and the hash value for each key is the namespace URI.
1095
+ #
1096
+ # *Example:*
1097
+ #
1098
+ # doc = Nokogiri::XML(<<~EOF)
1099
+ # <root xmlns="http://example.com/root" xmlns:in_scope="http://example.com/in_scope">
1100
+ # <first/>
1101
+ # <second xmlns="http://example.com/child"/>
1102
+ # <third xmlns:foo="http://example.com/foo"/>
1103
+ # </root>
1104
+ # EOF
1105
+ # doc.at_xpath("//root:first", "root" => "http://example.com/root").namespaces
1106
+ # # => {"xmlns"=>"http://example.com/root",
1107
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1108
+ # doc.at_xpath("//child:second", "child" => "http://example.com/child").namespaces
1109
+ # # => {"xmlns"=>"http://example.com/child",
1110
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1111
+ # doc.at_xpath("//root:third", "root" => "http://example.com/root").namespaces
1112
+ # # => {"xmlns:foo"=>"http://example.com/foo",
1113
+ # # "xmlns"=>"http://example.com/root",
1114
+ # # "xmlns:in_scope"=>"http://example.com/in_scope"}
1115
+ #
862
1116
  def namespaces
863
1117
  namespace_scopes.each_with_object({}) do |ns, hash|
864
1118
  prefix = ns.prefix
@@ -882,14 +1136,14 @@ module Nokogiri
882
1136
  type == DOCUMENT_NODE
883
1137
  end
884
1138
 
885
- # Returns true if this is an HTML4::Document node
1139
+ # Returns true if this is an HTML4::Document or HTML5::Document node
886
1140
  def html?
887
1141
  type == HTML_DOCUMENT_NODE
888
1142
  end
889
1143
 
890
1144
  # Returns true if this is a Document
891
1145
  def document?
892
- is_a? XML::Document
1146
+ is_a?(XML::Document)
893
1147
  end
894
1148
 
895
1149
  # Returns true if this is a ProcessingInstruction node
@@ -912,6 +1166,7 @@ module Nokogiri
912
1166
  # nil on XML documents and on unknown tags.
913
1167
  def description
914
1168
  return nil if document.xml?
1169
+
915
1170
  Nokogiri::HTML4::ElementDescription[name]
916
1171
  end
917
1172
 
@@ -927,7 +1182,7 @@ module Nokogiri
927
1182
  type == ELEMENT_NODE
928
1183
  end
929
1184
 
930
- alias :elem? :element?
1185
+ alias_method :elem?, :element?
931
1186
 
932
1187
  ###
933
1188
  # Turn this node in to a string. If the document is HTML, this method
@@ -943,9 +1198,9 @@ module Nokogiri
943
1198
 
944
1199
  # Get the path to this node as a CSS expression
945
1200
  def css_path
946
- path.split(/\//).map { |part|
947
- part.length == 0 ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
948
- }.compact.join(" > ")
1201
+ path.split(%r{/}).filter_map do |part|
1202
+ part.empty? ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
1203
+ end.join(" > ")
949
1204
  end
950
1205
 
951
1206
  ###
@@ -958,7 +1213,8 @@ module Nokogiri
958
1213
  parents = [parent]
959
1214
 
960
1215
  while parents.last.respond_to?(:parent)
961
- break unless ctx_parent = parents.last.parent
1216
+ break unless (ctx_parent = parents.last.parent)
1217
+
962
1218
  parents << ctx_parent
963
1219
  end
964
1220
 
@@ -967,16 +1223,16 @@ module Nokogiri
967
1223
  root = parents.last
968
1224
  search_results = root.search(selector)
969
1225
 
970
- NodeSet.new(document, parents.find_all { |parent|
1226
+ NodeSet.new(document, parents.find_all do |parent|
971
1227
  search_results.include?(parent)
972
- })
1228
+ end)
973
1229
  end
974
1230
 
975
1231
  ####
976
1232
  # Yields self and all children to +block+ recursively.
977
1233
  def traverse(&block)
978
1234
  children.each { |j| j.traverse(&block) }
979
- block.call(self)
1235
+ yield(self)
980
1236
  end
981
1237
 
982
1238
  ###
@@ -990,6 +1246,7 @@ module Nokogiri
990
1246
  def ==(other)
991
1247
  return false unless other
992
1248
  return false unless other.respond_to?(:pointer_id)
1249
+
993
1250
  pointer_id == other.pointer_id
994
1251
  end
995
1252
 
@@ -999,14 +1256,16 @@ module Nokogiri
999
1256
  def <=>(other)
1000
1257
  return nil unless other.is_a?(Nokogiri::XML::Node)
1001
1258
  return nil unless document == other.document
1002
- compare other
1259
+
1260
+ compare(other)
1003
1261
  end
1004
1262
 
1005
- # @!group Serialization and Generating Output
1263
+ # :section: Serialization and Generating Output
1006
1264
 
1007
1265
  ###
1008
- # Serialize Node using +options+. Save options can also be set using a
1009
- # block. See SaveOptions.
1266
+ # Serialize Node using +options+. Save options can also be set using a block.
1267
+ #
1268
+ # See also Nokogiri::XML::Node::SaveOptions and Node@Serialization+and+Generating+Output.
1010
1269
  #
1011
1270
  # These two statements are equivalent:
1012
1271
  #
@@ -1019,18 +1278,21 @@ module Nokogiri
1019
1278
  # end
1020
1279
  #
1021
1280
  def serialize(*args, &block)
1022
- options = args.first.is_a?(Hash) ? args.shift : {
1023
- :encoding => args[0],
1024
- :save_with => args[1],
1025
- }
1026
-
1027
- encoding = options[:encoding] || document.encoding
1028
- options[:encoding] = encoding
1029
-
1030
- outstring = String.new
1031
- outstring.force_encoding(Encoding.find(encoding || "utf-8"))
1032
- io = StringIO.new(outstring)
1033
- write_to io, options, &block
1281
+ options = if args.first.is_a?(Hash)
1282
+ args.shift
1283
+ else
1284
+ {
1285
+ encoding: args[0],
1286
+ save_with: args[1],
1287
+ }
1288
+ end
1289
+
1290
+ options[:encoding] ||= document.encoding
1291
+ encoding = Encoding.find(options[:encoding] || "UTF-8")
1292
+
1293
+ io = StringIO.new(String.new(encoding: encoding))
1294
+
1295
+ write_to(io, options, &block)
1034
1296
  io.string
1035
1297
  end
1036
1298
 
@@ -1042,7 +1304,7 @@ module Nokogiri
1042
1304
  # See Node#write_to for a list of +options+. For formatted output,
1043
1305
  # use Node#to_xhtml instead.
1044
1306
  def to_html(options = {})
1045
- to_format SaveOptions::DEFAULT_HTML, options
1307
+ to_format(SaveOptions::DEFAULT_HTML, options)
1046
1308
  end
1047
1309
 
1048
1310
  ###
@@ -1063,7 +1325,7 @@ module Nokogiri
1063
1325
  #
1064
1326
  # See Node#write_to for a list of +options+
1065
1327
  def to_xhtml(options = {})
1066
- to_format SaveOptions::DEFAULT_XHTML, options
1328
+ to_format(SaveOptions::DEFAULT_XHTML, options)
1067
1329
  end
1068
1330
 
1069
1331
  ###
@@ -1111,7 +1373,7 @@ module Nokogiri
1111
1373
  #
1112
1374
  # See Node#write_to for a list of +options+
1113
1375
  def write_html_to(io, options = {})
1114
- write_format_to SaveOptions::DEFAULT_HTML, io, options
1376
+ write_format_to(SaveOptions::DEFAULT_HTML, io, options)
1115
1377
  end
1116
1378
 
1117
1379
  ###
@@ -1119,7 +1381,7 @@ module Nokogiri
1119
1381
  #
1120
1382
  # See Node#write_to for a list of +options+
1121
1383
  def write_xhtml_to(io, options = {})
1122
- write_format_to SaveOptions::DEFAULT_XHTML, io, options
1384
+ write_format_to(SaveOptions::DEFAULT_XHTML, io, options)
1123
1385
  end
1124
1386
 
1125
1387
  ###
@@ -1130,7 +1392,7 @@ module Nokogiri
1130
1392
  # See Node#write_to for a list of options
1131
1393
  def write_xml_to(io, options = {})
1132
1394
  options[:save_with] ||= SaveOptions::DEFAULT_XML
1133
- write_to io, options
1395
+ write_to(io, options)
1134
1396
  end
1135
1397
 
1136
1398
  def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comments = false)
@@ -1141,7 +1403,70 @@ module Nokogiri
1141
1403
  end
1142
1404
  end
1143
1405
 
1144
- # @!endgroup
1406
+ DECONSTRUCT_KEYS = [:name, :attributes, :children, :namespace, :content, :elements, :inner_html].freeze # :nodoc:
1407
+ DECONSTRUCT_METHODS = { attributes: :attribute_nodes }.freeze # :nodoc:
1408
+
1409
+ #
1410
+ # :call-seq: deconstruct_keys(array_of_names) → Hash
1411
+ #
1412
+ # Returns a hash describing the Node, to use in pattern matching.
1413
+ #
1414
+ # Valid keys and their values:
1415
+ # - +name+ → (String) The name of this node, or "text" if it is a Text node.
1416
+ # - +namespace+ → (Namespace, nil) The namespace of this node, or nil if there is no namespace.
1417
+ # - +attributes+ → (Array<Attr>) The attributes of this node.
1418
+ # - +children+ → (Array<Node>) The children of this node. 💡 Note this includes text nodes.
1419
+ # - +elements+ → (Array<Node>) The child elements of this node. 💡 Note this does not include text nodes.
1420
+ # - +content+ → (String) The contents of all the text nodes in this node's subtree. See #content.
1421
+ # - +inner_html+ → (String) The inner markup for the children of this node. See #inner_html.
1422
+ #
1423
+ # ⚡ This is an experimental feature, available since v1.14.0
1424
+ #
1425
+ # *Example*
1426
+ #
1427
+ # doc = Nokogiri::XML.parse(<<~XML)
1428
+ # <?xml version="1.0"?>
1429
+ # <parent xmlns="http://nokogiri.org/ns/default" xmlns:noko="http://nokogiri.org/ns/noko">
1430
+ # <child1 foo="abc" noko:bar="def">First</child1>
1431
+ # <noko:child2 foo="qwe" noko:bar="rty">Second</noko:child2>
1432
+ # </parent>
1433
+ # XML
1434
+ #
1435
+ # doc.root.deconstruct_keys([:name, :namespace])
1436
+ # # => {:name=>"parent",
1437
+ # # :namespace=>
1438
+ # # #(Namespace:0x35c { href = "http://nokogiri.org/ns/default" })}
1439
+ #
1440
+ # doc.root.deconstruct_keys([:inner_html, :content])
1441
+ # # => {:content=>"\n" + " First\n" + " Second\n",
1442
+ # # :inner_html=>
1443
+ # # "\n" +
1444
+ # # " <child1 foo=\"abc\" noko:bar=\"def\">First</child1>\n" +
1445
+ # # " <noko:child2 foo=\"qwe\" noko:bar=\"rty\">Second</noko:child2>\n"}
1446
+ #
1447
+ # doc.root.elements.first.deconstruct_keys([:attributes])
1448
+ # # => {:attributes=>
1449
+ # # [#(Attr:0x370 { name = "foo", value = "abc" }),
1450
+ # # #(Attr:0x384 {
1451
+ # # name = "bar",
1452
+ # # namespace = #(Namespace:0x398 {
1453
+ # # prefix = "noko",
1454
+ # # href = "http://nokogiri.org/ns/noko"
1455
+ # # }),
1456
+ # # value = "def"
1457
+ # # })]}
1458
+ #
1459
+ def deconstruct_keys(keys)
1460
+ requested_keys = DECONSTRUCT_KEYS & keys
1461
+ {}.tap do |values|
1462
+ requested_keys.each do |key|
1463
+ method = DECONSTRUCT_METHODS[key] || key
1464
+ values[key] = send(method)
1465
+ end
1466
+ end
1467
+ end
1468
+
1469
+ # :section:
1145
1470
 
1146
1471
  protected
1147
1472
 
@@ -1159,9 +1484,9 @@ module Nokogiri
1159
1484
  return data
1160
1485
  end
1161
1486
 
1162
- raise ArgumentError, <<-EOERR
1163
- Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1164
- (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
1487
+ raise ArgumentError, <<~EOERR
1488
+ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1489
+ (You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
1165
1490
  EOERR
1166
1491
  end
1167
1492
 
@@ -1170,32 +1495,33 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1170
1495
  def keywordify(keywords)
1171
1496
  case keywords
1172
1497
  when Enumerable
1173
- return keywords
1498
+ keywords
1174
1499
  when String
1175
- return keywords.scan(/\S+/)
1500
+ keywords.scan(/\S+/)
1176
1501
  else
1177
- raise ArgumentError.new("Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}")
1502
+ raise ArgumentError,
1503
+ "Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}"
1178
1504
  end
1179
1505
  end
1180
1506
 
1181
1507
  def add_sibling(next_or_previous, node_or_tags)
1182
1508
  raise("Cannot add sibling to a node with no parent") unless parent
1183
1509
 
1184
- impl = (next_or_previous == :next) ? :add_next_sibling_node : :add_previous_sibling_node
1185
- iter = (next_or_previous == :next) ? :reverse_each : :each
1510
+ impl = next_or_previous == :next ? :add_next_sibling_node : :add_previous_sibling_node
1511
+ iter = next_or_previous == :next ? :reverse_each : :each
1186
1512
 
1187
1513
  node_or_tags = parent.coerce(node_or_tags)
1188
1514
  if node_or_tags.is_a?(XML::NodeSet)
1189
1515
  if text?
1190
- pivot = Nokogiri::XML::Node.new "dummy", document
1191
- send impl, pivot
1516
+ pivot = Nokogiri::XML::Node.new("dummy", document)
1517
+ send(impl, pivot)
1192
1518
  else
1193
1519
  pivot = self
1194
1520
  end
1195
- node_or_tags.send(iter) { |n| pivot.send impl, n }
1521
+ node_or_tags.send(iter) { |n| pivot.send(impl, n) }
1196
1522
  pivot.unlink if text?
1197
1523
  else
1198
- send impl, node_or_tags
1524
+ send(impl, node_or_tags)
1199
1525
  end
1200
1526
  node_or_tags
1201
1527
  end
@@ -1214,19 +1540,18 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
1214
1540
  return (io << dump_html) if USING_LIBXML_WITH_BROKEN_SERIALIZATION
1215
1541
 
1216
1542
  options[:save_with] ||= save_option
1217
- write_to io, options
1543
+ write_to(io, options)
1218
1544
  end
1219
1545
 
1220
1546
  def inspect_attributes
1221
1547
  [:name, :namespace, :attribute_nodes, :children]
1222
1548
  end
1223
1549
 
1224
- # @private
1225
- IMPLIED_XPATH_CONTEXTS = [".//".freeze].freeze
1550
+ IMPLIED_XPATH_CONTEXTS = [".//"].freeze
1226
1551
 
1227
1552
  def add_child_node_and_reparent_attrs(node)
1228
- add_child_node node
1229
- node.attribute_nodes.find_all { |a| a.name =~ /:/ }.each do |attr_node|
1553
+ add_child_node(node)
1554
+ node.attribute_nodes.find_all { |a| a.name.include?(":") }.each do |attr_node|
1230
1555
  attr_node.remove
1231
1556
  node[attr_node.name] = attr_node.value
1232
1557
  end