nokolexbor 0.3.0-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 312711b3230257af71217ed687988d668fbce13377e554b8b28376ebf72d3e51
4
+ data.tar.gz: 6ef1c2818ced12759d5f77bbe4b6769186733f8a2e3c5bc755e681caf69eb17f
5
+ SHA512:
6
+ metadata.gz: 9bd779787a696b7287bbf3d4e94655bee905efb67ef9dce15b75c221f0d94afb471f9fdaef941c60f4779ea1ba8108ce9a55c8aa99d97197d36b9c9caf06cec6
7
+ data.tar.gz: 4131a8c2d81edb47ec2447e56d8d51f460fa9b1bf713fbfbcf7c64253a78ac4f6030506d478df9f412b0d7eee598cf59e44413102a721cf1ed336c2476f7c7ed
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ class Attribute
5
+ attr_accessor :name
6
+ attr_accessor :value
7
+
8
+ def initialize(name, value)
9
+ @name = name
10
+ @value = value
11
+ end
12
+
13
+ alias_method :text, :value
14
+ alias_method :content, :value
15
+ alias_method :to_s, :value
16
+ alias_method :to_str, :value
17
+ end
18
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ class Document < Nokolexbor::Node
5
+ def create_element(name, *contents_or_attrs, &block)
6
+ elm = Nokolexbor::Element.new(name, self, &block)
7
+ contents_or_attrs.each do |arg|
8
+ case arg
9
+ when Hash
10
+ arg.each do |k, v|
11
+ elm[k.to_s] = v.to_s
12
+ end
13
+ else
14
+ elm.content = arg
15
+ end
16
+ end
17
+ elm
18
+ end
19
+
20
+ # Create a Text Node with +string+
21
+ def create_text_node(string, &block)
22
+ Nokolexbor::Text.new(string.to_s, self, &block)
23
+ end
24
+
25
+ # Create a CDATA Node containing +string+
26
+ def create_cdata(string, &block)
27
+ Nokolexbor::CDATA.new(string.to_s, self, &block)
28
+ end
29
+
30
+ # Create a Comment Node containing +string+
31
+ def create_comment(string, &block)
32
+ Nokolexbor::Comment.new(string.to_s, self, &block)
33
+ end
34
+
35
+ # A reference to +self+
36
+ def document
37
+ self
38
+ end
39
+
40
+ def meta_encoding
41
+ if (meta = at_css("meta[charset]"))
42
+ meta[:charset]
43
+ elsif (meta = meta_content_type)
44
+ meta["content"][/charset\s*=\s*([\w-]+)/i, 1]
45
+ end
46
+ end
47
+
48
+ def meta_encoding=(encoding)
49
+ if (meta = meta_content_type)
50
+ meta["content"] = format("text/html; charset=%s", encoding)
51
+ encoding
52
+ elsif (meta = at_css("meta[charset]"))
53
+ meta["charset"] = encoding
54
+ else
55
+ meta = Nokolexbor::Node.new("meta", self)
56
+ meta["charset"] = encoding
57
+
58
+ if (head = at_css("head"))
59
+ head.prepend_child(meta)
60
+ else
61
+ set_metadata_element(meta)
62
+ end
63
+ encoding
64
+ end
65
+ end
66
+
67
+ def meta_content_type
68
+ xpath("//meta[@http-equiv and boolean(@content)]").find do |node|
69
+ node["http-equiv"] =~ /\AContent-Type\z/i
70
+ end
71
+ end
72
+ private :meta_content_type
73
+
74
+ def set_metadata_element(element)
75
+ if (head = at_css("head"))
76
+ head << element
77
+ elsif (html = at_css("html"))
78
+ head = html.prepend_child(Nokolexbor::Node.new("head", self))
79
+ head.prepend_child(element)
80
+ elsif (first = children.find do |node|
81
+ case node
82
+ when Nokolexbor::Node
83
+ true
84
+ end
85
+ end)
86
+ # We reach here only if the underlying document model
87
+ # allows <html>/<head> elements to be omitted and does not
88
+ # automatically supply them.
89
+ first.add_previous_sibling(element)
90
+ else
91
+ html = add_child(Nokolexbor::Node.new("html", self))
92
+ head = html.add_child(Nokolexbor::Node.new("head", self))
93
+ head.prepend_child(element)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ class Node
5
+ include Enumerable
6
+
7
+ ELEMENT_NODE = 1
8
+ ATTRIBUTE_NODE = 2
9
+ TEXT_NODE = 3
10
+ CDATA_SECTION_NODE = 4
11
+ ENTITY_REF_NODE = 5
12
+ ENTITY_NODE = 6
13
+ PI_NODE = 7
14
+ COMMENT_NODE = 8
15
+ DOCUMENT_NODE = 9
16
+ DOCUMENT_TYPE_NODE = 10
17
+ DOCUMENT_FRAG_NODE = 11
18
+ NOTATION_NODE = 12
19
+
20
+ attr_reader :document
21
+
22
+ LOOKS_LIKE_XPATH = %r{^(\./|/|\.\.|\.$)}
23
+
24
+ def comment?
25
+ type == COMMENT_NODE
26
+ end
27
+
28
+ def cdata?
29
+ type == CDATA_SECTION_NODE
30
+ end
31
+
32
+ def processing_instruction?
33
+ type == PI_NODE
34
+ end
35
+
36
+ def text?
37
+ type == TEXT_NODE
38
+ end
39
+
40
+ def fragment?
41
+ type == DOCUMENT_FRAG_NODE
42
+ end
43
+
44
+ def element?
45
+ type == ELEMENT_NODE
46
+ end
47
+
48
+ def document?
49
+ is_a?(Nokolexbor::Document)
50
+ end
51
+
52
+ def ancestors(selector = nil)
53
+ return NodeSet.new(@document) unless respond_to?(:parent)
54
+ return NodeSet.new(@document) unless parent
55
+
56
+ parents = [parent]
57
+
58
+ while parents.last.respond_to?(:parent)
59
+ break unless (ctx_parent = parents.last.parent)
60
+
61
+ parents << ctx_parent
62
+ end
63
+
64
+ return NodeSet.new(@document, parents) unless selector
65
+
66
+ root = parents.last
67
+ search_results = root.search(selector)
68
+
69
+ NodeSet.new(@document, parents.find_all do |parent|
70
+ search_results.include?(parent)
71
+ end)
72
+ end
73
+
74
+ def wrap(node)
75
+ case node
76
+ when String
77
+ new_parent = fragment(node).child
78
+ when Node
79
+ new_parent = node.dup
80
+ else
81
+ raise ArgumentError, "Requires a String or Node argument, and cannot accept a #{node.class}"
82
+ end
83
+
84
+ if parent
85
+ add_sibling(:next, new_parent)
86
+ else
87
+ new_parent.remove
88
+ end
89
+ new_parent.add_child(self)
90
+
91
+ self
92
+ end
93
+
94
+ def add_previous_sibling(node_or_tags)
95
+ raise ArgumentError,
96
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
97
+
98
+ add_sibling(:previous, node_or_tags)
99
+ end
100
+
101
+ def add_next_sibling(node_or_tags)
102
+ raise ArgumentError,
103
+ "A document may not have multiple root nodes." if parent&.document? && !(node_or_tags.comment? || node_or_tags.processing_instruction?)
104
+
105
+ add_sibling(:next, node_or_tags)
106
+ end
107
+
108
+ def before(node_or_tags)
109
+ add_previous_sibling(node_or_tags)
110
+ self
111
+ end
112
+
113
+ def after(node_or_tags)
114
+ add_next_sibling(node_or_tags)
115
+ self
116
+ end
117
+
118
+ alias_method :next_sibling, :next
119
+ alias_method :previous_sibling, :previous
120
+ alias_method :next=, :add_next_sibling
121
+ alias_method :previous=, :add_previous_sibling
122
+
123
+ def <<(node_or_tags)
124
+ add_child(node_or_tags)
125
+ self
126
+ end
127
+
128
+ def prepend_child(node)
129
+ if (first = children.first)
130
+ # Mimic the error add_child would raise.
131
+ raise "Document already has a root node" if document? && !(node.comment? || node.processing_instruction?)
132
+
133
+ first.add_sibling(:previous, node)
134
+ else
135
+ add_child(node)
136
+ end
137
+ end
138
+
139
+ def traverse(&block)
140
+ children.each { |j| j.traverse(&block) }
141
+ yield(self)
142
+ end
143
+
144
+ def matches?(selector)
145
+ ancestors.last.css(selector).any? { |node| node == self }
146
+ end
147
+
148
+ def attribute(name)
149
+ return nil unless key?(name)
150
+ Attribute.new(name, attr(name))
151
+ end
152
+
153
+ def attributes
154
+ attrs.map { |k, v| [k, Attribute.new(k, v)] }.to_h
155
+ end
156
+
157
+ def replace(node)
158
+ if node.is_a?(NodeSet)
159
+ node.each { |n| add_sibling(:previous, n) }
160
+ else
161
+ add_sibling(:previous, node)
162
+ end
163
+ remove
164
+ end
165
+
166
+ def children=(node)
167
+ children.remove
168
+ if node.is_a?(NodeSet)
169
+ node.each { |n| add_child(n) }
170
+ else
171
+ add_child(node)
172
+ end
173
+ end
174
+
175
+ def parent=(parent_node)
176
+ parent_node.add_child(self)
177
+ end
178
+
179
+ def each
180
+ attributes.each do |name, node|
181
+ yield [name, node.value]
182
+ end
183
+ end
184
+
185
+ alias_method :inner_html=, :children=
186
+
187
+ def css(*args)
188
+ css_impl(args.join(', '))
189
+ end
190
+
191
+ def at_css(*args)
192
+ at_css_impl(args.join(', '))
193
+ end
194
+
195
+ def xpath(*args)
196
+ paths, handler, ns, binds = extract_params(args)
197
+
198
+ xpath_internal(self, paths, handler, ns, binds)
199
+ end
200
+
201
+ def at_xpath(*args)
202
+ xpath(*args).first
203
+ end
204
+
205
+ def search(*args)
206
+ paths, handler, ns, binds = extract_params(args)
207
+
208
+ if paths.size == 1 && !LOOKS_LIKE_XPATH.match?(paths.first)
209
+ return css(paths.first)
210
+ end
211
+
212
+ xpath(*(paths + [ns, handler, binds].compact))
213
+ end
214
+
215
+ alias_method :/, :search
216
+
217
+ def at(*args)
218
+ paths, handler, ns, binds = extract_params(args)
219
+
220
+ if paths.size == 1 && !LOOKS_LIKE_XPATH.match?(paths.first)
221
+ return at_css(paths.first)
222
+ end
223
+
224
+ at_xpath(*(paths + [ns, handler, binds].compact))
225
+ end
226
+
227
+ alias_method :%, :at
228
+
229
+ def classes
230
+ kwattr_values("class")
231
+ end
232
+
233
+ def add_class(names)
234
+ kwattr_add("class", names)
235
+ end
236
+
237
+ def append_class(names)
238
+ kwattr_append("class", names)
239
+ end
240
+
241
+ def remove_class(names = nil)
242
+ kwattr_remove("class", names)
243
+ end
244
+
245
+ def kwattr_values(attribute_name)
246
+ keywordify(attr(attribute_name) || [])
247
+ end
248
+
249
+ def kwattr_add(attribute_name, keywords)
250
+ keywords = keywordify(keywords)
251
+ current_kws = kwattr_values(attribute_name)
252
+ new_kws = (current_kws + (keywords - current_kws)).join(" ")
253
+ set_attr(attribute_name, new_kws)
254
+ self
255
+ end
256
+
257
+ def kwattr_append(attribute_name, keywords)
258
+ keywords = keywordify(keywords)
259
+ current_kws = kwattr_values(attribute_name)
260
+ new_kws = (current_kws + keywords).join(" ")
261
+ set_attr(attribute_name, new_kws)
262
+ self
263
+ end
264
+
265
+ def kwattr_remove(attribute_name, keywords)
266
+ if keywords.nil?
267
+ remove_attr(attribute_name)
268
+ return self
269
+ end
270
+
271
+ keywords = keywordify(keywords)
272
+ current_kws = kwattr_values(attribute_name)
273
+ new_kws = current_kws - keywords
274
+ if new_kws.empty?
275
+ remove_attr(attribute_name)
276
+ else
277
+ set_attr(attribute_name, new_kws.join(" "))
278
+ end
279
+ self
280
+ end
281
+
282
+ def keywordify(keywords)
283
+ case keywords
284
+ when Enumerable
285
+ keywords
286
+ when String
287
+ keywords.scan(/\S+/)
288
+ else
289
+ raise ArgumentError,
290
+ "Keyword attributes must be passed as either a String or an Enumerable, but received #{keywords.class}"
291
+ end
292
+ end
293
+
294
+ def write_to(io, *options)
295
+ io.write(to_html(*options))
296
+ end
297
+
298
+ alias_method :write_html_to, :write_to
299
+
300
+ private
301
+
302
+ def xpath_internal(node, paths, handler, ns, binds)
303
+ # document = node.document
304
+ # return NodeSet.new(document) unless document
305
+
306
+ if paths.length == 1
307
+ return xpath_impl(node, paths.first, handler, ns, binds)
308
+ end
309
+
310
+ NodeSet.new(@document) do |combined|
311
+ paths.each do |path|
312
+ xpath_impl(node, path, handler, ns, binds).each { |set| combined << set }
313
+ end
314
+ end
315
+ end
316
+
317
+ def xpath_impl(node, path, handler, ns, binds)
318
+ ctx = XPathContext.new(node)
319
+ ctx.register_namespaces(ns)
320
+ # path = path.gsub(/xmlns:/, " :") unless Nokogiri.uses_libxml?
321
+
322
+ binds&.each do |key, value|
323
+ ctx.register_variable(key.to_s, value)
324
+ end
325
+
326
+ ctx.evaluate(path, handler)
327
+ end
328
+
329
+ def extract_params(params)
330
+ handler = params.find do |param|
331
+ ![Hash, String, Symbol].include?(param.class)
332
+ end
333
+ params -= [handler] if handler
334
+
335
+ hashes = []
336
+ while Hash === params.last || params.last.nil?
337
+ hashes << params.pop
338
+ break if params.empty?
339
+ end
340
+ ns, binds = hashes.reverse
341
+
342
+ # ns ||= (document.root&.namespaces || {})
343
+ ns ||= {}
344
+
345
+ [params, handler, ns, binds]
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ class NodeSet < Nokolexbor::Node
5
+ include Enumerable
6
+
7
+ def self.new(document, list = [])
8
+ obj = allocate
9
+ obj.instance_variable_set(:@document, document)
10
+ list.each { |x| obj << x }
11
+ yield obj if block_given?
12
+ obj
13
+ end
14
+
15
+ def each
16
+ return to_enum unless block_given?
17
+
18
+ 0.upto(length - 1) do |x|
19
+ yield self[x]
20
+ end
21
+ self
22
+ end
23
+
24
+ def first(n = nil)
25
+ return self[0] unless n
26
+
27
+ list = []
28
+ [n, length].min.times { |i| list << self[i] }
29
+ list
30
+ end
31
+
32
+ def last
33
+ self[-1]
34
+ end
35
+
36
+ def empty?
37
+ length == 0
38
+ end
39
+
40
+ def index(node = nil)
41
+ if node
42
+ each_with_index { |member, j| return j if member == node }
43
+ elsif block_given?
44
+ each_with_index { |member, j| return j if yield(member) }
45
+ end
46
+ nil
47
+ end
48
+
49
+ def content
50
+ self.map(&:content).join
51
+ end
52
+
53
+ alias_method :text, :content
54
+ alias_method :inner_text, :content
55
+ alias_method :to_str, :content
56
+
57
+ def inner_html(*args)
58
+ self.map { |n| n.inner_html(*args) }.join
59
+ end
60
+
61
+ def outer_html(*args)
62
+ self.map { |n| n.outer_html(*args) }.join
63
+ end
64
+
65
+ alias_method :to_s, :outer_html
66
+ alias_method :to_html, :outer_html
67
+ alias_method :serialize, :outer_html
68
+
69
+ def remove
70
+ self.each(&:remove)
71
+ end
72
+
73
+ alias_method :unlink, :remove
74
+ alias_method :to_ary, :to_a
75
+
76
+ def destroy
77
+ self.each(&:destroy)
78
+ end
79
+
80
+ def pop
81
+ return nil if length == 0
82
+
83
+ delete(last)
84
+ end
85
+
86
+ def shift
87
+ return nil if length == 0
88
+
89
+ delete(first)
90
+ end
91
+
92
+ def ==(other)
93
+ return false unless other.is_a?(NodeSet)
94
+ return false unless length == other.length
95
+
96
+ each_with_index do |node, i|
97
+ return false unless node == other[i]
98
+ end
99
+ true
100
+ end
101
+
102
+ def children
103
+ node_set = NodeSet.new(@document)
104
+ each do |node|
105
+ node.children.each { |n| node_set.push(n) }
106
+ end
107
+ node_set
108
+ end
109
+
110
+ def reverse
111
+ node_set = NodeSet.new(@document)
112
+ (length - 1).downto(0) do |x|
113
+ node_set.push(self[x])
114
+ end
115
+ node_set
116
+ end
117
+
118
+ def xpath(*args)
119
+ paths, handler, ns, binds = extract_params(args)
120
+
121
+ NodeSet.new(@document) do |set|
122
+ each do |node|
123
+ node.send(:xpath_internal, node, paths, handler, ns, binds).each do |inner_node|
124
+ set << inner_node
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ end
131
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ VERSION = '0.3.0'
5
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ module XPath
5
+ class SyntaxError < StandardError
6
+ attr_reader :domain
7
+ attr_reader :code
8
+ attr_reader :level
9
+ attr_reader :file
10
+ attr_reader :line
11
+ attr_reader :str1
12
+ attr_reader :str2
13
+ attr_reader :str3
14
+ attr_reader :int1
15
+ attr_reader :column
16
+
17
+ ###
18
+ # return true if this is a non error
19
+ def none?
20
+ level == 0
21
+ end
22
+
23
+ ###
24
+ # return true if this is a warning
25
+ def warning?
26
+ level == 1
27
+ end
28
+
29
+ ###
30
+ # return true if this is an error
31
+ def error?
32
+ level == 2
33
+ end
34
+
35
+ ###
36
+ # return true if this error is fatal
37
+ def fatal?
38
+ level == 3
39
+ end
40
+
41
+ def to_s
42
+ message = super.chomp
43
+ [location_to_s, level_to_s, message]
44
+ .compact.join(": ")
45
+ .force_encoding(message.encoding)
46
+ end
47
+
48
+ private
49
+
50
+ def level_to_s
51
+ case level
52
+ when 3 then "FATAL"
53
+ when 2 then "ERROR"
54
+ when 1 then "WARNING"
55
+ end
56
+ end
57
+
58
+ def nil_or_zero?(attribute)
59
+ attribute.nil? || attribute.zero?
60
+ end
61
+
62
+ def location_to_s
63
+ return nil if nil_or_zero?(line) && nil_or_zero?(column)
64
+
65
+ "#{line}:#{column}"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nokolexbor
4
+ class XPathContext
5
+ ###
6
+ # Register namespaces in +namespaces+
7
+ def register_namespaces(namespaces)
8
+ namespaces.each do |k, v|
9
+ k = k.to_s.gsub(/.*:/, "") # strip off 'xmlns:' or 'xml:'
10
+ register_ns(k, v)
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/nokolexbor.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ # pre-compiled extension by rake-compiler is located inside lib/nokolexbor/<ruby_version>/
5
+ RUBY_VERSION =~ /(\d+\.\d+)/
6
+ require "nokolexbor/#{Regexp.last_match(1)}/nokolexbor"
7
+ rescue LoadError => e
8
+ if /GLIBC/.match?(e.message)
9
+ warn(<<~EOM)
10
+ ERROR: It looks like you're trying to use Nokolexbor as a precompiled native gem on a system
11
+ with an unsupported version of glibc.
12
+ #{e.message}
13
+ If that's the case, then please install Nokolexbor via the `ruby` platform gem:
14
+ gem install nokolexbor --platform=ruby
15
+ or:
16
+ bundle config set force_ruby_platform true
17
+ EOM
18
+ raise e
19
+ end
20
+
21
+ require 'nokolexbor/nokolexbor'
22
+ end
23
+
24
+ require 'nokolexbor/version'
25
+ require 'nokolexbor/node'
26
+ require 'nokolexbor/document'
27
+ require 'nokolexbor/node_set'
28
+ require 'nokolexbor/attribute'
29
+ require 'nokolexbor/xpath'
30
+ require 'nokolexbor/xpath_context'
31
+
32
+ module Nokolexbor
33
+ class << self
34
+ def HTML(*args)
35
+ Document.parse(*args)
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nokolexbor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: x86_64-darwin
6
+ authors:
7
+ - Yicheng Zhou
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ description: Nokolexbor is a high-performance HTML5 parser, with support for both
42
+ CSS selectors and XPath. It's API is designed to be compatible with Nokogiri.
43
+ email: zyc9012@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/nokolexbor.rb
49
+ - lib/nokolexbor/2.6/nokolexbor.bundle
50
+ - lib/nokolexbor/2.7/nokolexbor.bundle
51
+ - lib/nokolexbor/3.0/nokolexbor.bundle
52
+ - lib/nokolexbor/3.1/nokolexbor.bundle
53
+ - lib/nokolexbor/attribute.rb
54
+ - lib/nokolexbor/document.rb
55
+ - lib/nokolexbor/node.rb
56
+ - lib/nokolexbor/node_set.rb
57
+ - lib/nokolexbor/version.rb
58
+ - lib/nokolexbor/xpath.rb
59
+ - lib/nokolexbor/xpath_context.rb
60
+ homepage: https://github.com/serpapi/nokolexbor
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '2.6'
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.dev
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.3.4
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: High-performance HTML5 parser, with support for both CSS selectors and XPath.
86
+ test_files: []