nokolexbor 0.7.0-aarch64-linux
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.
- checksums.yaml +7 -0
- data/lib/nokolexbor/3.1/nokolexbor.so +0 -0
- data/lib/nokolexbor/3.2/nokolexbor.so +0 -0
- data/lib/nokolexbor/3.3/nokolexbor.so +0 -0
- data/lib/nokolexbor/3.4/nokolexbor.so +0 -0
- data/lib/nokolexbor/4.0/nokolexbor.so +0 -0
- data/lib/nokolexbor/builder.rb +223 -0
- data/lib/nokolexbor/document.rb +168 -0
- data/lib/nokolexbor/document_fragment.rb +42 -0
- data/lib/nokolexbor/node.rb +775 -0
- data/lib/nokolexbor/node_set.rb +293 -0
- data/lib/nokolexbor/version.rb +5 -0
- data/lib/nokolexbor/xpath.rb +69 -0
- data/lib/nokolexbor/xpath_context.rb +14 -0
- data/lib/nokolexbor.rb +50 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1853f48d77d8dc95236e11a3a41e2e38e276c28e6cc89a18100786892c359707
|
|
4
|
+
data.tar.gz: d4ebcf31410396478657ecedc405622b5c97ed431c2dd9afea8c199efb2d27cd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 588c9575348817533f15249f6fab58ea84823b4e33d6300db70b7b1a4052e278868b19263f1fc9ec822d3098e9314422b792440aa8e8976e859fcbb0ccc21c43
|
|
7
|
+
data.tar.gz: bee83d0ff2ecc3bc390f574d26eeef91df0791deac17caa80fe4bdfc206a7f419030cea1c70dc1273d6e71d68c39178fc356efa2c1b0e6fa5d2d9756f3727584
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Nokolexbor
|
|
6
|
+
# A DSL for programmatically building HTML documents.
|
|
7
|
+
#
|
|
8
|
+
# == No-arg block (instance_eval style) – tag names are called bare:
|
|
9
|
+
#
|
|
10
|
+
# Nokolexbor do
|
|
11
|
+
# body do
|
|
12
|
+
# h1 'Hello world'
|
|
13
|
+
# p 'This little p'
|
|
14
|
+
# ul do
|
|
15
|
+
# li 'Go to market'
|
|
16
|
+
# li 'Go to bed'
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# == Block-parameter style – tag names are called on the builder argument:
|
|
22
|
+
#
|
|
23
|
+
# Nokolexbor::Builder.new do |b|
|
|
24
|
+
# b.body do
|
|
25
|
+
# b.h1 'Hello world'
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# == Building into an existing node:
|
|
30
|
+
#
|
|
31
|
+
# Nokolexbor::Builder.with(existing_node) do
|
|
32
|
+
# span 'injected'
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
class Builder
|
|
36
|
+
# The Document used to create new nodes
|
|
37
|
+
attr_accessor :doc
|
|
38
|
+
|
|
39
|
+
# The node that currently receives new children
|
|
40
|
+
attr_accessor :parent
|
|
41
|
+
|
|
42
|
+
# Arity of the outermost block (drives instance_eval vs yield dispatch)
|
|
43
|
+
attr_accessor :arity
|
|
44
|
+
|
|
45
|
+
# A context object for use when the block has no arguments
|
|
46
|
+
attr_accessor :context
|
|
47
|
+
|
|
48
|
+
# Build into an existing +root+ node.
|
|
49
|
+
def self.with(root, &block)
|
|
50
|
+
new(root, &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param root [Node, nil]
|
|
54
|
+
# When given, new nodes are appended under +root+ and +root.document+
|
|
55
|
+
# is used as the owning document. When omitted a fresh {Document} and
|
|
56
|
+
# a {DocumentFragment} container are created.
|
|
57
|
+
def initialize(root = nil, &block)
|
|
58
|
+
if root
|
|
59
|
+
@doc = root.document
|
|
60
|
+
@parent = root
|
|
61
|
+
else
|
|
62
|
+
@doc = Document.new
|
|
63
|
+
@parent = DocumentFragment.new(@doc)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@context = nil
|
|
67
|
+
@arity = nil
|
|
68
|
+
|
|
69
|
+
return unless block
|
|
70
|
+
|
|
71
|
+
@arity = block.arity
|
|
72
|
+
if @arity <= 0
|
|
73
|
+
# Capture outer self so that outer methods / ivars remain accessible
|
|
74
|
+
# via method_missing delegation while the builder acts as self.
|
|
75
|
+
@context = eval("self", block.binding)
|
|
76
|
+
instance_eval(&block)
|
|
77
|
+
else
|
|
78
|
+
yield self
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Insert a Text node containing +string+.
|
|
83
|
+
def text(string)
|
|
84
|
+
insert(@doc.create_text_node(string))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Insert a CDATA node containing +string+.
|
|
88
|
+
def cdata(string)
|
|
89
|
+
insert(@doc.create_cdata(string))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Insert a Comment node containing +string+.
|
|
93
|
+
def comment(string)
|
|
94
|
+
insert(@doc.create_comment(string))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Append raw HTML (parsed into nodes) under the current parent.
|
|
98
|
+
def <<(string)
|
|
99
|
+
@doc.fragment(string).children.each { |x| insert(x) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Serialize the built content to an HTML string.
|
|
103
|
+
def to_html
|
|
104
|
+
@parent.to_html
|
|
105
|
+
end
|
|
106
|
+
alias_method :to_s, :to_html
|
|
107
|
+
|
|
108
|
+
# Ruby's Kernel#p, Kernel#pp, etc. are defined on Object and therefore on
|
|
109
|
+
# Builder itself. They must be overridden so they fall through to
|
|
110
|
+
# method_missing and create the corresponding HTML element instead of
|
|
111
|
+
# printing values to stdout.
|
|
112
|
+
def p(*args, &block) # :nodoc:
|
|
113
|
+
method_missing(:p, *args, &block)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def method_missing(method, *args, &block) # :nodoc:
|
|
117
|
+
# Only delegate to the outer context for methods the user defined there.
|
|
118
|
+
# Standard Object / Kernel methods (like :p, :pp, :puts …) must not be
|
|
119
|
+
# forwarded to @context because all objects respond to them, which would
|
|
120
|
+
# bypass element creation entirely.
|
|
121
|
+
if @context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)
|
|
122
|
+
@context.send(method, *args, &block)
|
|
123
|
+
else
|
|
124
|
+
node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args)
|
|
125
|
+
insert(node, &block)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def respond_to_missing?(method, include_private = false) # :nodoc:
|
|
130
|
+
(@context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)) || super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Methods defined on plain Object that should never be forwarded to @context
|
|
134
|
+
# as HTML element creation fallbacks.
|
|
135
|
+
OBJECT_INSTANCE_METHODS = Object.instance_methods.to_set.freeze
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Add +node+ as a child of @parent; if a block is given, push @parent
|
|
140
|
+
# to +node+ for the duration of that block, then restore it.
|
|
141
|
+
# Returns a {NodeBuilder} for the inserted node so attributes / classes
|
|
142
|
+
# can be chained: <tt>div.container.hero!</tt>
|
|
143
|
+
def insert(node, &block)
|
|
144
|
+
node = @parent.add_child(node)
|
|
145
|
+
if block
|
|
146
|
+
old_parent = @parent
|
|
147
|
+
@parent = node
|
|
148
|
+
@arity ||= block.arity
|
|
149
|
+
begin
|
|
150
|
+
if @arity <= 0
|
|
151
|
+
instance_eval(&block)
|
|
152
|
+
else
|
|
153
|
+
yield(self)
|
|
154
|
+
end
|
|
155
|
+
ensure
|
|
156
|
+
@parent = old_parent
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
NodeBuilder.new(node, self)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Wraps a built node and exposes a fluent API for setting classes and ids:
|
|
163
|
+
#
|
|
164
|
+
# div.container # => <div class="container">
|
|
165
|
+
# div.box.highlight # => <div class="box highlight">
|
|
166
|
+
# div.thing! # => <div id="thing">
|
|
167
|
+
# div.container.hero! # => <div class="container" id="hero">
|
|
168
|
+
#
|
|
169
|
+
class NodeBuilder # :nodoc:
|
|
170
|
+
def initialize(node, doc_builder)
|
|
171
|
+
@node = node
|
|
172
|
+
@doc_builder = doc_builder
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def []=(k, v)
|
|
176
|
+
@node[k] = v
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def [](k)
|
|
180
|
+
@node[k]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def method_missing(method, *args, &block)
|
|
184
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
|
185
|
+
|
|
186
|
+
case method.to_s
|
|
187
|
+
when /^(.*)!$/
|
|
188
|
+
@node["id"] = Regexp.last_match(1)
|
|
189
|
+
@node.content = args.first if args.first
|
|
190
|
+
when /^(.*)=$/
|
|
191
|
+
@node[Regexp.last_match(1)] = args.first
|
|
192
|
+
else
|
|
193
|
+
@node["class"] =
|
|
194
|
+
((@node["class"] || "").split(/\s/) + [method.to_s]).join(" ")
|
|
195
|
+
@node.content = args.first if args.first
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
opts.each do |k, v|
|
|
199
|
+
@node[k.to_s] = ((@node[k.to_s] || "").split(/\s/) + [v.to_s]).join(" ")
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if block
|
|
203
|
+
old_parent = @doc_builder.parent
|
|
204
|
+
@doc_builder.parent = @node
|
|
205
|
+
arity = @doc_builder.arity || block.arity
|
|
206
|
+
value = if arity <= 0
|
|
207
|
+
@doc_builder.instance_eval(&block)
|
|
208
|
+
else
|
|
209
|
+
yield(@doc_builder)
|
|
210
|
+
end
|
|
211
|
+
@doc_builder.parent = old_parent
|
|
212
|
+
return value
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
self
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def respond_to_missing?(_method, _include_private = false) # :nodoc:
|
|
219
|
+
true
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nokolexbor
|
|
4
|
+
class Document < Nokolexbor::Node
|
|
5
|
+
# Create an {Element} with +name+ belonging to this document, optionally setting contents or
|
|
6
|
+
# attributes.
|
|
7
|
+
#
|
|
8
|
+
# @param name [String]
|
|
9
|
+
# @param contents_or_attrs [#to_s, Hash]
|
|
10
|
+
#
|
|
11
|
+
# @return [Element]
|
|
12
|
+
#
|
|
13
|
+
# @example An empty element without attributes
|
|
14
|
+
# doc.create_element("div")
|
|
15
|
+
# # => <div></div>
|
|
16
|
+
#
|
|
17
|
+
# @example An element with contents
|
|
18
|
+
# doc.create_element("div", "contents")
|
|
19
|
+
# # => <div>contents</div>
|
|
20
|
+
#
|
|
21
|
+
# @example An element with attributes
|
|
22
|
+
# doc.create_element("div", {"class" => "container"})
|
|
23
|
+
# # => <div class='container'></div>
|
|
24
|
+
#
|
|
25
|
+
# @example An element with contents and attributes
|
|
26
|
+
# doc.create_element("div", "contents", {"class" => "container"})
|
|
27
|
+
# # => <div class='container'>contents</div>
|
|
28
|
+
#
|
|
29
|
+
# @example Passing a block to mutate the element
|
|
30
|
+
# doc.create_element("div") { |node| node["class"] = "blue" }
|
|
31
|
+
# # => <div class='blue'></div>
|
|
32
|
+
def create_element(name, *contents_or_attrs, &block)
|
|
33
|
+
elm = Nokolexbor::Element.new(name, self, &block)
|
|
34
|
+
contents_or_attrs.each do |arg|
|
|
35
|
+
case arg
|
|
36
|
+
when Hash
|
|
37
|
+
arg.each do |k, v|
|
|
38
|
+
elm[k.to_s] = v.to_s
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
elm.content = arg.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
elm
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Create a {Text} with +string+.
|
|
48
|
+
#
|
|
49
|
+
# @return [Text]
|
|
50
|
+
def create_text_node(string, &block)
|
|
51
|
+
Nokolexbor::Text.new(string.to_s, self, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Create a {CDATA} containing +string+.
|
|
55
|
+
#
|
|
56
|
+
# @return [CDATA]
|
|
57
|
+
def create_cdata(string, &block)
|
|
58
|
+
Nokolexbor::CDATA.new(string.to_s, self, &block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a {Comment} containing +string+.
|
|
62
|
+
#
|
|
63
|
+
# @return [Comment]
|
|
64
|
+
def create_comment(string, &block)
|
|
65
|
+
Nokolexbor::Comment.new(string.to_s, self, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A reference to +self+.
|
|
69
|
+
#
|
|
70
|
+
# @return [Document]
|
|
71
|
+
def document
|
|
72
|
+
self
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get the meta tag encoding for this document. If there is no meta tag, nil is returned.
|
|
76
|
+
#
|
|
77
|
+
# @return [String]
|
|
78
|
+
def meta_encoding
|
|
79
|
+
if (meta = at_css("meta[charset]"))
|
|
80
|
+
meta[:charset]
|
|
81
|
+
elsif (meta = meta_content_type)
|
|
82
|
+
meta["content"][/charset\s*=\s*([\w-]+)/i, 1]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Set the meta tag encoding for this document.
|
|
87
|
+
#
|
|
88
|
+
# If an meta encoding tag is already present, its content is
|
|
89
|
+
# replaced with the given text.
|
|
90
|
+
#
|
|
91
|
+
# Otherwise, this method tries to create one at an appropriate
|
|
92
|
+
# place supplying head and/or html elements as necessary, which
|
|
93
|
+
# is inside a head element if any, and before any text node or
|
|
94
|
+
# content element (typically <body>) if any.
|
|
95
|
+
def meta_encoding=(encoding)
|
|
96
|
+
if (meta = meta_content_type)
|
|
97
|
+
meta["content"] = format("text/html; charset=%s", encoding)
|
|
98
|
+
encoding
|
|
99
|
+
elsif (meta = at_css("meta[charset]"))
|
|
100
|
+
meta["charset"] = encoding
|
|
101
|
+
else
|
|
102
|
+
meta = Nokolexbor::Node.new("meta", self)
|
|
103
|
+
meta["charset"] = encoding
|
|
104
|
+
|
|
105
|
+
if (head = at_css("head"))
|
|
106
|
+
head.prepend_child(meta)
|
|
107
|
+
else
|
|
108
|
+
set_metadata_element(meta)
|
|
109
|
+
end
|
|
110
|
+
encoding
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def meta_content_type
|
|
115
|
+
xpath("//meta[@http-equiv and boolean(@content)]").find do |node|
|
|
116
|
+
node["http-equiv"] =~ /\AContent-Type\z/i
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
private :meta_content_type
|
|
120
|
+
|
|
121
|
+
def set_metadata_element(element)
|
|
122
|
+
if (head = at_css("head"))
|
|
123
|
+
head << element
|
|
124
|
+
elsif (html = at_css("html"))
|
|
125
|
+
head = html.prepend_child(Nokolexbor::Node.new("head", self))
|
|
126
|
+
head.prepend_child(element)
|
|
127
|
+
elsif (first = children.find do |node|
|
|
128
|
+
case node
|
|
129
|
+
when Nokolexbor::Node
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
end)
|
|
133
|
+
# We reach here only if the underlying document model
|
|
134
|
+
# allows <html>/<head> elements to be omitted and does not
|
|
135
|
+
# automatically supply them.
|
|
136
|
+
first.add_previous_sibling(element)
|
|
137
|
+
else
|
|
138
|
+
html = add_child(Nokolexbor::Node.new("html", self))
|
|
139
|
+
head = html.add_child(Nokolexbor::Node.new("head", self))
|
|
140
|
+
head.prepend_child(element)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Parse HTML into a {Document}.
|
|
145
|
+
#
|
|
146
|
+
# @param string_or_io [String, #read]
|
|
147
|
+
# The HTML to be parsed. It may be a String, or any object that
|
|
148
|
+
# responds to #read such as an IO, or StringIO.
|
|
149
|
+
#
|
|
150
|
+
# @return [Document]
|
|
151
|
+
def self.parse(string_or_io)
|
|
152
|
+
html = string_or_io
|
|
153
|
+
if string_or_io.respond_to?(:read)
|
|
154
|
+
html = string_or_io.read
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
if html.respond_to?(:encoding) && html.encoding != Encoding::UTF_8
|
|
158
|
+
html = html.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
parse_native(html)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
IMPLIED_XPATH_CONTEXTS = ["//"].freeze # :nodoc:
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nokolexbor
|
|
4
|
+
class DocumentFragment < Nokolexbor::Node
|
|
5
|
+
# Create a {DocumentFragment} from +tags+.
|
|
6
|
+
#
|
|
7
|
+
# @return [DocumentFragment]
|
|
8
|
+
def self.parse(tags)
|
|
9
|
+
new(Nokolexbor::Document.new, tags, nil)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Create a new {DocumentFragment} from +tags+.
|
|
13
|
+
#
|
|
14
|
+
# If +ctx+ is present, it is used as a context node for the
|
|
15
|
+
# subtree created.
|
|
16
|
+
def initialize(document, tags = nil, ctx = nil)
|
|
17
|
+
return self unless tags
|
|
18
|
+
|
|
19
|
+
ctx ||= document
|
|
20
|
+
node_set = ctx.parse(tags)
|
|
21
|
+
node_set.each { |child| child.parent = self } unless node_set.empty?
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [String] The name of {DocumentFragment}
|
|
26
|
+
def name
|
|
27
|
+
"#document-fragment"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias_method :outer_html, :inner_html
|
|
31
|
+
alias_method :to_html, :outer_html
|
|
32
|
+
alias_method :to_s, :outer_html
|
|
33
|
+
alias_method :serialize, :outer_html
|
|
34
|
+
|
|
35
|
+
# Create a {DocumentFragment} from +data+.
|
|
36
|
+
#
|
|
37
|
+
# @return [DocumentFragment]
|
|
38
|
+
def fragment(data)
|
|
39
|
+
document.fragment(data)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|