nokogiri 1.11.0.rc1-java → 1.11.2-java

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