nokolexbor 0.6.4 → 0.7.0
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 +4 -4
- data/lib/nokolexbor/builder.rb +223 -0
- data/lib/nokolexbor/version.rb +1 -1
- data/lib/nokolexbor.rb +13 -1
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb6fea4008f68e39df01fbc3920634bd747f6c6e58605876b4600b6037745d4a
|
|
4
|
+
data.tar.gz: 38f67c6e320e95d35a82db68f34b08e7103fa8d56e52fe6e59ebdeb266701c16
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a44ba9da124b17e58ae2abc8c6e99cfab22edbdcdff5b07e9636838f28098a4716aa6f50cf7282c1f15fcbb0630b7bd5ef814b54cb7886dd75b16a864e496405
|
|
7
|
+
data.tar.gz: 40fcdc69521d2f985be5fa62addf3f4fd03ac577eeb66878f496dbc684fc3925ceddb9fa2f74a870188431c672b16394ccc6c58303caae6c6718af8e978bc967
|
|
@@ -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
|
data/lib/nokolexbor/version.rb
CHANGED
data/lib/nokolexbor.rb
CHANGED
|
@@ -28,11 +28,23 @@ require 'nokolexbor/node_set'
|
|
|
28
28
|
require 'nokolexbor/document_fragment'
|
|
29
29
|
require 'nokolexbor/xpath'
|
|
30
30
|
require 'nokolexbor/xpath_context'
|
|
31
|
+
require 'nokolexbor/builder'
|
|
31
32
|
|
|
32
33
|
module Nokolexbor
|
|
33
34
|
class << self
|
|
34
|
-
def
|
|
35
|
+
def parse(*args)
|
|
35
36
|
Document.parse(*args)
|
|
36
37
|
end
|
|
38
|
+
|
|
39
|
+
alias_method :HTML, :parse
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Parse an HTML document, or build one with the DSL when a block is given.
|
|
44
|
+
def Nokolexbor(string_or_io = nil, &block)
|
|
45
|
+
if block
|
|
46
|
+
Nokolexbor::Builder.new(&block).parent
|
|
47
|
+
else
|
|
48
|
+
Nokolexbor.parse(string_or_io)
|
|
37
49
|
end
|
|
38
50
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nokolexbor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yicheng Zhou
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-25 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rake-compiler
|
|
@@ -120,6 +119,7 @@ files:
|
|
|
120
119
|
- ext/nokolexbor/xml_tree.c
|
|
121
120
|
- ext/nokolexbor/xml_xpath.c
|
|
122
121
|
- lib/nokolexbor.rb
|
|
122
|
+
- lib/nokolexbor/builder.rb
|
|
123
123
|
- lib/nokolexbor/document.rb
|
|
124
124
|
- lib/nokolexbor/document_fragment.rb
|
|
125
125
|
- lib/nokolexbor/node.rb
|
|
@@ -523,7 +523,6 @@ licenses:
|
|
|
523
523
|
- MIT
|
|
524
524
|
metadata:
|
|
525
525
|
msys2_mingw_dependencies: cmake
|
|
526
|
-
post_install_message:
|
|
527
526
|
rdoc_options: []
|
|
528
527
|
require_paths:
|
|
529
528
|
- lib
|
|
@@ -538,8 +537,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
538
537
|
- !ruby/object:Gem::Version
|
|
539
538
|
version: '0'
|
|
540
539
|
requirements: []
|
|
541
|
-
rubygems_version:
|
|
542
|
-
signing_key:
|
|
540
|
+
rubygems_version: 4.0.6
|
|
543
541
|
specification_version: 4
|
|
544
542
|
summary: High-performance HTML5 parser, with support for both CSS selectors and XPath.
|
|
545
543
|
test_files: []
|