nokogiri 1.10.8-java → 1.11.0.rc3-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -22
  3. data/ext/java/nokogiri/HtmlDocument.java +34 -46
  4. data/ext/java/nokogiri/HtmlSaxParserContext.java +87 -57
  5. data/ext/java/nokogiri/NokogiriService.java +1 -1
  6. data/ext/java/nokogiri/XmlAttr.java +13 -20
  7. data/ext/java/nokogiri/XmlAttributeDecl.java +11 -12
  8. data/ext/java/nokogiri/XmlCdata.java +3 -4
  9. data/ext/java/nokogiri/XmlComment.java +1 -1
  10. data/ext/java/nokogiri/XmlDocument.java +148 -175
  11. data/ext/java/nokogiri/XmlDocumentFragment.java +13 -31
  12. data/ext/java/nokogiri/XmlDtd.java +5 -8
  13. data/ext/java/nokogiri/XmlElement.java +1 -20
  14. data/ext/java/nokogiri/XmlElementDecl.java +23 -28
  15. data/ext/java/nokogiri/XmlEntityDecl.java +23 -27
  16. data/ext/java/nokogiri/XmlEntityReference.java +2 -2
  17. data/ext/java/nokogiri/XmlNamespace.java +72 -89
  18. data/ext/java/nokogiri/XmlNode.java +300 -401
  19. data/ext/java/nokogiri/XmlNodeSet.java +72 -77
  20. data/ext/java/nokogiri/XmlReader.java +10 -11
  21. data/ext/java/nokogiri/XmlSaxParserContext.java +7 -7
  22. data/ext/java/nokogiri/XmlSchema.java +3 -3
  23. data/ext/java/nokogiri/XmlText.java +12 -9
  24. data/ext/java/nokogiri/XmlXpathContext.java +7 -7
  25. data/ext/java/nokogiri/XsltStylesheet.java +7 -15
  26. data/ext/java/nokogiri/internals/HtmlDomParserContext.java +4 -10
  27. data/ext/java/nokogiri/internals/NokogiriHelpers.java +71 -135
  28. data/ext/java/nokogiri/internals/NokogiriNamespaceCache.java +90 -58
  29. data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +5 -4
  30. data/ext/java/nokogiri/internals/ParserContext.java +27 -73
  31. data/ext/java/nokogiri/internals/ReaderNode.java +2 -4
  32. data/ext/java/nokogiri/internals/XmlDomParserContext.java +17 -32
  33. data/ext/nokogiri/extconf.rb +50 -37
  34. data/ext/nokogiri/nokogiri.c +12 -6
  35. data/ext/nokogiri/nokogiri.h +13 -0
  36. data/ext/nokogiri/xml_document.c +16 -2
  37. data/ext/nokogiri/xml_io.c +8 -6
  38. data/ext/nokogiri/xml_node.c +20 -0
  39. data/ext/nokogiri/xml_reader.c +6 -17
  40. data/ext/nokogiri/xml_schema.c +29 -0
  41. data/ext/nokogiri/xslt_stylesheet.c +0 -4
  42. data/lib/nokogiri.rb +3 -20
  43. data/lib/nokogiri/css.rb +1 -0
  44. data/lib/nokogiri/css/node.rb +1 -0
  45. data/lib/nokogiri/css/parser.rb +61 -60
  46. data/lib/nokogiri/css/parser_extras.rb +39 -36
  47. data/lib/nokogiri/css/syntax_error.rb +1 -0
  48. data/lib/nokogiri/css/tokenizer.rb +1 -0
  49. data/lib/nokogiri/css/xpath_visitor.rb +3 -1
  50. data/lib/nokogiri/decorators/slop.rb +1 -0
  51. data/lib/nokogiri/html.rb +1 -0
  52. data/lib/nokogiri/html/builder.rb +1 -0
  53. data/lib/nokogiri/html/document.rb +1 -0
  54. data/lib/nokogiri/html/document_fragment.rb +1 -0
  55. data/lib/nokogiri/html/element_description.rb +1 -0
  56. data/lib/nokogiri/html/element_description_defaults.rb +1 -0
  57. data/lib/nokogiri/html/entity_lookup.rb +1 -0
  58. data/lib/nokogiri/html/sax/parser.rb +1 -0
  59. data/lib/nokogiri/html/sax/parser_context.rb +1 -0
  60. data/lib/nokogiri/html/sax/push_parser.rb +1 -0
  61. data/lib/nokogiri/jruby/dependencies.rb +20 -0
  62. data/lib/nokogiri/nokogiri.jar +0 -0
  63. data/lib/nokogiri/syntax_error.rb +1 -0
  64. data/lib/nokogiri/version.rb +86 -45
  65. data/lib/nokogiri/xml.rb +1 -0
  66. data/lib/nokogiri/xml/attr.rb +1 -0
  67. data/lib/nokogiri/xml/attribute_decl.rb +1 -0
  68. data/lib/nokogiri/xml/builder.rb +3 -2
  69. data/lib/nokogiri/xml/cdata.rb +1 -0
  70. data/lib/nokogiri/xml/character_data.rb +1 -0
  71. data/lib/nokogiri/xml/document.rb +3 -8
  72. data/lib/nokogiri/xml/document_fragment.rb +1 -0
  73. data/lib/nokogiri/xml/dtd.rb +1 -0
  74. data/lib/nokogiri/xml/element_content.rb +1 -0
  75. data/lib/nokogiri/xml/element_decl.rb +1 -0
  76. data/lib/nokogiri/xml/entity_decl.rb +1 -0
  77. data/lib/nokogiri/xml/entity_reference.rb +1 -0
  78. data/lib/nokogiri/xml/namespace.rb +1 -0
  79. data/lib/nokogiri/xml/node.rb +539 -224
  80. data/lib/nokogiri/xml/node/save_options.rb +1 -0
  81. data/lib/nokogiri/xml/node_set.rb +1 -0
  82. data/lib/nokogiri/xml/notation.rb +1 -0
  83. data/lib/nokogiri/xml/parse_options.rb +4 -3
  84. data/lib/nokogiri/xml/pp.rb +1 -0
  85. data/lib/nokogiri/xml/pp/character_data.rb +1 -0
  86. data/lib/nokogiri/xml/pp/node.rb +1 -0
  87. data/lib/nokogiri/xml/processing_instruction.rb +1 -0
  88. data/lib/nokogiri/xml/reader.rb +7 -3
  89. data/lib/nokogiri/xml/relax_ng.rb +1 -0
  90. data/lib/nokogiri/xml/sax.rb +1 -0
  91. data/lib/nokogiri/xml/sax/document.rb +1 -0
  92. data/lib/nokogiri/xml/sax/parser.rb +1 -0
  93. data/lib/nokogiri/xml/sax/parser_context.rb +1 -0
  94. data/lib/nokogiri/xml/sax/push_parser.rb +1 -0
  95. data/lib/nokogiri/xml/schema.rb +1 -0
  96. data/lib/nokogiri/xml/searchable.rb +22 -15
  97. data/lib/nokogiri/xml/syntax_error.rb +1 -0
  98. data/lib/nokogiri/xml/text.rb +1 -0
  99. data/lib/nokogiri/xml/xpath.rb +1 -0
  100. data/lib/nokogiri/xml/xpath/syntax_error.rb +1 -0
  101. data/lib/nokogiri/xml/xpath_context.rb +1 -0
  102. data/lib/nokogiri/xslt.rb +1 -0
  103. data/lib/nokogiri/xslt/stylesheet.rb +1 -0
  104. data/lib/xsd/xmlparser/nokogiri.rb +1 -0
  105. metadata +53 -34
  106. data/ext/java/nokogiri/internals/NokogiriEncodingReaderWrapper.java +0 -107
  107. data/ext/java/nokogiri/internals/UncloseableInputStream.java +0 -102
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri/syntax_error'
2
3
  module Nokogiri
3
4
  module CSS
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #--
2
3
  # DO NOT MODIFY!!!!
3
4
  # This file is automatically generated by rex 1.0.7
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module CSS
3
4
  class XPathVisitor # :nodoc:
@@ -51,7 +52,8 @@ module Nokogiri
51
52
  when /^comment\(/
52
53
  "comment()"
53
54
  when /^has\(/
54
- ".//#{node.value[1].accept(self)}"
55
+ is_direct = node.value[1].value[0].nil? # e.g. "has(> a)", "has(~ a)", "has(+ a)"
56
+ ".#{"//" if !is_direct}#{node.value[1].accept(self)}"
55
57
  else
56
58
  args = ['.'] + node.value[1..-1]
57
59
  "#{node.value.first}#{args.join(', ')})"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module Decorators
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri/html/entity_lookup'
2
3
  require 'nokogiri/html/document'
3
4
  require 'nokogiri/html/document_fragment'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  class Document < Nokogiri::XML::Document
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  class DocumentFragment < Nokogiri::XML::DocumentFragment
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  class ElementDescription
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  class ElementDescription
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  class EntityDescription < Struct.new(:value, :name, :description); end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  module SAX
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module HTML
3
4
  module SAX
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # The line below caused a problem on non-GAE rack environment.
3
+ # unless defined?(JRuby::Rack::VERSION) || defined?(AppEngine::ApiProxy)
4
+ #
5
+ # However, simply cutting defined?(JRuby::Rack::VERSION) off resulted in
6
+ # an unable-to-load-nokogiri problem. Thus, now, Nokogiri checks the presense
7
+ # of appengine-rack.jar in $LOAD_PATH. If Nokogiri is on GAE, Nokogiri
8
+ # should skip loading xml jars. This is because those are in WEB-INF/lib and
9
+ # already set in the classpath.
10
+ unless $LOAD_PATH.to_s.include?("appengine-rack")
11
+ require 'stringio'
12
+ require 'isorelax.jar'
13
+ require 'jing.jar'
14
+ require 'nekohtml.jar'
15
+ require 'nekodtd.jar'
16
+ require 'xercesImpl.jar'
17
+ require 'serializer.jar'
18
+ require 'xalan.jar'
19
+ require 'xml-apis.jar'
20
+ end
Binary file
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  class SyntaxError < ::StandardError
3
4
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  # The version of Nokogiri you are using
3
- VERSION = "1.10.8"
4
+ VERSION = "1.11.0.rc3"
4
5
 
5
6
  class VersionInfo # :nodoc:
6
7
  def jruby?
@@ -11,19 +12,30 @@ module Nokogiri
11
12
  defined?(RUBY_ENGINE) ? RUBY_ENGINE : "mri"
12
13
  end
13
14
 
14
- def loaded_parser_version
15
- LIBXML_PARSER_VERSION.
15
+ def loaded_libxml_version
16
+ Gem::Version.new(LIBXML_LOADED_VERSION.
16
17
  scan(/^(\d+)(\d\d)(\d\d)(?!\d)/).first.
17
18
  collect(&:to_i).
18
- join(".")
19
+ join("."))
19
20
  end
20
21
 
21
- def compiled_parser_version
22
- LIBXML_VERSION
22
+ def compiled_libxml_version
23
+ Gem::Version.new LIBXML_COMPILED_VERSION
24
+ end
25
+
26
+ def loaded_libxslt_version
27
+ Gem::Version.new(LIBXSLT_LOADED_VERSION.
28
+ scan(/^(\d+)(\d\d)(\d\d)(?!\d)/).first.
29
+ collect(&:to_i).
30
+ join("."))
31
+ end
32
+
33
+ def compiled_libxslt_version
34
+ Gem::Version.new LIBXSLT_COMPILED_VERSION
23
35
  end
24
36
 
25
37
  def libxml2?
26
- defined?(LIBXML_VERSION)
38
+ defined?(LIBXML_COMPILED_VERSION)
27
39
  end
28
40
 
29
41
  def libxml2_using_system?
@@ -35,47 +47,63 @@ module Nokogiri
35
47
  end
36
48
 
37
49
  def warnings
38
- return [] unless libxml2?
50
+ warnings = []
39
51
 
40
- if compiled_parser_version != loaded_parser_version
41
- ["Nokogiri was built against LibXML version #{compiled_parser_version}, but has dynamically loaded #{loaded_parser_version}"]
42
- else
43
- []
52
+ if libxml2?
53
+ if compiled_libxml_version != loaded_libxml_version
54
+ warnings << "Nokogiri was built against libxml version #{compiled_libxml_version}, but has dynamically loaded #{loaded_libxml_version}"
55
+ end
56
+
57
+ if compiled_libxslt_version != loaded_libxslt_version
58
+ warnings << "Nokogiri was built against libxslt version #{compiled_libxslt_version}, but has dynamically loaded #{loaded_libxslt_version}"
59
+ end
44
60
  end
61
+
62
+ warnings
45
63
  end
46
64
 
47
65
  def to_hash
48
- hash_info = {}
49
- hash_info["warnings"] = []
50
- hash_info["nokogiri"] = Nokogiri::VERSION
51
- hash_info["ruby"] = {}
52
- hash_info["ruby"]["version"] = ::RUBY_VERSION
53
- hash_info["ruby"]["platform"] = ::RUBY_PLATFORM
54
- hash_info["ruby"]["description"] = ::RUBY_DESCRIPTION
55
- hash_info["ruby"]["engine"] = engine
56
- hash_info["ruby"]["jruby"] = jruby? if jruby?
66
+ {}.tap do |vi|
67
+ vi["warnings"] = []
68
+ vi["nokogiri"] = Nokogiri::VERSION
69
+ vi["ruby"] = {}.tap do |ruby|
70
+ ruby["version"] = ::RUBY_VERSION
71
+ ruby["platform"] = ::RUBY_PLATFORM
72
+ ruby["gem_platform"] = ::Gem::Platform.local.to_s
73
+ ruby["description"] = ::RUBY_DESCRIPTION
74
+ ruby["engine"] = engine
75
+ ruby["jruby"] = jruby? if jruby?
76
+ end
57
77
 
58
- if libxml2?
59
- hash_info["libxml"] = {}
60
- hash_info["libxml"]["binding"] = "extension"
61
- if libxml2_using_packaged?
62
- hash_info["libxml"]["source"] = "packaged"
63
- hash_info["libxml"]["libxml2_path"] = NOKOGIRI_LIBXML2_PATH
64
- hash_info["libxml"]["libxslt_path"] = NOKOGIRI_LIBXSLT_PATH
65
- hash_info["libxml"]["libxml2_patches"] = NOKOGIRI_LIBXML2_PATCHES
66
- hash_info["libxml"]["libxslt_patches"] = NOKOGIRI_LIBXSLT_PATCHES
67
- else
68
- hash_info["libxml"]["source"] = "system"
78
+ if libxml2?
79
+ vi["libxml"] = {}.tap do |libxml|
80
+ if libxml2_using_packaged?
81
+ libxml["source"] = "packaged"
82
+ libxml["patches"] = NOKOGIRI_LIBXML2_PATCHES
83
+ else
84
+ libxml["source"] = "system"
85
+ end
86
+ libxml["compiled"] = compiled_libxml_version.to_s
87
+ libxml["loaded"] = loaded_libxml_version.to_s
88
+ end
89
+
90
+ vi["libxslt"] = {}.tap do |libxslt|
91
+ if libxml2_using_packaged?
92
+ libxslt["source"] = "packaged"
93
+ libxslt["patches"] = NOKOGIRI_LIBXSLT_PATCHES
94
+ else
95
+ libxslt["source"] = "system"
96
+ end
97
+ libxslt["compiled"] = compiled_libxslt_version.to_s
98
+ libxslt["loaded"] = loaded_libxslt_version.to_s
99
+ end
100
+
101
+ vi["warnings"] = warnings
102
+ elsif jruby?
103
+ vi["xerces"] = Nokogiri::XERCES_VERSION
104
+ vi["nekohtml"] = Nokogiri::NEKO_VERSION
69
105
  end
70
- hash_info["libxml"]["compiled"] = compiled_parser_version
71
- hash_info["libxml"]["loaded"] = loaded_parser_version
72
- hash_info["warnings"] = warnings
73
- elsif jruby?
74
- hash_info["xerces"] = Nokogiri::XERCES_VERSION
75
- hash_info["nekohtml"] = Nokogiri::NEKO_VERSION
76
106
  end
77
-
78
- hash_info
79
107
  end
80
108
 
81
109
  def to_markdown
@@ -96,14 +124,27 @@ module Nokogiri
96
124
  def self.instance; @@instance; end
97
125
  end
98
126
 
99
- # More complete version information about libxml
100
- VERSION_INFO = VersionInfo.instance.to_hash
101
-
102
- def self.uses_libxml? # :nodoc:
103
- VersionInfo.instance.libxml2?
127
+ def self.uses_libxml?(requirement = nil) # :nodoc:
128
+ return false unless VersionInfo.instance.libxml2?
129
+ return true unless requirement
130
+ return Gem::Requirement.new(requirement).satisfied_by?(VersionInfo.instance.loaded_libxml_version)
104
131
  end
105
132
 
106
133
  def self.jruby? # :nodoc:
107
134
  VersionInfo.instance.jruby?
108
135
  end
136
+
137
+ # Ensure constants used in this file are loaded - see #1896
138
+ if Nokogiri.jruby?
139
+ require "nokogiri/jruby/dependencies"
140
+ end
141
+ begin
142
+ RUBY_VERSION =~ /(\d+\.\d+)/
143
+ require "nokogiri/#{$1}/nokogiri"
144
+ rescue LoadError
145
+ require "nokogiri/nokogiri"
146
+ end
147
+
148
+ # More complete version information about libxml
149
+ VERSION_INFO = VersionInfo.instance.to_hash
109
150
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri/xml/pp'
2
3
  require 'nokogiri/xml/parse_options'
3
4
  require 'nokogiri/xml/sax'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class Attr < Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -244,8 +245,8 @@ module Nokogiri
244
245
  #
245
246
  # For example:
246
247
  #
247
- # doc = Nokogiri::XML(open('somedoc.xml'))
248
- # Nokogiri::XML::Builder.with(doc.at('some_tag')) do |xml|
248
+ # doc = Nokogiri::XML(File.read('somedoc.xml'))
249
+ # Nokogiri::XML::Builder.with(doc.at_css('some_tag')) do |xml|
249
250
  # # ... Use normal builder methods here ...
250
251
  # xml.awesome # add the "awesome" tag below "some_tag"
251
252
  # end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class CDATA < Nokogiri::XML::Text
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class CharacterData < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ##
@@ -254,18 +255,12 @@ module Nokogiri
254
255
  ##
255
256
  # +JRuby+
256
257
  # Wraps Java's org.w3c.dom.document and returns Nokogiri::XML::Document
257
- def self.wrap document
258
- raise "JRuby only method" unless Nokogiri.jruby?
259
- return wrapJavaDocument(document)
260
- end
258
+ def self.wrap(document) end if false # native-ext provides Document.wrap
261
259
 
262
260
  ##
263
261
  # +JRuby+
264
262
  # Returns Java's org.w3c.dom.document of this Document.
265
- def to_java
266
- raise "JRuby only method" unless Nokogiri.jruby?
267
- return toJavaDocument()
268
- end
263
+ def to_java; end if false # JRuby provides #to_java
269
264
 
270
265
  private
271
266
  def self.empty_doc? string_or_io
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class DocumentFragment < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class DTD < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  ###
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class ElementDecl < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class EntityDecl < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class EntityReference < Nokogiri::XML::Node
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Nokogiri
2
3
  module XML
3
4
  class Namespace
@@ -1,6 +1,7 @@
1
1
  # encoding: UTF-8
2
- require 'stringio'
3
- require 'nokogiri/xml/node/save_options'
2
+ # frozen_string_literal: true
3
+ require "stringio"
4
+ require "nokogiri/xml/node/save_options"
4
5
 
5
6
  module Nokogiri
6
7
  module XML
@@ -56,49 +57,49 @@ module Nokogiri
56
57
  include Enumerable
57
58
 
58
59
  # Element node type, see Nokogiri::XML::Node#element?
59
- ELEMENT_NODE = 1
60
+ ELEMENT_NODE = 1
60
61
  # Attribute node type
61
- ATTRIBUTE_NODE = 2
62
+ ATTRIBUTE_NODE = 2
62
63
  # Text node type, see Nokogiri::XML::Node#text?
63
- TEXT_NODE = 3
64
+ TEXT_NODE = 3
64
65
  # CDATA node type, see Nokogiri::XML::Node#cdata?
65
66
  CDATA_SECTION_NODE = 4
66
67
  # Entity reference node type
67
- ENTITY_REF_NODE = 5
68
+ ENTITY_REF_NODE = 5
68
69
  # Entity node type
69
- ENTITY_NODE = 6
70
+ ENTITY_NODE = 6
70
71
  # PI node type
71
- PI_NODE = 7
72
+ PI_NODE = 7
72
73
  # Comment node type, see Nokogiri::XML::Node#comment?
73
- COMMENT_NODE = 8
74
+ COMMENT_NODE = 8
74
75
  # Document node type, see Nokogiri::XML::Node#xml?
75
- DOCUMENT_NODE = 9
76
+ DOCUMENT_NODE = 9
76
77
  # Document type node type
77
78
  DOCUMENT_TYPE_NODE = 10
78
79
  # Document fragment node type
79
80
  DOCUMENT_FRAG_NODE = 11
80
81
  # Notation node type
81
- NOTATION_NODE = 12
82
+ NOTATION_NODE = 12
82
83
  # HTML document node type, see Nokogiri::XML::Node#html?
83
84
  HTML_DOCUMENT_NODE = 13
84
85
  # DTD node type
85
- DTD_NODE = 14
86
+ DTD_NODE = 14
86
87
  # Element declaration type
87
- ELEMENT_DECL = 15
88
+ ELEMENT_DECL = 15
88
89
  # Attribute declaration type
89
- ATTRIBUTE_DECL = 16
90
+ ATTRIBUTE_DECL = 16
90
91
  # Entity declaration type
91
- ENTITY_DECL = 17
92
+ ENTITY_DECL = 17
92
93
  # Namespace declaration type
93
- NAMESPACE_DECL = 18
94
+ NAMESPACE_DECL = 18
94
95
  # XInclude start type
95
- XINCLUDE_START = 19
96
+ XINCLUDE_START = 19
96
97
  # XInclude end type
97
- XINCLUDE_END = 20
98
+ XINCLUDE_END = 20
98
99
  # DOCB document node type
99
100
  DOCB_DOCUMENT_NODE = 21
100
101
 
101
- def initialize name, document # :nodoc:
102
+ def initialize(name, document) # :nodoc:
102
103
  # ... Ya. This is empty on purpose.
103
104
  end
104
105
 
@@ -108,24 +109,18 @@ module Nokogiri
108
109
  document.decorate(self)
109
110
  end
110
111
 
112
+ # @!group Searching via XPath or CSS Queries
113
+
111
114
  ###
112
115
  # Search this node's immediate children using CSS selector +selector+
113
- def > selector
116
+ def >(selector)
114
117
  ns = document.root.namespaces
115
118
  xpath CSS.xpath_for(selector, :prefix => "./", :ns => ns).first
116
119
  end
117
120
 
118
- ###
119
- # Get the attribute value for the attribute +name+
120
- def [] name
121
- get(name.to_s)
122
- end
121
+ # @!endgroup
123
122
 
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
123
+ # @!group Manipulating Document Structure
129
124
 
130
125
  ###
131
126
  # Add +node_or_tags+ as a child of this Node.
@@ -134,7 +129,7 @@ module Nokogiri
134
129
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
135
130
  #
136
131
  # Also see related method +<<+.
137
- def add_child node_or_tags
132
+ def add_child(node_or_tags)
138
133
  node_or_tags = coerce(node_or_tags)
139
134
  if node_or_tags.is_a?(XML::NodeSet)
140
135
  node_or_tags.each { |n| add_child_node_and_reparent_attrs n }
@@ -151,7 +146,7 @@ module Nokogiri
151
146
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
152
147
  #
153
148
  # Also see related method +add_child+.
154
- def prepend_child node_or_tags
149
+ def prepend_child(node_or_tags)
155
150
  if first = children.first
156
151
  # Mimic the error add_child would raise.
157
152
  raise RuntimeError, "Document already has a root node" if document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
@@ -161,7 +156,6 @@ module Nokogiri
161
156
  end
162
157
  end
163
158
 
164
-
165
159
  ###
166
160
  # Add html around this node
167
161
  #
@@ -180,7 +174,7 @@ module Nokogiri
180
174
  # Returns self, to support chaining of calls (e.g., root << child1 << child2)
181
175
  #
182
176
  # Also see related method +add_child+.
183
- def << node_or_tags
177
+ def <<(node_or_tags)
184
178
  add_child node_or_tags
185
179
  self
186
180
  end
@@ -192,7 +186,7 @@ module Nokogiri
192
186
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
193
187
  #
194
188
  # Also see related method +before+.
195
- def add_previous_sibling node_or_tags
189
+ def add_previous_sibling(node_or_tags)
196
190
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
197
191
 
198
192
  add_sibling :previous, node_or_tags
@@ -205,7 +199,7 @@ module Nokogiri
205
199
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
206
200
  #
207
201
  # Also see related method +after+.
208
- def add_next_sibling node_or_tags
202
+ def add_next_sibling(node_or_tags)
209
203
  raise ArgumentError.new("A document may not have multiple root nodes.") if (parent && parent.document?) && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
210
204
 
211
205
  add_sibling :next, node_or_tags
@@ -218,7 +212,7 @@ module Nokogiri
218
212
  # Returns self, to support chaining of calls.
219
213
  #
220
214
  # Also see related method +add_previous_sibling+.
221
- def before node_or_tags
215
+ def before(node_or_tags)
222
216
  add_previous_sibling node_or_tags
223
217
  self
224
218
  end
@@ -230,7 +224,7 @@ module Nokogiri
230
224
  # Returns self, to support chaining of calls.
231
225
  #
232
226
  # Also see related method +add_next_sibling+.
233
- def after node_or_tags
227
+ def after(node_or_tags)
234
228
  add_next_sibling node_or_tags
235
229
  self
236
230
  end
@@ -242,7 +236,7 @@ module Nokogiri
242
236
  # Returns self.
243
237
  #
244
238
  # Also see related method +children=+
245
- def inner_html= node_or_tags
239
+ def inner_html=(node_or_tags)
246
240
  self.children = node_or_tags
247
241
  self
248
242
  end
@@ -254,7 +248,7 @@ module Nokogiri
254
248
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
255
249
  #
256
250
  # Also see related method +inner_html=+
257
- def children= node_or_tags
251
+ def children=(node_or_tags)
258
252
  node_or_tags = coerce(node_or_tags)
259
253
  children.unlink
260
254
  if node_or_tags.is_a?(XML::NodeSet)
@@ -272,13 +266,13 @@ module Nokogiri
272
266
  # Returns the reparented node (if +node_or_tags+ is a Node), or NodeSet (if +node_or_tags+ is a DocumentFragment, NodeSet, or string).
273
267
  #
274
268
  # Also see related method +swap+.
275
- def replace node_or_tags
269
+ def replace(node_or_tags)
276
270
  # We cannot replace a text node directly, otherwise libxml will return
277
271
  # an internal error at parser.c:13031, I don't know exactly why
278
272
  # libxml is trying to find a parent node that is an element or document
279
273
  # so I can't tell if this is bug in libxml or not. issue #775.
280
274
  if text?
281
- replacee = Nokogiri::XML::Node.new 'dummy', document
275
+ replacee = Nokogiri::XML::Node.new "dummy", document
282
276
  add_previous_sibling_node replacee
283
277
  unlink
284
278
  return replacee.replace node_or_tags
@@ -302,33 +296,98 @@ module Nokogiri
302
296
  # Returns self, to support chaining of calls.
303
297
  #
304
298
  # Also see related method +replace+.
305
- def swap node_or_tags
299
+ def swap(node_or_tags)
306
300
  replace node_or_tags
307
301
  self
308
302
  end
309
303
 
310
- alias :next :next_sibling
311
- alias :previous :previous_sibling
304
+ ####
305
+ # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
306
+ def content=(string)
307
+ self.native_content = encode_special_chars(string.to_s)
308
+ end
309
+
310
+ ###
311
+ # Set the parent Node for this Node
312
+ def parent=(parent_node)
313
+ parent_node.add_child(self)
314
+ parent_node
315
+ end
312
316
 
313
- # :stopdoc:
314
- # HACK: This is to work around an RDoc bug
315
- alias :next= :add_next_sibling
316
- # :startdoc:
317
+ ###
318
+ # Adds a default namespace supplied as a string +url+ href, to self.
319
+ # The consequence is as an xmlns attribute with supplied argument were
320
+ # present in parsed XML. A default namespace set with this method will
321
+ # now show up in #attributes, but when this node is serialized to XML an
322
+ # "xmlns" attribute will appear. See also #namespace and #namespace=
323
+ def default_namespace=(url)
324
+ add_namespace_definition(nil, url)
325
+ end
317
326
 
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
327
+ ###
328
+ # Set the default namespace on this node (as would be defined with an
329
+ # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
330
+ # a Namespace added this way will NOT be serialized as an xmlns attribute
331
+ # for this node. You probably want #default_namespace= instead, or perhaps
332
+ # #add_namespace_definition with a nil prefix argument.
333
+ def namespace=(ns)
334
+ return set_namespace(ns) unless ns
335
+
336
+ unless Nokogiri::XML::Namespace === ns
337
+ raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
338
+ end
339
+ if ns.document != document
340
+ raise ArgumentError, "namespace must be declared on the same document"
341
+ end
342
+
343
+ set_namespace ns
344
+ end
345
+
346
+ ###
347
+ # Do xinclude substitution on the subtree below node. If given a block, a
348
+ # Nokogiri::XML::ParseOptions object initialized from +options+, will be
349
+ # passed to it, allowing more convenient modification of the parser options.
350
+ def do_xinclude(options = XML::ParseOptions::DEFAULT_XML)
351
+ options = Nokogiri::XML::ParseOptions.new(options) if Integer === options
352
+
353
+ # give options to user
354
+ yield options if block_given?
355
+
356
+ # call c extension
357
+ process_xincludes(options.to_i)
358
+ end
359
+
360
+ alias :next :next_sibling
361
+ alias :previous :previous_sibling
362
+ alias :next= :add_next_sibling
363
+ alias :previous= :add_previous_sibling
364
+ alias :remove :unlink
365
+ alias :name= :node_name=
366
+ alias :add_namespace :add_namespace_definition
367
+
368
+ # @!endgroup
369
+
370
+ alias :text :content
371
+ alias :inner_text :content
372
+ alias :name :node_name
373
+ alias :type :node_type
374
+ alias :to_str :text
375
+ alias :clone :dup
376
+ alias :elements :element_children
377
+
378
+ # @!group Working With Node Attributes
379
+
380
+ ###
381
+ # Get the attribute value for the attribute +name+
382
+ def [](name)
383
+ get(name.to_s)
384
+ end
385
+
386
+ ###
387
+ # Set the attribute value for the attribute +name+ to +value+
388
+ def []=(name, value)
389
+ set name.to_s, value.to_s
390
+ end
332
391
 
333
392
  ####
334
393
  # Returns a hash containing the node's attributes. The key is
@@ -337,9 +396,9 @@ module Nokogiri
337
396
  # If you need to distinguish attributes with the same name, with different namespaces
338
397
  # use #attribute_nodes instead.
339
398
  def attributes
340
- Hash[attribute_nodes.map { |node|
341
- [node.node_name, node]
342
- }]
399
+ attribute_nodes.each_with_object({}) do |node, hash|
400
+ hash[node.node_name] = node
401
+ end
343
402
  end
344
403
 
345
404
  ###
@@ -348,6 +407,12 @@ module Nokogiri
348
407
  attribute_nodes.map(&:value)
349
408
  end
350
409
 
410
+ ###
411
+ # Does this Node's attributes include <value>
412
+ def value?(value)
413
+ values.include? value
414
+ end
415
+
351
416
  ###
352
417
  # Get the attribute names for this Node.
353
418
  def keys
@@ -363,82 +428,366 @@ module Nokogiri
363
428
  end
364
429
 
365
430
  ###
366
- # Get the list of class names of this Node, without
367
- # deduplication or sorting.
431
+ # Remove the attribute named +name+
432
+ def remove_attribute(name)
433
+ attr = attributes[name].remove if key? name
434
+ clear_xpath_context if Nokogiri.jruby?
435
+ attr
436
+ end
437
+
438
+ # Get the CSS class names of a Node.
439
+ #
440
+ # This is a convenience function and is equivalent to:
441
+ # node.kwattr_values("class")
442
+ #
443
+ # @see #kwattr_values
444
+ # @see #add_class
445
+ # @see #append_class
446
+ # @see #remove_class
447
+ #
448
+ # @return [Array<String>]
449
+ #
450
+ # The CSS classes present in the Node's +class+ attribute. If
451
+ # the attribute is empty or non-existent, the return value is
452
+ # an empty array.
453
+ #
454
+ # @example
455
+ # node # => <div class="section title header"></div>
456
+ # node.classes # => ["section", "title", "header"]
457
+ #
368
458
  def classes
369
- self['class'].to_s.scan(/\S+/)
459
+ kwattr_values("class")
370
460
  end
371
461
 
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.
462
+ # Ensure HTML CSS classes are present on a +Node+. Any CSS
463
+ # classes in +names+ that already exist in the +Node+'s +class+
464
+ # attribute are _not_ added. Note that any existing duplicates
465
+ # in the +class+ attribute are not removed. Compare with
466
+ # {#append_class}.
467
+ #
468
+ # This is a convenience function and is equivalent to:
469
+ # node.kwattr_add("class", names)
470
+ #
471
+ # @see #kwattr_add
472
+ # @see #classes
473
+ # @see #append_class
474
+ # @see #remove_class
475
+ #
476
+ # @param names [String, Array<String>]
477
+ #
478
+ # CSS class names to be added to the Node's +class+
479
+ # attribute. May be a string containing whitespace-delimited
480
+ # names, or an Array of String names. Any class names already
481
+ # present will not be added. Any class names not present will
482
+ # be added. If no +class+ attribute exists, one is created.
483
+ #
484
+ # @return [Node] Returns +self+ for ease of chaining method calls.
485
+ #
486
+ # @example Ensure that a +Node+ has CSS class "section"
487
+ # node # => <div></div>
488
+ # node.add_class("section") # => <div class="section"></div>
489
+ # node.add_class("section") # => <div class="section"></div> # duplicate not added
490
+ #
491
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via a String argument.
492
+ # node # => <div class="section section"></div>
493
+ # node.add_class("section header") # => <div class="section section header"></div>
494
+ # # Note that the CSS class "section" is not added because it is already present.
495
+ # # Note also that the pre-existing duplicate CSS class "section" is not removed.
496
+ #
497
+ # @example Ensure that a +Node+ has CSS classes "section" and "header", via an Array argument.
498
+ # node # => <div></div>
499
+ # node.add_class(["section", "header"]) # => <div class="section header"></div>
500
+ #
501
+ def add_class(names)
502
+ kwattr_add("class", names)
503
+ end
504
+
505
+ # Add HTML CSS classes to a +Node+, regardless of
506
+ # duplication. Compare with {#add_class}.
507
+ #
508
+ # This is a convenience function and is equivalent to:
509
+ # node.kwattr_append("class", names)
510
+ #
511
+ # @see #kwattr_append
512
+ # @see #classes
513
+ # @see #add_class
514
+ # @see #remove_class
515
+ #
516
+ # @param names [String, Array<String>]
517
+ #
518
+ # CSS class names to be appended to the Node's +class+
519
+ # attribute. May be a string containing whitespace-delimited
520
+ # names, or an Array of String names. All class names passed
521
+ # in will be appended to the +class+ attribute even if they
522
+ # are already present in the attribute value. If no +class+
523
+ # attribute exists, one is created.
524
+ #
525
+ # @return [Node] Returns +self+ for ease of chaining method calls.
526
+ #
527
+ # @example Append "section" to a +Node+'s CSS +class+ attriubute
528
+ # node # => <div></div>
529
+ # node.append_class("section") # => <div class="section"></div>
530
+ # node.append_class("section") # => <div class="section section"></div> # duplicate added!
531
+ #
532
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via a String argument.
533
+ # node # => <div class="section section"></div>
534
+ # node.append_class("section header") # => <div class="section section section header"></div>
535
+ # # Note that the CSS class "section" is appended even though it is already present.
536
+ #
537
+ # @example Append "section" and "header" to a +Node+'s CSS +class+ attribute, via an Array argument.
538
+ # node # => <div></div>
539
+ # node.append_class(["section", "header"]) # => <div class="section header"></div>
540
+ # node.append_class(["section", "header"]) # => <div class="section header section header"></div>
541
+ #
542
+ def append_class(names)
543
+ kwattr_append("class", names)
544
+ end
545
+
546
+ # Remove HTML CSS classes from a +Node+. Any CSS classes in +names+ that
547
+ # exist in the +Node+'s +class+ attribute are removed, including any
548
+ # multiple entries.
549
+ #
550
+ # If no CSS classes remain after this operation, or if +names+ is
551
+ # +nil+, the +class+ attribute is deleted from the node.
552
+ #
553
+ # This is a convenience function and is equivalent to:
554
+ # node.kwattr_remove("class", names)
555
+ #
556
+ # @see #kwattr_remove
557
+ # @see #classes
558
+ # @see #add_class
559
+ # @see #append_class
560
+ #
561
+ # @param names [String, Array<String>]
562
+ #
563
+ # CSS class names to be removed from the Node's +class+ attribute. May
564
+ # be a string containing whitespace-delimited names, or an Array of
565
+ # String names. Any class names already present will be removed. If no
566
+ # CSS classes remain, the +class+ attribute is deleted.
567
+ #
568
+ # @return [Node] Returns +self+ for ease of chaining method calls.
569
+ #
570
+ # @example
571
+ # node # => <div class="section header"></div>
572
+ # node.remove_class("section") # => <div class="header"></div>
573
+ # node.remove_class("header") # => <div></div> # attribute is deleted when empty
574
+ #
575
+ def remove_class(names = nil)
576
+ kwattr_remove("class", names)
577
+ end
578
+
579
+ # Retrieve values from a keyword attribute of a Node.
580
+ #
581
+ # A "keyword attribute" is a node attribute that contains a set
582
+ # of space-delimited values. Perhaps the most familiar example
583
+ # of this is the HTML +class+ attribute used to contain CSS
584
+ # classes. But other keyword attributes exist, for instance
585
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
586
+ #
587
+ # @see #classes
588
+ # @see #kwattr_add
589
+ # @see #kwattr_append
590
+ # @see #kwattr_remove
591
+ #
592
+ # @param attribute_name [String] The name of the keyword attribute to be inspected.
593
+ #
594
+ # @return [Array<String>]
595
+ #
596
+ # The values present in the Node's +attribute_name+
597
+ # attribute. If the attribute is empty or non-existent, the
598
+ # return value is an empty array.
377
599
  #
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(' ')
600
+ # @example
601
+ # node # => <a rel="nofollow noopener external">link</a>
602
+ # node.kwattr_values("rel") # => ["nofollow", "noopener", "external"]
603
+ #
604
+ # @since v1.11.0
605
+ #
606
+ def kwattr_values(attribute_name)
607
+ keywordify(get_attribute(attribute_name) || [])
608
+ end
609
+
610
+ # Ensure that values are present in a keyword attribute.
611
+ #
612
+ # Any values in +keywords+ that already exist in the +Node+'s
613
+ # attribute values are _not_ added. Note that any existing
614
+ # duplicates in the attribute values are not removed. Compare
615
+ # with {#kwattr_append}.
616
+ #
617
+ # A "keyword attribute" is a node attribute that contains a set
618
+ # of space-delimited values. Perhaps the most familiar example
619
+ # of this is the HTML +class+ attribute used to contain CSS
620
+ # classes. But other keyword attributes exist, for instance
621
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
622
+ #
623
+ # @see #add_class
624
+ # @see #kwattr_values
625
+ # @see #kwattr_append
626
+ # @see #kwattr_remove
627
+ #
628
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
629
+ #
630
+ # @param keywords [String, Array<String>]
631
+ #
632
+ # Keywords to be added to the attribute named
633
+ # +attribute_name+. May be a string containing
634
+ # whitespace-delimited values, or an Array of String
635
+ # values. Any values already present will not be added. Any
636
+ # values not present will be added. If the named attribute
637
+ # does not exist, it is created.
638
+ #
639
+ # @return [Node] Returns +self+ for ease of chaining method calls.
640
+ #
641
+ # @example Ensure that a +Node+ has "nofollow" in its +rel+ attribute.
642
+ # node # => <a></a>
643
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a>
644
+ # node.kwattr_add("rel", "nofollow") # => <a rel="nofollow"></a> # duplicate not added
645
+ #
646
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via a String argument.
647
+ # node # => <a rel="nofollow nofollow"></a>
648
+ # node.kwattr_add("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
649
+ # # Note that "nofollow" is not added because it is already present.
650
+ # # Note also that the pre-existing duplicate "nofollow" is not removed.
651
+ #
652
+ # @example Ensure that a +Node+ has "nofollow" and "noreferrer" in its +rel+ attribute, via an Array argument.
653
+ # node # => <a></a>
654
+ # node.kwattr_add("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
655
+ #
656
+ # @since v1.11.0
657
+ #
658
+ def kwattr_add(attribute_name, keywords)
659
+ keywords = keywordify(keywords)
660
+ current_kws = kwattr_values(attribute_name)
661
+ new_kws = (current_kws + (keywords - current_kws)).join(" ")
662
+ set_attribute(attribute_name, new_kws)
383
663
  self
384
664
  end
385
665
 
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.
666
+ # Add keywords to a Node's keyword attribute, regardless of
667
+ # duplication. Compare with {#kwattr_add}.
668
+ #
669
+ # A "keyword attribute" is a node attribute that contains a set
670
+ # of space-delimited values. Perhaps the most familiar example
671
+ # of this is the HTML +class+ attribute used to contain CSS
672
+ # classes. But other keyword attributes exist, for instance
673
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
674
+ #
675
+ # @see #append_class
676
+ # @see #kwattr_values
677
+ # @see #kwattr_add
678
+ # @see #kwattr_remove
679
+ #
680
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
681
+ #
682
+ # @param keywords [String, Array<String>]
683
+ #
684
+ # Keywords to be added to the attribute named
685
+ # +attribute_name+. May be a string containing
686
+ # whitespace-delimited values, or an Array of String
687
+ # values. All values passed in will be appended to the named
688
+ # attribute even if they are already present in the
689
+ # attribute. If the named attribute does not exist, it is
690
+ # created.
691
+ #
692
+ # @return [Node] Returns +self+ for ease of chaining method calls.
693
+ #
694
+ # @example Append "nofollow" to the +rel+ attribute.
695
+ # node # => <a></a>
696
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow"></a>
697
+ # node.kwattr_append("rel", "nofollow") # => <a rel="nofollow nofollow"></a> # duplicate added!
391
698
  #
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(' ')
699
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via a String argument.
700
+ # node # => <a rel="nofollow"></a>
701
+ # node.kwattr_append("rel", "nofollow noreferrer") # => <a rel="nofollow nofollow noreferrer"></a>
702
+ # # Note that "nofollow" is appended even though it is already present.
703
+ #
704
+ # @example Append "nofollow" and "noreferrer" to the +rel+ attribute, via an Array argument.
705
+ # node # => <a></a>
706
+ # node.kwattr_append("rel", ["nofollow", "noreferrer"]) # => <a rel="nofollow noreferrer"></a>
707
+ #
708
+ # @since v1.11.0
709
+ #
710
+ def kwattr_append(attribute_name, keywords)
711
+ keywords = keywordify(keywords)
712
+ current_kws = kwattr_values(attribute_name)
713
+ new_kws = (current_kws + keywords).join(" ")
714
+ set_attribute(attribute_name, new_kws)
396
715
  self
397
716
  end
398
717
 
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.
718
+ # Remove keywords from a keyword attribute. Any matching
719
+ # keywords that exist in the named attribute are removed,
720
+ # including any multiple entries.
403
721
  #
404
- # More than one class may be removed at a time, separated by a
405
- # space.
722
+ # If no keywords remain after this operation, or if +keywords+
723
+ # is +nil+, the attribute is deleted from the node.
406
724
  #
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
725
+ # A "keyword attribute" is a node attribute that contains a set
726
+ # of space-delimited values. Perhaps the most familiar example
727
+ # of this is the HTML +class+ attribute used to contain CSS
728
+ # classes. But other keyword attributes exist, for instance
729
+ # [`rel`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel).
730
+ #
731
+ # @see #remove_class
732
+ # @see #kwattr_values
733
+ # @see #kwattr_add
734
+ # @see #kwattr_append
735
+ #
736
+ # @param attribute_name [String] The name of the keyword attribute to be modified.
737
+ #
738
+ # @param keywords [String, Array<String>]
739
+ #
740
+ # Keywords to be removed from the attribute named
741
+ # +attribute_name+. May be a string containing
742
+ # whitespace-delimited values, or an Array of String
743
+ # values. Any keywords present in the named attribute will be
744
+ # removed. If no keywords remain, or if +keywords+ is nil, the
745
+ # attribute is deleted.
746
+ #
747
+ # @return [Node] Returns +self+ for ease of chaining method calls.
748
+ #
749
+ # @example
750
+ # node # => <a rel="nofollow noreferrer">link</a>
751
+ # node.kwattr_remove("rel", "nofollow") # => <a rel="noreferrer">link</a>
752
+ # node.kwattr_remove("rel", "noreferrer") # => <a>link</a> # attribute is deleted when empty
753
+ #
754
+ # @since v1.11.0
755
+ #
756
+ def kwattr_remove(attribute_name, keywords)
757
+ if keywords.nil?
758
+ remove_attribute(attribute_name)
759
+ return self
760
+ end
761
+
762
+ keywords = keywordify(keywords)
763
+ current_kws = kwattr_values(attribute_name)
764
+ new_kws = current_kws - keywords
765
+ if new_kws.empty?
766
+ remove_attribute(attribute_name)
417
767
  else
418
- delete "class"
768
+ set_attribute(attribute_name, new_kws.join(" "))
419
769
  end
420
770
  self
421
771
  end
422
772
 
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
773
  alias :delete :remove_attribute
774
+ alias :get_attribute :[]
775
+ alias :attr :[]
776
+ alias :set_attribute :[]=
777
+ alias :has_attribute? :key?
778
+
779
+ # @!endgroup
431
780
 
432
781
  ###
433
782
  # Returns true if this Node matches +selector+
434
- def matches? selector
783
+ def matches?(selector)
435
784
  ancestors.last.search(selector).include?(self)
436
785
  end
437
786
 
438
787
  ###
439
788
  # Create a DocumentFragment containing +tags+ that is relative to _this_
440
789
  # context node.
441
- def fragment tags
790
+ def fragment(tags)
442
791
  type = document.html? ? Nokogiri::HTML : Nokogiri::XML
443
792
  type::DocumentFragment.new(document, tags, self)
444
793
  end
@@ -447,7 +796,7 @@ module Nokogiri
447
796
  # Parse +string_or_io+ as a document fragment within the context of
448
797
  # *this* node. Returns a XML::NodeSet containing the nodes parsed from
449
798
  # +string_or_io+.
450
- def parse string_or_io, options = nil
799
+ def parse(string_or_io, options = nil)
451
800
  ##
452
801
  # When the current node is unparented and not an element node, use the
453
802
  # document as the parsing context instead. Otherwise, the in-context
@@ -481,19 +830,6 @@ module Nokogiri
481
830
  node_set
482
831
  end
483
832
 
484
- ####
485
- # Set the Node's content to a Text node containing +string+. The string gets XML escaped, not interpreted as markup.
486
- def content= string
487
- self.native_content = encode_special_chars(string.to_s)
488
- end
489
-
490
- ###
491
- # Set the parent Node for this Node
492
- def parent= parent_node
493
- parent_node.add_child(self)
494
- parent_node
495
- end
496
-
497
833
  ###
498
834
  # Returns a Hash of +{prefix => value}+ for all namespaces on this
499
835
  # node and its ancestors.
@@ -509,10 +845,11 @@ module Nokogiri
509
845
  # default namespaces set on ancestor will NOT be, even if self
510
846
  # has no explicit default namespace.
511
847
  def namespaces
512
- Hash[namespace_scopes.map { |nd|
513
- key = ['xmlns', nd.prefix].compact.join(':')
514
- [key, nd.href]
515
- }]
848
+ namespace_scopes.each_with_object({}) do |ns, hash|
849
+ prefix = ns.prefix
850
+ key = prefix ? "xmlns:#{prefix}" : "xmlns"
851
+ hash[key] = ns.href
852
+ end
516
853
  end
517
854
 
518
855
  # Returns true if this is a Comment
@@ -574,6 +911,7 @@ module Nokogiri
574
911
  def element?
575
912
  type == ELEMENT_NODE
576
913
  end
914
+
577
915
  alias :elem? :element?
578
916
 
579
917
  ###
@@ -584,7 +922,7 @@ module Nokogiri
584
922
  end
585
923
 
586
924
  # Get the inner_html for this node's Node#children
587
- def inner_html *args
925
+ def inner_html(*args)
588
926
  children.map { |x| x.to_html(*args) }.join
589
927
  end
590
928
 
@@ -592,13 +930,13 @@ module Nokogiri
592
930
  def css_path
593
931
  path.split(/\//).map { |part|
594
932
  part.length == 0 ? nil : part.gsub(/\[(\d+)\]/, ':nth-of-type(\1)')
595
- }.compact.join(' > ')
933
+ }.compact.join(" > ")
596
934
  end
597
935
 
598
936
  ###
599
937
  # Get a list of ancestor Node for this Node. If +selector+ is given,
600
938
  # the ancestors must match +selector+
601
- def ancestors selector = nil
939
+ def ancestors(selector = nil)
602
940
  return NodeSet.new(document) unless respond_to?(:parent)
603
941
  return NodeSet.new(document) unless parent
604
942
 
@@ -619,57 +957,38 @@ module Nokogiri
619
957
  })
620
958
  end
621
959
 
622
- ###
623
- # Adds a default namespace supplied as a string +url+ href, to self.
624
- # The consequence is as an xmlns attribute with supplied argument were
625
- # present in parsed XML. A default namespace set with this method will
626
- # now show up in #attributes, but when this node is serialized to XML an
627
- # "xmlns" attribute will appear. See also #namespace and #namespace=
628
- def default_namespace= url
629
- add_namespace_definition(nil, url)
630
- end
631
- alias :add_namespace :add_namespace_definition
632
-
633
- ###
634
- # Set the default namespace on this node (as would be defined with an
635
- # "xmlns=" attribute in XML source), as a Namespace object +ns+. Note that
636
- # a Namespace added this way will NOT be serialized as an xmlns attribute
637
- # for this node. You probably want #default_namespace= instead, or perhaps
638
- # #add_namespace_definition with a nil prefix argument.
639
- def namespace= ns
640
- return set_namespace(ns) unless ns
641
-
642
- unless Nokogiri::XML::Namespace === ns
643
- raise TypeError, "#{ns.class} can't be coerced into Nokogiri::XML::Namespace"
644
- end
645
- if ns.document != document
646
- raise ArgumentError, 'namespace must be declared on the same document'
647
- end
648
-
649
- set_namespace ns
650
- end
651
-
652
960
  ####
653
961
  # Yields self and all children to +block+ recursively.
654
- def traverse &block
655
- children.each{|j| j.traverse(&block) }
962
+ def traverse(&block)
963
+ children.each { |j| j.traverse(&block) }
656
964
  block.call(self)
657
965
  end
658
966
 
659
967
  ###
660
968
  # Accept a visitor. This method calls "visit" on +visitor+ with self.
661
- def accept visitor
969
+ def accept(visitor)
662
970
  visitor.visit(self)
663
971
  end
664
972
 
665
973
  ###
666
974
  # Test to see if this Node is equal to +other+
667
- def == other
975
+ def ==(other)
668
976
  return false unless other
669
977
  return false unless other.respond_to?(:pointer_id)
670
978
  pointer_id == other.pointer_id
671
979
  end
672
980
 
981
+ ###
982
+ # Compare two Node objects with respect to their Document. Nodes from
983
+ # different documents cannot be compared.
984
+ def <=>(other)
985
+ return nil unless other.is_a?(Nokogiri::XML::Node)
986
+ return nil unless document == other.document
987
+ compare other
988
+ end
989
+
990
+ # @!group Serialization and Generating Output
991
+
673
992
  ###
674
993
  # Serialize Node using +options+. Save options can also be set using a
675
994
  # block. See SaveOptions.
@@ -684,17 +1003,17 @@ module Nokogiri
684
1003
  # config.format.as_xml
685
1004
  # end
686
1005
  #
687
- def serialize *args, &block
1006
+ def serialize(*args, &block)
688
1007
  options = args.first.is_a?(Hash) ? args.shift : {
689
- :encoding => args[0],
690
- :save_with => args[1]
1008
+ :encoding => args[0],
1009
+ :save_with => args[1],
691
1010
  }
692
1011
 
693
1012
  encoding = options[:encoding] || document.encoding
694
1013
  options[:encoding] = encoding
695
1014
 
696
1015
  outstring = String.new
697
- outstring.force_encoding(Encoding.find(encoding || 'utf-8'))
1016
+ outstring.force_encoding(Encoding.find(encoding || "utf-8"))
698
1017
  io = StringIO.new(outstring)
699
1018
  write_to io, options, &block
700
1019
  io.string
@@ -707,7 +1026,7 @@ module Nokogiri
707
1026
  #
708
1027
  # See Node#write_to for a list of +options+. For formatted output,
709
1028
  # use Node#to_xhtml instead.
710
- def to_html options = {}
1029
+ def to_html(options = {})
711
1030
  to_format SaveOptions::DEFAULT_HTML, options
712
1031
  end
713
1032
 
@@ -717,7 +1036,7 @@ module Nokogiri
717
1036
  # doc.to_xml(:indent => 5, :encoding => 'UTF-8')
718
1037
  #
719
1038
  # See Node#write_to for a list of +options+
720
- def to_xml options = {}
1039
+ def to_xml(options = {})
721
1040
  options[:save_with] ||= SaveOptions::DEFAULT_XML
722
1041
  serialize(options)
723
1042
  end
@@ -728,7 +1047,7 @@ module Nokogiri
728
1047
  # doc.to_xhtml(:indent => 5, :encoding => 'UTF-8')
729
1048
  #
730
1049
  # See Node#write_to for a list of +options+
731
- def to_xhtml options = {}
1050
+ def to_xhtml(options = {})
732
1051
  to_format SaveOptions::DEFAULT_XHTML, options
733
1052
  end
734
1053
 
@@ -749,29 +1068,34 @@ module Nokogiri
749
1068
  #
750
1069
  # node.write_to(io, :indent_text => '-', :indent => 2)
751
1070
  #
752
- def write_to io, *options
753
- options = options.first.is_a?(Hash) ? options.shift : {}
754
- encoding = options[:encoding] || options[0]
1071
+ def write_to(io, *options)
1072
+ options = options.first.is_a?(Hash) ? options.shift : {}
1073
+ encoding = options[:encoding] || options[0]
755
1074
  if Nokogiri.jruby?
756
- save_options = options[:save_with] || options[1]
757
- indent_times = options[:indent] || 0
1075
+ save_options = options[:save_with] || options[1]
1076
+ indent_times = options[:indent] || 0
758
1077
  else
759
- save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
760
- indent_times = options[:indent] || 2
1078
+ save_options = options[:save_with] || options[1] || SaveOptions::FORMAT
1079
+ indent_times = options[:indent] || 2
761
1080
  end
762
- indent_text = options[:indent_text] || ' '
1081
+ indent_text = options[:indent_text] || " "
1082
+
1083
+ # Any string times 0 returns an empty string. Therefore, use the same
1084
+ # string instead of generating a new empty string for every node with
1085
+ # zero indentation.
1086
+ indentation = indent_times.zero? ? "" : (indent_text * indent_times)
763
1087
 
764
1088
  config = SaveOptions.new(save_options.to_i)
765
1089
  yield config if block_given?
766
1090
 
767
- native_write_to(io, encoding, indent_text * indent_times, config.options)
1091
+ native_write_to(io, encoding, indentation, config.options)
768
1092
  end
769
1093
 
770
1094
  ###
771
1095
  # Write Node as HTML to +io+ with +options+
772
1096
  #
773
1097
  # See Node#write_to for a list of +options+
774
- def write_html_to io, options = {}
1098
+ def write_html_to(io, options = {})
775
1099
  write_format_to SaveOptions::DEFAULT_HTML, io, options
776
1100
  end
777
1101
 
@@ -779,7 +1103,7 @@ module Nokogiri
779
1103
  # Write Node as XHTML to +io+ with +options+
780
1104
  #
781
1105
  # See Node#write_to for a list of +options+
782
- def write_xhtml_to io, options = {}
1106
+ def write_xhtml_to(io, options = {})
783
1107
  write_format_to SaveOptions::DEFAULT_XHTML, io, options
784
1108
  end
785
1109
 
@@ -789,35 +1113,12 @@ module Nokogiri
789
1113
  # doc.write_xml_to io, :encoding => 'UTF-8'
790
1114
  #
791
1115
  # See Node#write_to for a list of options
792
- def write_xml_to io, options = {}
1116
+ def write_xml_to(io, options = {})
793
1117
  options[:save_with] ||= SaveOptions::DEFAULT_XML
794
1118
  write_to io, options
795
1119
  end
796
1120
 
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
804
- end
805
-
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?
815
-
816
- # call c extension
817
- process_xincludes(options.to_i)
818
- end
819
-
820
- def canonicalize(mode=XML::XML_C14N_1_0,inclusive_namespaces=nil,with_comments=false)
1121
+ def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comments = false)
821
1122
  c14n_root = self
822
1123
  document.canonicalize(mode, inclusive_namespaces, with_comments) do |node, parent|
823
1124
  tn = node.is_a?(XML::Node) ? node : parent
@@ -825,16 +1126,29 @@ module Nokogiri
825
1126
  end
826
1127
  end
827
1128
 
1129
+ # @!endgroup
1130
+
828
1131
  private
829
1132
 
830
- def add_sibling next_or_previous, node_or_tags
1133
+ def keywordify(keywords)
1134
+ case keywords
1135
+ when Enumerable
1136
+ return keywords
1137
+ when String
1138
+ return keywords.scan(/\S+/)
1139
+ else
1140
+ raise ArgumentError.new("Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}")
1141
+ end
1142
+ end
1143
+
1144
+ def add_sibling(next_or_previous, node_or_tags)
831
1145
  impl = (next_or_previous == :next) ? :add_next_sibling_node : :add_previous_sibling_node
832
- iter = (next_or_previous == :next) ? :reverse_each : :each
1146
+ iter = (next_or_previous == :next) ? :reverse_each : :each
833
1147
 
834
1148
  node_or_tags = coerce node_or_tags
835
1149
  if node_or_tags.is_a?(XML::NodeSet)
836
1150
  if text?
837
- pivot = Nokogiri::XML::Node.new 'dummy', document
1151
+ pivot = Nokogiri::XML::Node.new "dummy", document
838
1152
  send impl, pivot
839
1153
  else
840
1154
  pivot = self
@@ -847,17 +1161,18 @@ module Nokogiri
847
1161
  node_or_tags
848
1162
  end
849
1163
 
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]
1164
+ USING_LIBXML_WITH_BROKEN_SERIALIZATION = Nokogiri.uses_libxml?("~> 2.6.0").freeze
1165
+ private_constant :USING_LIBXML_WITH_BROKEN_SERIALIZATION
1166
+
1167
+ def to_format(save_option, options)
1168
+ return dump_html if USING_LIBXML_WITH_BROKEN_SERIALIZATION
853
1169
 
854
1170
  options[:save_with] = save_option unless options[:save_with]
855
1171
  serialize(options)
856
1172
  end
857
1173
 
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]
1174
+ def write_format_to(save_option, io, options)
1175
+ return (io << dump_html) if USING_LIBXML_WITH_BROKEN_SERIALIZATION
861
1176
 
862
1177
  options[:save_with] ||= save_option
863
1178
  write_to io, options
@@ -867,7 +1182,7 @@ module Nokogiri
867
1182
  [:name, :namespace, :attribute_nodes, :children]
868
1183
  end
869
1184
 
870
- def coerce data # :nodoc:
1185
+ def coerce(data)
871
1186
  case data
872
1187
  when XML::NodeSet
873
1188
  return data
@@ -888,9 +1203,9 @@ Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
888
1203
  end
889
1204
 
890
1205
  # @private
891
- IMPLIED_XPATH_CONTEXTS = [ './/'.freeze ].freeze # :nodoc:
1206
+ IMPLIED_XPATH_CONTEXTS = [".//".freeze].freeze
892
1207
 
893
- def add_child_node_and_reparent_attrs node # :nodoc:
1208
+ def add_child_node_and_reparent_attrs(node)
894
1209
  add_child_node node
895
1210
  node.attribute_nodes.find_all { |a| a.name =~ /:/ }.each do |attr_node|
896
1211
  attr_node.remove