Class: Dommy::HTMLCollection
- Inherits:
-
Object
- Object
- Dommy::HTMLCollection
show all
- Includes:
- Bridge::Methods, Enumerable
- Defined in:
- lib/dommy/html_collection.rb
Overview
HTMLCollection — live, ordered set of Element nodes. Distinct
from NodeList in two ways:
- Always element-only (Node types other than Element are skipped)
- Supports `namedItem(name)` lookup by `id` or `name` attribute
Live behavior: pass an evaluator block (called &compute) that
returns the current element list on every access. Each query
re-evaluates, so mutations to the parent tree are reflected
immediately.
Intentionally NOT a subclass of Array; spec semantics demand
Array.isArray(html_collection) === false in real browsers, and
mirroring that here helps tests written against MDN behavior.
Constant Summary
collapse
- HTML_NAMESPACE =
Shared getElementsByTagNameNS(namespace, localName) — a live collection
of descendants of root matching the (namespace, localName) filter, where
"*" matches any. An empty-string namespace means the null namespace.
"http://www.w3.org/1999/xhtml"
Class Method Summary
collapse
Instance Method Summary
collapse
included
Constructor Details
Returns a new instance of HTMLCollection.
21
22
23
|
# File 'lib/dommy/html_collection.rb', line 21
def initialize(&compute)
@compute = compute
end
|
Class Method Details
.ascii_downcase(str) ⇒ Object
ASCII-only lowercase (A-Z -> a-z), leaving non-ASCII code points intact,
per the spec's "converted to ASCII lowercase".
74
75
76
|
# File 'lib/dommy/html_collection.rb', line 74
def self.ascii_downcase(str)
str.gsub(/[A-Z]/) { |c| (c.ord + 32).chr }
end
|
.elements_by_tag_name(root, document, qualified_name) ⇒ Object
WHATWG getElementsByTagName(qualifiedName) — a live collection filtered
by qualified name. "*" matches any. In an HTML document, HTML-namespace
elements match case-insensitively (ASCII), while other-namespace elements
(and everything in a non-HTML document) match case-sensitively.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
# File 'lib/dommy/html_collection.rb', line 34
def self.elements_by_tag_name(root, document, qualified_name)
qn = qualified_name.to_s
html_doc = document.respond_to?(:html_document?) ? document.html_document? : true
qn_lower = ascii_downcase(qn)
new do
root.css("*").filter_map do |node|
el = document.wrap_node(node)
next nil unless el
next el if qn == "*"
el_qn = qualified_name_of(el)
el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
match =
if html_doc && el_ns == HTML_NAMESPACE
el_qn == qn_lower
else
el_qn == qn
end
match ? el : nil
end
end
end
|
.elements_by_tag_name_ns(root, document, namespace, local_name) ⇒ Object
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
# File 'lib/dommy/html_collection.rb', line 78
def self.elements_by_tag_name_ns(root, document, namespace, local_name)
ns = namespace.to_s
ns_filter = ns == "*" ? :any : (ns.empty? ? nil : ns)
local = local_name.to_s
local_filter = local == "*" ? :any : local
new do
root.css("*").filter_map do |node|
el = document.wrap_node(node)
next nil unless el
el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
next nil unless ns_filter == :any || el_ns == ns_filter
el_local = el.respond_to?(:local_name) ? el.local_name : nil
next nil unless local_filter == :any || el_local == local_filter
el
end
end
end
|
.qualified_name_of(el) ⇒ Object
The element's qualified name (prefix:localName, or just localName). The
backend node name can't be trusted — the HTML parser lowercases it — so
rebuild it from the case-preserving local name and prefix.
64
65
66
67
68
69
70
|
# File 'lib/dommy/html_collection.rb', line 64
def self.qualified_name_of(el)
local = el.respond_to?(:local_name) ? el.local_name.to_s : ""
prefix = el.respond_to?(:__js_get__) ? el.__js_get__("prefix") : nil
prefix = nil if prefix.nil? || prefix.to_s.empty? ||
(defined?(Bridge::UNDEFINED) && prefix.equal?(Bridge::UNDEFINED))
prefix ? "#{prefix}:#{local}" : local
end
|
Instance Method Details
#[](key) ⇒ Object
[] supports both integer index (coll[0], coll[-1]) and
string name (coll["myId"]). Negative indices are interpreted
Ruby-style (offset from the end), even though the spec's
item(i) is positive-only.
146
147
148
149
150
151
152
153
154
155
|
# File 'lib/dommy/html_collection.rb', line 146
def [](key)
case key
when Integer
to_a[key]
when /\A-?\d+\z/
to_a[key.to_i]
else
named_item(key)
end
end
|
#__js_call__(method, args) ⇒ Object
218
219
220
221
222
223
224
225
|
# File 'lib/dommy/html_collection.rb', line 218
def __js_call__(method, args)
case method
when "item"
item(args[0])
when "namedItem"
named_item(args[0])
end
end
|
#__js_get__(key) ⇒ Object
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/dommy/html_collection.rb', line 173
def __js_get__(key)
case key
when "length"
length
when Integer
item(key)
else
s = key.to_s
if s.match?(/\A\d+\z/) && s.to_i < 4_294_967_295 && s == s.to_i.to_s
item(s.to_i)
else
named_item(s) || (s == "length" ? length : Bridge::ABSENT)
end
end
end
|
#__js_named_props__ ⇒ Object
WebIDL "supported property names" for HTMLCollection: in tree order, each
element contributes its non-empty id, then (if it is in the HTML
namespace) its non-empty name — ignoring duplicates.
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
# File 'lib/dommy/html_collection.rb', line 198
def __js_named_props__
names = []
to_a.each do |el|
next unless el.respond_to?(:__dommy_backend_node__)
node = el.__dommy_backend_node__
id = node["id"].to_s
names << id if !id.empty? && !names.include?(id)
name = node["name"].to_s
next if name.empty? || names.include?(name)
html_ns = !el.respond_to?(:namespace_uri) || el.namespace_uri == "http://www.w3.org/1999/xhtml"
names << name if html_ns
end
names
end
|
#each(&blk) ⇒ Object
165
166
167
|
# File 'lib/dommy/html_collection.rb', line 165
def each(&blk)
to_a.each(&blk)
end
|
#empty? ⇒ Boolean
109
110
111
|
# File 'lib/dommy/html_collection.rb', line 109
def empty?
to_a.empty?
end
|
#first(n = nil) ⇒ Object
157
158
159
|
# File 'lib/dommy/html_collection.rb', line 157
def first(n = nil)
n.nil? ? to_a.first : to_a.first(n)
end
|
#item(index) ⇒ Object
113
114
115
116
117
|
# File 'lib/dommy/html_collection.rb', line 113
def item(index)
to_a[index.to_i % 4_294_967_296]
end
|
#last(n = nil) ⇒ Object
161
162
163
|
# File 'lib/dommy/html_collection.rb', line 161
def last(n = nil)
n.nil? ? to_a.last : to_a.last(n)
end
|
#length ⇒ Object
Also known as:
size
103
104
105
|
# File 'lib/dommy/html_collection.rb', line 103
def length
to_a.length
end
|
#named_item(name) ⇒ Object
namedItem(name) returns the first element whose id or
name attribute equals name. Returns nil if no match.
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
# File 'lib/dommy/html_collection.rb', line 121
def named_item(name)
key = (name.is_a?(Float) && name.finite? && name == name.to_i) ? name.to_i.to_s : name.to_s
return nil if key.empty?
to_a.find do |el|
next false unless el.respond_to?(:__dommy_backend_node__)
node = el.__dommy_backend_node__
next true if node["id"].to_s == key
html_ns = !el.respond_to?(:namespace_uri) || el.namespace_uri == "http://www.w3.org/1999/xhtml"
html_ns && node["name"].to_s == key
end
end
|
#to_a ⇒ Object
169
170
171
|
# File 'lib/dommy/html_collection.rb', line 169
def to_a
@compute.call
end
|