nokogiri 1.10.9 → 1.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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