gumbo-html 0.2.3 → 0.3.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.
- package/README.md +27 -10
- package/binding.gyp +49 -0
- package/examples/example.js +87 -0
- package/examples/scrape.js +301 -0
- package/index.d.ts +58 -3
- package/index.js +7 -2
- package/lib/wrapper.js +385 -0
- package/package.json +36 -5
- package/src/addon.cc +19 -0
- package/src/gumbo-parser/COPYING +201 -0
- package/src/gumbo-parser/README.md +8 -0
- package/src/gumbo-parser/src/attribute.c +44 -0
- package/src/gumbo-parser/src/attribute.h +37 -0
- package/src/gumbo-parser/src/char_ref.c +23069 -0
- package/src/gumbo-parser/src/char_ref.h +60 -0
- package/src/gumbo-parser/src/error.c +279 -0
- package/src/gumbo-parser/src/error.h +225 -0
- package/src/gumbo-parser/src/gumbo.h +671 -0
- package/src/gumbo-parser/src/insertion_mode.h +57 -0
- package/src/gumbo-parser/src/parser.c +4192 -0
- package/src/gumbo-parser/src/parser.h +57 -0
- package/src/gumbo-parser/src/string_buffer.c +110 -0
- package/src/gumbo-parser/src/string_buffer.h +84 -0
- package/src/gumbo-parser/src/string_piece.c +48 -0
- package/src/gumbo-parser/src/string_piece.h +38 -0
- package/src/gumbo-parser/src/tag.c +95 -0
- package/src/gumbo-parser/src/tag_enum.h +153 -0
- package/src/gumbo-parser/src/tag_gperf.h +105 -0
- package/src/gumbo-parser/src/tag_sizes.h +4 -0
- package/src/gumbo-parser/src/tag_strings.h +153 -0
- package/src/gumbo-parser/src/token_type.h +41 -0
- package/src/gumbo-parser/src/tokenizer.c +2897 -0
- package/src/gumbo-parser/src/tokenizer.h +123 -0
- package/src/gumbo-parser/src/tokenizer_states.h +103 -0
- package/src/gumbo-parser/src/utf8.c +270 -0
- package/src/gumbo-parser/src/utf8.h +132 -0
- package/src/gumbo-parser/src/util.c +58 -0
- package/src/gumbo-parser/src/util.h +60 -0
- package/src/gumbo-parser/src/vector.c +123 -0
- package/src/gumbo-parser/src/vector.h +67 -0
- package/src/html_document.cc +411 -0
- package/src/html_document.h +56 -0
- package/src/html_element.cc +963 -0
- package/src/html_element.h +70 -0
- package/src/include/win/strings.h +11 -0
- package/src/jsa.c +182 -0
- package/src/jsa.h +44 -0
- package/src/xnode.c +372 -0
- package/src/xnode_query.c +330 -0
- package/src/xnode_query.h +186 -0
- package/src/xnode_query_parser.c +414 -0
- package/install.js +0 -15
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
#include "html_element.h"
|
|
2
|
+
#include "html_document.h"
|
|
3
|
+
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
namespace html {
|
|
9
|
+
|
|
10
|
+
Napi::FunctionReference Element::constructor;
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
bool GetStringArg(const Napi::CallbackInfo& info, size_t index,
|
|
15
|
+
const char *name, std::string *value) {
|
|
16
|
+
Napi::Env env = info.Env();
|
|
17
|
+
if (info.Length() <= index || !info[index].IsString()) {
|
|
18
|
+
Napi::TypeError::New(env, std::string(name) + " must be a string")
|
|
19
|
+
.ThrowAsJavaScriptException();
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
*value = info[index].As<Napi::String>().Utf8Value();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bool GetOptionalStringArg(const Napi::CallbackInfo& info, size_t index,
|
|
27
|
+
const char *name, std::string *value,
|
|
28
|
+
bool *has_value) {
|
|
29
|
+
if (info.Length() <= index || info[index].IsUndefined() || info[index].IsNull()) {
|
|
30
|
+
*has_value = false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
*has_value = true;
|
|
34
|
+
return GetStringArg(info, index, name, value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
bool HasNode(const Napi::CallbackInfo& info, Element *element) {
|
|
38
|
+
if (element->xnode_) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
Napi::Error::New(info.Env(), "Invalid element").ThrowAsJavaScriptException();
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class QueryHandle {
|
|
46
|
+
public:
|
|
47
|
+
explicit QueryHandle(const char *selector)
|
|
48
|
+
: query_(selector ? xnode_query_create(selector, NULL, NULL) : nullptr) {}
|
|
49
|
+
|
|
50
|
+
~QueryHandle() {
|
|
51
|
+
if (query_) {
|
|
52
|
+
xnode_query_free(query_);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
XNodeQuery *get() const { return query_; }
|
|
57
|
+
|
|
58
|
+
private:
|
|
59
|
+
XNodeQuery *query_;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
bool MatchQueryAt(XNode *node, XNodeQuery *query, int index) {
|
|
63
|
+
if (!node || !query || index < 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
XNodeSelector *selector = xnode_query_selector(query, index);
|
|
68
|
+
if (!xnode_match_node(node, selector)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (index == 0) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
switch (selector->combinator) {
|
|
77
|
+
case XQSEL_DESCENDANT: {
|
|
78
|
+
for (XNode *parent = xnode_parent(node); parent; parent = xnode_parent(parent)) {
|
|
79
|
+
if (MatchQueryAt(parent, query, index - 1)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
case XQSEL_CHILD:
|
|
86
|
+
return MatchQueryAt(xnode_parent(node), query, index - 1);
|
|
87
|
+
case XQSEL_ADJACENT_SIBLING:
|
|
88
|
+
return MatchQueryAt(xnode_prev(node, NULL), query, index - 1);
|
|
89
|
+
case XQSEL_GENERAL_SIBLING:
|
|
90
|
+
for (XNode *prev = xnode_prev(node, NULL); prev; prev = xnode_prev(prev, NULL)) {
|
|
91
|
+
if (MatchQueryAt(prev, query, index - 1)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
default:
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
bool MatchQuery(XNode *node, XNodeQuery *query) {
|
|
102
|
+
uint32_t count = jsa_size(&query->selectors);
|
|
103
|
+
return count > 0 && MatchQueryAt(node, query, static_cast<int>(count - 1));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
} // namespace
|
|
107
|
+
|
|
108
|
+
Element::Element(const Napi::CallbackInfo& info)
|
|
109
|
+
: Napi::ObjectWrap<Element>(info), xnode_(nullptr), xdoc_(nullptr) {
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Element::~Element() {
|
|
113
|
+
if (xdoc_ && --xdoc_->ref_count == 0) {
|
|
114
|
+
delete xdoc_;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
std::string get_inner_text(GumboNode *node) {
|
|
119
|
+
if (node->type == GUMBO_NODE_TEXT) {
|
|
120
|
+
return node->v.text.text;
|
|
121
|
+
} else if (node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE) {
|
|
122
|
+
std::string result;
|
|
123
|
+
GumboVector *children = xnode_children(node);
|
|
124
|
+
if (children) {
|
|
125
|
+
for (unsigned i = 0; i < children->length; ++i) {
|
|
126
|
+
GumboNode* child = (GumboNode*) children->data[i];
|
|
127
|
+
result += get_inner_text(child);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
return "";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
std::string normalize_whitespace(const std::string &s) {
|
|
136
|
+
std::string result;
|
|
137
|
+
result.reserve(s.size());
|
|
138
|
+
bool inSpace = false;
|
|
139
|
+
for (size_t i = 0; i < s.size(); i++) {
|
|
140
|
+
char c = s[i];
|
|
141
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v') {
|
|
142
|
+
if (!inSpace && !result.empty()) {
|
|
143
|
+
result += ' ';
|
|
144
|
+
}
|
|
145
|
+
inSpace = true;
|
|
146
|
+
} else {
|
|
147
|
+
result += c;
|
|
148
|
+
inSpace = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!result.empty() && result.back() == ' ') {
|
|
152
|
+
result.pop_back();
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
std::string join_text_with_separator(XNode *node, const std::string &sep) {
|
|
158
|
+
std::string result;
|
|
159
|
+
std::function<void(XNode*)> walk = [&](XNode *n) {
|
|
160
|
+
if (n->type == GUMBO_NODE_TEXT || n->type == GUMBO_NODE_WHITESPACE) {
|
|
161
|
+
std::string t(n->v.text.text);
|
|
162
|
+
size_t start = 0, end = t.size();
|
|
163
|
+
while (start < end && (t[start] == ' ' || t[start] == '\t' || t[start] == '\n' || t[start] == '\r')) start++;
|
|
164
|
+
while (end > start && (t[end-1] == ' ' || t[end-1] == '\t' || t[end-1] == '\n' || t[end-1] == '\r')) end--;
|
|
165
|
+
if (start < end) {
|
|
166
|
+
if (!result.empty()) result += sep;
|
|
167
|
+
result += t.substr(start, end - start);
|
|
168
|
+
}
|
|
169
|
+
} else if (n->type == GUMBO_NODE_ELEMENT || n->type == GUMBO_NODE_TEMPLATE) {
|
|
170
|
+
GumboVector *kids = xnode_children(n);
|
|
171
|
+
if (kids) {
|
|
172
|
+
for (unsigned i = 0; i < kids->length; i++) {
|
|
173
|
+
walk(static_cast<XNode*>(kids->data[i]));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
walk(node);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Napi::Value Element::GetTagName(const Napi::CallbackInfo& info) {
|
|
183
|
+
Napi::Env env = info.Env();
|
|
184
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
185
|
+
if (!HasNode(info, element)) {
|
|
186
|
+
return env.Undefined();
|
|
187
|
+
}
|
|
188
|
+
if (element->xnode_->type == GUMBO_NODE_ELEMENT ||
|
|
189
|
+
element->xnode_->type == GUMBO_NODE_TEMPLATE) {
|
|
190
|
+
size_t len;
|
|
191
|
+
const char *s = xnode_type(element->xnode_, &len);
|
|
192
|
+
return Napi::String::New(env, s, len);
|
|
193
|
+
}
|
|
194
|
+
return env.Null();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Napi::Value Element::GetInnerText(const Napi::CallbackInfo& info) {
|
|
198
|
+
Napi::Env env = info.Env();
|
|
199
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
200
|
+
if (!HasNode(info, element)) {
|
|
201
|
+
return env.Undefined();
|
|
202
|
+
}
|
|
203
|
+
if (element->xnode_->type == GUMBO_NODE_TEXT ||
|
|
204
|
+
element->xnode_->type == GUMBO_NODE_ELEMENT ||
|
|
205
|
+
element->xnode_->type == GUMBO_NODE_TEMPLATE) {
|
|
206
|
+
std::string text = get_inner_text(element->xnode_);
|
|
207
|
+
return Napi::String::New(env, text);
|
|
208
|
+
}
|
|
209
|
+
return env.Undefined();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Napi::Value Element::GetNodeType(const Napi::CallbackInfo& info) {
|
|
213
|
+
Napi::Env env = info.Env();
|
|
214
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
215
|
+
if (!HasNode(info, element)) {
|
|
216
|
+
return env.Undefined();
|
|
217
|
+
}
|
|
218
|
+
const char *type;
|
|
219
|
+
switch (element->xnode_->type) {
|
|
220
|
+
case GUMBO_NODE_DOCUMENT: type = "DOCUMENT"; break;
|
|
221
|
+
case GUMBO_NODE_ELEMENT: type = "ELEMENT"; break;
|
|
222
|
+
case GUMBO_NODE_TEXT: type = "TEXT"; break;
|
|
223
|
+
case GUMBO_NODE_CDATA: type = "CDATA"; break;
|
|
224
|
+
case GUMBO_NODE_COMMENT: type = "COMMENT"; break;
|
|
225
|
+
case GUMBO_NODE_WHITESPACE: type = "WHITESPACE"; break;
|
|
226
|
+
case GUMBO_NODE_TEMPLATE: type = "TEMPLATE"; break;
|
|
227
|
+
default: type = "UNKNOWN"; break;
|
|
228
|
+
}
|
|
229
|
+
return Napi::String::New(env, type);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Napi::Value Element::GetChildNodes(const Napi::CallbackInfo& info) {
|
|
233
|
+
Napi::Env env = info.Env();
|
|
234
|
+
Napi::HandleScope scope(env);
|
|
235
|
+
Element *element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
236
|
+
if (!HasNode(info, element)) {
|
|
237
|
+
return env.Undefined();
|
|
238
|
+
}
|
|
239
|
+
GumboNodeType type = element->xnode_->type;
|
|
240
|
+
if ((type == GUMBO_NODE_ELEMENT) || (type == GUMBO_NODE_TEMPLATE)) {
|
|
241
|
+
GumboVector* children = &element->xnode_->v.element.children;
|
|
242
|
+
Napi::Array array = Napi::Array::New(env, children->length);
|
|
243
|
+
for (unsigned int i = 0; i < children->length; ++i) {
|
|
244
|
+
XNode* child = static_cast<XNode*> (children->data[i]);
|
|
245
|
+
array.Set(i, Create(env, element->xdoc_, child));
|
|
246
|
+
}
|
|
247
|
+
return array;
|
|
248
|
+
}
|
|
249
|
+
return Napi::Array::New(env, 0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Napi::Value Element::GetOuterHTML(const Napi::CallbackInfo& info) {
|
|
253
|
+
Napi::Env env = info.Env();
|
|
254
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
255
|
+
if (!HasNode(info, element)) {
|
|
256
|
+
return env.Undefined();
|
|
257
|
+
}
|
|
258
|
+
size_t len;
|
|
259
|
+
const char *s = xnode_html(element->xnode_, &len);
|
|
260
|
+
return Napi::String::New(env, s, len);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
Napi::Value Element::Create(Napi::Env env, XDocWrapper *xdoc, XNode *xnode) {
|
|
264
|
+
if (xdoc && xnode) {
|
|
265
|
+
Napi::EscapableHandleScope scope(env);
|
|
266
|
+
Napi::Object obj = Element::constructor.New({});
|
|
267
|
+
Element *element = Element::Unwrap(obj);
|
|
268
|
+
element->xnode_ = xnode;
|
|
269
|
+
element->xdoc_ = xdoc;
|
|
270
|
+
xdoc->ref_count++;
|
|
271
|
+
return scope.Escape(obj);
|
|
272
|
+
}
|
|
273
|
+
return env.Null();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Napi::Value Element::GetParent(const Napi::CallbackInfo& info) {
|
|
277
|
+
Napi::Env env = info.Env();
|
|
278
|
+
Element *element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
279
|
+
if (!HasNode(info, element)) {
|
|
280
|
+
return env.Undefined();
|
|
281
|
+
}
|
|
282
|
+
XNode *parent = xnode_parent(element->xnode_);
|
|
283
|
+
if (!parent || parent->type == GUMBO_NODE_DOCUMENT) {
|
|
284
|
+
return env.Null();
|
|
285
|
+
}
|
|
286
|
+
return Create(env, element->xdoc_, parent);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
void Element::Init(Napi::Env env) {
|
|
290
|
+
Napi::Function func = DefineClass(env, "Element", {
|
|
291
|
+
InstanceMethod("attr", &Element::Attr),
|
|
292
|
+
InstanceMethod("attr_s", &Element::AttrSafe),
|
|
293
|
+
InstanceMethod("find", &Element::Find),
|
|
294
|
+
InstanceMethod("first", &Element::First),
|
|
295
|
+
InstanceMethod("first_s", &Element::FirstSafe),
|
|
296
|
+
InstanceMethod("only", &Element::Only),
|
|
297
|
+
InstanceMethod("only_s", &Element::OnlySafe),
|
|
298
|
+
InstanceMethod("hasClass", &Element::HasClass),
|
|
299
|
+
InstanceMethod("hasAttribute", &Element::HasAttribute),
|
|
300
|
+
InstanceMethod("next", &Element::Next),
|
|
301
|
+
InstanceMethod("prev", &Element::Prev),
|
|
302
|
+
InstanceAccessor("tagName", &Element::GetTagName, nullptr),
|
|
303
|
+
InstanceAccessor("innerText", &Element::GetInnerText, nullptr),
|
|
304
|
+
InstanceAccessor("outerHTML", &Element::GetOuterHTML, nullptr),
|
|
305
|
+
InstanceAccessor("parent", &Element::GetParent, nullptr),
|
|
306
|
+
InstanceAccessor("nodeType", &Element::GetNodeType, nullptr),
|
|
307
|
+
InstanceAccessor("childNodes", &Element::GetChildNodes, nullptr),
|
|
308
|
+
|
|
309
|
+
InstanceMethod("firstOrThrow", &Element::FirstOrThrow),
|
|
310
|
+
InstanceMethod("onlyOrThrow", &Element::OnlyOrThrow),
|
|
311
|
+
InstanceMethod("attrOrThrow", &Element::AttrOrThrow),
|
|
312
|
+
InstanceMethod("text", &Element::Text),
|
|
313
|
+
InstanceMethod("textOrThrow", &Element::TextOrThrow),
|
|
314
|
+
InstanceMethod("exists", &Element::Exists),
|
|
315
|
+
InstanceMethod("count", &Element::Count),
|
|
316
|
+
InstanceMethod("closest", &Element::Closest),
|
|
317
|
+
InstanceMethod("children", &Element::Children),
|
|
318
|
+
InstanceMethod("siblings", &Element::Siblings),
|
|
319
|
+
InstanceMethod("matches", &Element::Matches),
|
|
320
|
+
InstanceMethod("is", &Element::Is),
|
|
321
|
+
InstanceMethod("rows", &Element::Rows),
|
|
322
|
+
InstanceAccessor("textContent", &Element::GetTextContent, nullptr),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
constructor = Napi::Persistent(func);
|
|
326
|
+
constructor.SuppressDestruct();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
Napi::Value Element::Attr(const Napi::CallbackInfo& info) {
|
|
330
|
+
Napi::Env env = info.Env();
|
|
331
|
+
|
|
332
|
+
if (info.Length() != 1) {
|
|
333
|
+
Napi::Error::New(env, "Empty name.").ThrowAsJavaScriptException();
|
|
334
|
+
return env.Undefined();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Napi::HandleScope scope(env);
|
|
338
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
339
|
+
if (!HasNode(info, element)) {
|
|
340
|
+
return env.Undefined();
|
|
341
|
+
}
|
|
342
|
+
std::string name;
|
|
343
|
+
if (!GetStringArg(info, 0, "name", &name)) {
|
|
344
|
+
return env.Undefined();
|
|
345
|
+
}
|
|
346
|
+
const char *value = xnode_attr(element->xnode_, name.c_str());
|
|
347
|
+
if (value) {
|
|
348
|
+
return Napi::String::New(env, value);
|
|
349
|
+
}
|
|
350
|
+
return env.Undefined();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
Napi::Value Element::AttrSafe(const Napi::CallbackInfo& info) {
|
|
354
|
+
Napi::Env env = info.Env();
|
|
355
|
+
|
|
356
|
+
if (info.Length() != 1) {
|
|
357
|
+
Napi::Error::New(env, "Empty name.").ThrowAsJavaScriptException();
|
|
358
|
+
return env.Undefined();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Napi::HandleScope scope(env);
|
|
362
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
363
|
+
if (!HasNode(info, element)) {
|
|
364
|
+
return env.Undefined();
|
|
365
|
+
}
|
|
366
|
+
std::string name;
|
|
367
|
+
if (!GetStringArg(info, 0, "name", &name)) {
|
|
368
|
+
return env.Undefined();
|
|
369
|
+
}
|
|
370
|
+
const char *value = xnode_attr(element->xnode_, name.c_str());
|
|
371
|
+
if (value) {
|
|
372
|
+
return Napi::String::New(env, value);
|
|
373
|
+
}
|
|
374
|
+
Napi::Error::New(env, "Attribute not found").ThrowAsJavaScriptException();
|
|
375
|
+
return env.Undefined();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
Napi::Value Element::Find(const Napi::CallbackInfo& info) {
|
|
379
|
+
Napi::Env env = info.Env();
|
|
380
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
381
|
+
if (!HasNode(info, elem)) {
|
|
382
|
+
return env.Undefined();
|
|
383
|
+
}
|
|
384
|
+
std::string selector;
|
|
385
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
386
|
+
return env.Undefined();
|
|
387
|
+
}
|
|
388
|
+
return Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
Napi::Value Element::First(const Napi::CallbackInfo& info) {
|
|
392
|
+
Napi::Env env = info.Env();
|
|
393
|
+
Napi::HandleScope scope(env);
|
|
394
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
395
|
+
if (!HasNode(info, elem)) {
|
|
396
|
+
return env.Undefined();
|
|
397
|
+
}
|
|
398
|
+
std::string selector;
|
|
399
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
400
|
+
return env.Undefined();
|
|
401
|
+
}
|
|
402
|
+
Napi::Value value = Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
403
|
+
if (!value.IsArray()) {
|
|
404
|
+
return env.Undefined();
|
|
405
|
+
}
|
|
406
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
407
|
+
if (array.Length() > 0) {
|
|
408
|
+
return array.Get(uint32_t(0));
|
|
409
|
+
}
|
|
410
|
+
return env.Null();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
Napi::Value Element::FirstSafe(const Napi::CallbackInfo& info) {
|
|
414
|
+
Napi::Env env = info.Env();
|
|
415
|
+
Napi::HandleScope scope(env);
|
|
416
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
417
|
+
if (!HasNode(info, elem)) {
|
|
418
|
+
return env.Undefined();
|
|
419
|
+
}
|
|
420
|
+
std::string selector;
|
|
421
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
422
|
+
return env.Undefined();
|
|
423
|
+
}
|
|
424
|
+
Napi::Value value = Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
425
|
+
if (!value.IsArray()) {
|
|
426
|
+
return env.Undefined();
|
|
427
|
+
}
|
|
428
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
429
|
+
if (array.Length() > 0) {
|
|
430
|
+
return array.Get(uint32_t(0));
|
|
431
|
+
}
|
|
432
|
+
Napi::Error::New(env, "No element found").ThrowAsJavaScriptException();
|
|
433
|
+
return env.Undefined();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
Napi::Value Element::Only(const Napi::CallbackInfo& info) {
|
|
437
|
+
Napi::Env env = info.Env();
|
|
438
|
+
Napi::HandleScope scope(env);
|
|
439
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
440
|
+
if (!HasNode(info, elem)) {
|
|
441
|
+
return env.Undefined();
|
|
442
|
+
}
|
|
443
|
+
std::string selector;
|
|
444
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
445
|
+
return env.Undefined();
|
|
446
|
+
}
|
|
447
|
+
Napi::Value value = Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
448
|
+
if (!value.IsArray()) {
|
|
449
|
+
return env.Undefined();
|
|
450
|
+
}
|
|
451
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
452
|
+
if (array.Length() == 1) {
|
|
453
|
+
return array.Get(uint32_t(0));
|
|
454
|
+
}
|
|
455
|
+
return env.Null();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
Napi::Value Element::OnlySafe(const Napi::CallbackInfo& info) {
|
|
459
|
+
Napi::Env env = info.Env();
|
|
460
|
+
Napi::HandleScope scope(env);
|
|
461
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
462
|
+
if (!HasNode(info, elem)) {
|
|
463
|
+
return env.Undefined();
|
|
464
|
+
}
|
|
465
|
+
std::string selector;
|
|
466
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
467
|
+
return env.Undefined();
|
|
468
|
+
}
|
|
469
|
+
Napi::Value value = Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
470
|
+
if (!value.IsArray()) {
|
|
471
|
+
return env.Undefined();
|
|
472
|
+
}
|
|
473
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
474
|
+
if (array.Length() == 1) {
|
|
475
|
+
return array.Get(uint32_t(0));
|
|
476
|
+
}
|
|
477
|
+
Napi::Error::New(env, "Not a single element").ThrowAsJavaScriptException();
|
|
478
|
+
return env.Undefined();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
Napi::Value Element::HasClass(const Napi::CallbackInfo& info) {
|
|
482
|
+
Napi::Env env = info.Env();
|
|
483
|
+
if (info.Length() < 1) {
|
|
484
|
+
Napi::Error::New(env, "No class name.").ThrowAsJavaScriptException();
|
|
485
|
+
return env.Undefined();
|
|
486
|
+
}
|
|
487
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
488
|
+
if (!HasNode(info, elem)) {
|
|
489
|
+
return env.Undefined();
|
|
490
|
+
}
|
|
491
|
+
std::string name;
|
|
492
|
+
if (!GetStringArg(info, 0, "name", &name)) {
|
|
493
|
+
return env.Undefined();
|
|
494
|
+
}
|
|
495
|
+
bool value = xnode_has_class(elem->xnode_, name.c_str());
|
|
496
|
+
return Napi::Boolean::New(env, value);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
Napi::Value Element::HasAttribute(const Napi::CallbackInfo& info) {
|
|
500
|
+
Napi::Env env = info.Env();
|
|
501
|
+
if (info.Length() < 1) {
|
|
502
|
+
Napi::Error::New(env, "No attribute name.").ThrowAsJavaScriptException();
|
|
503
|
+
return env.Undefined();
|
|
504
|
+
}
|
|
505
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
506
|
+
if (!HasNode(info, elem)) {
|
|
507
|
+
return env.Undefined();
|
|
508
|
+
}
|
|
509
|
+
std::string name;
|
|
510
|
+
if (!GetStringArg(info, 0, "name", &name)) {
|
|
511
|
+
return env.Undefined();
|
|
512
|
+
}
|
|
513
|
+
bool value = xnode_has_attr(elem->xnode_, name.c_str());
|
|
514
|
+
return Napi::Boolean::New(env, value);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
Napi::Value Element::Next(const Napi::CallbackInfo& info) {
|
|
518
|
+
Napi::Env env = info.Env();
|
|
519
|
+
Napi::HandleScope scope(env);
|
|
520
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
521
|
+
if (!HasNode(info, elem)) {
|
|
522
|
+
return env.Undefined();
|
|
523
|
+
}
|
|
524
|
+
std::string selector;
|
|
525
|
+
bool has_selector = false;
|
|
526
|
+
if (!GetOptionalStringArg(info, 0, "selector", &selector, &has_selector)) {
|
|
527
|
+
return env.Undefined();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (has_selector) {
|
|
531
|
+
QueryHandle query(selector.c_str());
|
|
532
|
+
if (!query.get()) {
|
|
533
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
534
|
+
return env.Undefined();
|
|
535
|
+
}
|
|
536
|
+
for (XNode *next = xnode_next(elem->xnode_, NULL); next; next = xnode_next(next, NULL)) {
|
|
537
|
+
if (MatchQuery(next, query.get())) {
|
|
538
|
+
return Create(env, elem->xdoc_, next);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return env.Null();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
XNode *next = xnode_next(elem->xnode_, NULL);
|
|
545
|
+
return Create(env, elem->xdoc_, next);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
Napi::Value Element::Prev(const Napi::CallbackInfo& info) {
|
|
549
|
+
Napi::Env env = info.Env();
|
|
550
|
+
Napi::HandleScope scope(env);
|
|
551
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
552
|
+
if (!HasNode(info, elem)) {
|
|
553
|
+
return env.Undefined();
|
|
554
|
+
}
|
|
555
|
+
std::string selector;
|
|
556
|
+
bool has_selector = false;
|
|
557
|
+
if (!GetOptionalStringArg(info, 0, "selector", &selector, &has_selector)) {
|
|
558
|
+
return env.Undefined();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (has_selector) {
|
|
562
|
+
QueryHandle query(selector.c_str());
|
|
563
|
+
if (!query.get()) {
|
|
564
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
565
|
+
return env.Undefined();
|
|
566
|
+
}
|
|
567
|
+
for (XNode *prev = xnode_prev(elem->xnode_, NULL); prev; prev = xnode_prev(prev, NULL)) {
|
|
568
|
+
if (MatchQuery(prev, query.get())) {
|
|
569
|
+
return Create(env, elem->xdoc_, prev);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return env.Null();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
XNode *prev = xnode_prev(elem->xnode_, NULL);
|
|
576
|
+
return Create(env, elem->xdoc_, prev);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
Napi::Value Element::Query(Napi::Env env, XDocWrapper *wrapper,
|
|
580
|
+
const XNode *node, const char *selector) {
|
|
581
|
+
QueryHandle query(selector);
|
|
582
|
+
if (!query.get()) {
|
|
583
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
584
|
+
return env.Undefined();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
Napi::EscapableHandleScope scope(env);
|
|
588
|
+
const XNodeArray *result = xnode_query_execute(query.get(), node);
|
|
589
|
+
Napi::Array array = Napi::Array::New(
|
|
590
|
+
env, result ? xnode_array_size(result) : 0);
|
|
591
|
+
if (result) {
|
|
592
|
+
for (uint32_t i = 0; i < xnode_array_size(result); i++) {
|
|
593
|
+
Napi::Value obj = Create(env, wrapper, xnode_array_get(result, i));
|
|
594
|
+
array.Set(i, obj);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return scope.Escape(array);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
Napi::Value Element::FirstOrThrow(const Napi::CallbackInfo& info) {
|
|
602
|
+
return FirstSafe(info);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
Napi::Value Element::OnlyOrThrow(const Napi::CallbackInfo& info) {
|
|
606
|
+
return OnlySafe(info);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
Napi::Value Element::AttrOrThrow(const Napi::CallbackInfo& info) {
|
|
610
|
+
return AttrSafe(info);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
Napi::Value Element::Text(const Napi::CallbackInfo& info) {
|
|
614
|
+
Napi::Env env = info.Env();
|
|
615
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
616
|
+
if (!HasNode(info, elem)) {
|
|
617
|
+
return env.Undefined();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
bool isSelector = (info.Length() > 0 && info[0].IsString());
|
|
621
|
+
bool isOptions = (info.Length() > 0 && info[0].IsObject());
|
|
622
|
+
|
|
623
|
+
auto applyOptions = [env](XNode *node, const Napi::Object &opts) -> Napi::Value {
|
|
624
|
+
std::string text;
|
|
625
|
+
if (opts.Has("separator") && opts.Get("separator").IsString()) {
|
|
626
|
+
std::string sep = opts.Get("separator").As<Napi::String>();
|
|
627
|
+
text = join_text_with_separator(node, sep);
|
|
628
|
+
} else {
|
|
629
|
+
text = get_inner_text(node);
|
|
630
|
+
}
|
|
631
|
+
if (opts.Has("normalize") && opts.Get("normalize").As<Napi::Boolean>()) {
|
|
632
|
+
text = normalize_whitespace(text);
|
|
633
|
+
}
|
|
634
|
+
return Napi::String::New(env, text);
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
if (isSelector) {
|
|
638
|
+
std::string selector = info[0].As<Napi::String>();
|
|
639
|
+
Napi::Value value = Element::Query(env, elem->xdoc_, elem->xnode_, selector.c_str());
|
|
640
|
+
if (!value.IsArray()) {
|
|
641
|
+
return env.Undefined();
|
|
642
|
+
}
|
|
643
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
644
|
+
if (array.Length() > 0) {
|
|
645
|
+
Napi::Value first = array.Get(uint32_t(0));
|
|
646
|
+
Element *match = Element::Unwrap(first.As<Napi::Object>());
|
|
647
|
+
if (info.Length() > 1 && info[1].IsObject()) {
|
|
648
|
+
return applyOptions(match->xnode_, info[1].As<Napi::Object>());
|
|
649
|
+
}
|
|
650
|
+
return Napi::String::New(env, get_inner_text(match->xnode_));
|
|
651
|
+
}
|
|
652
|
+
return env.Null();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (isOptions) {
|
|
656
|
+
return applyOptions(elem->xnode_, info[0].As<Napi::Object>());
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (info.Length() > 0 && !info[0].IsUndefined() && !info[0].IsNull()) {
|
|
660
|
+
Napi::TypeError::New(env, "selector or options object expected")
|
|
661
|
+
.ThrowAsJavaScriptException();
|
|
662
|
+
return env.Undefined();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return Napi::String::New(env, get_inner_text(elem->xnode_));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
Napi::Value Element::TextOrThrow(const Napi::CallbackInfo& info) {
|
|
669
|
+
Napi::Env env = info.Env();
|
|
670
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
671
|
+
if (!HasNode(info, elem)) {
|
|
672
|
+
return env.Undefined();
|
|
673
|
+
}
|
|
674
|
+
std::string selector;
|
|
675
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
676
|
+
return env.Undefined();
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
QueryHandle query(selector.c_str());
|
|
680
|
+
if (!query.get()) {
|
|
681
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
682
|
+
return env.Undefined();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const XNodeArray *result = xnode_query_execute(query.get(), elem->xnode_);
|
|
686
|
+
if (result && xnode_array_size(result) > 0) {
|
|
687
|
+
XNode *node = xnode_array_get(result, 0);
|
|
688
|
+
return Napi::String::New(env, get_inner_text(node));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
Napi::Error::New(env, "No element found").ThrowAsJavaScriptException();
|
|
692
|
+
return env.Undefined();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
Napi::Value Element::Exists(const Napi::CallbackInfo& info) {
|
|
696
|
+
Napi::Env env = info.Env();
|
|
697
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
698
|
+
if (!HasNode(info, elem)) {
|
|
699
|
+
return env.Undefined();
|
|
700
|
+
}
|
|
701
|
+
std::string selector;
|
|
702
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
703
|
+
return env.Undefined();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
QueryHandle query(selector.c_str());
|
|
707
|
+
if (!query.get()) {
|
|
708
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
709
|
+
return env.Undefined();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const XNodeArray *result = xnode_query_execute(query.get(), elem->xnode_);
|
|
713
|
+
return Napi::Boolean::New(env, result && xnode_array_size(result) > 0);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
Napi::Value Element::Count(const Napi::CallbackInfo& info) {
|
|
717
|
+
Napi::Env env = info.Env();
|
|
718
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
719
|
+
if (!HasNode(info, elem)) {
|
|
720
|
+
return env.Undefined();
|
|
721
|
+
}
|
|
722
|
+
std::string selector;
|
|
723
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
724
|
+
return env.Undefined();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
QueryHandle query(selector.c_str());
|
|
728
|
+
if (!query.get()) {
|
|
729
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
730
|
+
return env.Undefined();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const XNodeArray *result = xnode_query_execute(query.get(), elem->xnode_);
|
|
734
|
+
return Napi::Number::New(env, result ? xnode_array_size(result) : 0);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
Napi::Value Element::Closest(const Napi::CallbackInfo& info) {
|
|
738
|
+
Napi::Env env = info.Env();
|
|
739
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
740
|
+
if (!HasNode(info, elem)) {
|
|
741
|
+
return env.Undefined();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
std::string selector;
|
|
745
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
746
|
+
return env.Undefined();
|
|
747
|
+
}
|
|
748
|
+
QueryHandle query(selector.c_str());
|
|
749
|
+
if (!query.get()) {
|
|
750
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
751
|
+
return env.Undefined();
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
XNode *current = xnode_parent(elem->xnode_);
|
|
755
|
+
while (current) {
|
|
756
|
+
if (MatchQuery(current, query.get())) {
|
|
757
|
+
return Create(env, elem->xdoc_, current);
|
|
758
|
+
}
|
|
759
|
+
current = xnode_parent(current);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return env.Null();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
Napi::Value Element::Matches(const Napi::CallbackInfo& info) {
|
|
766
|
+
Napi::Env env = info.Env();
|
|
767
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
768
|
+
if (!HasNode(info, elem)) {
|
|
769
|
+
return env.Undefined();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
std::string selector;
|
|
773
|
+
if (!GetStringArg(info, 0, "selector", &selector)) {
|
|
774
|
+
return env.Undefined();
|
|
775
|
+
}
|
|
776
|
+
QueryHandle query(selector.c_str());
|
|
777
|
+
if (!query.get()) {
|
|
778
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
779
|
+
return env.Undefined();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return Napi::Boolean::New(env, MatchQuery(elem->xnode_, query.get()));
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
Napi::Value Element::Is(const Napi::CallbackInfo& info) {
|
|
786
|
+
return Matches(info);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
Napi::Value Element::Children(const Napi::CallbackInfo& info) {
|
|
790
|
+
Napi::Env env = info.Env();
|
|
791
|
+
Napi::HandleScope scope(env);
|
|
792
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
793
|
+
if (!HasNode(info, elem)) {
|
|
794
|
+
return env.Undefined();
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
GumboVector *kids = xnode_children(elem->xnode_);
|
|
798
|
+
if (!kids) {
|
|
799
|
+
return Napi::Array::New(env, 0);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
XNodeQuery *filter = nullptr;
|
|
803
|
+
std::string selStr;
|
|
804
|
+
if (info.Length() > 0 && info[0].IsString() && !info[0].IsUndefined() && !info[0].IsNull()) {
|
|
805
|
+
selStr = info[0].As<Napi::String>();
|
|
806
|
+
filter = xnode_query_create(selStr.c_str(), NULL, NULL);
|
|
807
|
+
if (!filter) {
|
|
808
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
809
|
+
return Napi::Array::New(env, 0);
|
|
810
|
+
}
|
|
811
|
+
} else if (info.Length() > 0 && !info[0].IsUndefined() && !info[0].IsNull()) {
|
|
812
|
+
Napi::TypeError::New(env, "selector must be a string")
|
|
813
|
+
.ThrowAsJavaScriptException();
|
|
814
|
+
return env.Undefined();
|
|
815
|
+
}
|
|
816
|
+
Napi::Array result = Napi::Array::New(env);
|
|
817
|
+
uint32_t count = 0;
|
|
818
|
+
for (unsigned i = 0; i < kids->length; ++i) {
|
|
819
|
+
XNode *child = static_cast<XNode*>(kids->data[i]);
|
|
820
|
+
if (child->type != GUMBO_NODE_ELEMENT && child->type != GUMBO_NODE_TEMPLATE) continue;
|
|
821
|
+
if (filter && !MatchQuery(child, filter)) continue;
|
|
822
|
+
result.Set(count++, Create(env, elem->xdoc_, child));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (filter) {
|
|
826
|
+
xnode_query_free(filter);
|
|
827
|
+
}
|
|
828
|
+
return result;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
Napi::Value Element::Siblings(const Napi::CallbackInfo& info) {
|
|
832
|
+
Napi::Env env = info.Env();
|
|
833
|
+
Napi::HandleScope scope(env);
|
|
834
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
835
|
+
if (!HasNode(info, elem)) {
|
|
836
|
+
return env.Undefined();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
XNode *parent = xnode_parent(elem->xnode_);
|
|
840
|
+
if (!parent) {
|
|
841
|
+
return Napi::Array::New(env, 0);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
GumboVector *kids = xnode_children(parent);
|
|
845
|
+
if (!kids) {
|
|
846
|
+
return Napi::Array::New(env, 0);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
XNodeQuery *filter = nullptr;
|
|
850
|
+
std::string selStr;
|
|
851
|
+
if (info.Length() > 0 && info[0].IsString() && !info[0].IsUndefined() && !info[0].IsNull()) {
|
|
852
|
+
selStr = info[0].As<Napi::String>();
|
|
853
|
+
filter = xnode_query_create(selStr.c_str(), NULL, NULL);
|
|
854
|
+
if (!filter) {
|
|
855
|
+
Napi::Error::New(env, "Bad selector.").ThrowAsJavaScriptException();
|
|
856
|
+
return Napi::Array::New(env, 0);
|
|
857
|
+
}
|
|
858
|
+
} else if (info.Length() > 0 && !info[0].IsUndefined() && !info[0].IsNull()) {
|
|
859
|
+
Napi::TypeError::New(env, "selector must be a string")
|
|
860
|
+
.ThrowAsJavaScriptException();
|
|
861
|
+
return env.Undefined();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
Napi::Array result = Napi::Array::New(env);
|
|
865
|
+
uint32_t count = 0;
|
|
866
|
+
for (unsigned i = 0; i < kids->length; ++i) {
|
|
867
|
+
XNode *sibling = static_cast<XNode*>(kids->data[i]);
|
|
868
|
+
if (sibling == elem->xnode_) continue;
|
|
869
|
+
if (sibling->type != GUMBO_NODE_ELEMENT && sibling->type != GUMBO_NODE_TEMPLATE) continue;
|
|
870
|
+
if (filter && !MatchQuery(sibling, filter)) continue;
|
|
871
|
+
result.Set(count++, Create(env, elem->xdoc_, sibling));
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (filter) {
|
|
875
|
+
xnode_query_free(filter);
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
Napi::Value Element::Rows(const Napi::CallbackInfo& info) {
|
|
881
|
+
Napi::Env env = info.Env();
|
|
882
|
+
Napi::HandleScope scope(env);
|
|
883
|
+
Element* elem = Element::Unwrap(info.This().As<Napi::Object>());
|
|
884
|
+
if (!HasNode(info, elem)) {
|
|
885
|
+
return env.Undefined();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
QueryHandle trQuery("tr");
|
|
889
|
+
if (!trQuery.get()) {
|
|
890
|
+
return Napi::Array::New(env, 0);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const XNodeArray *rows = xnode_query_execute(trQuery.get(), elem->xnode_);
|
|
894
|
+
if (!rows || xnode_array_size(rows) == 0) {
|
|
895
|
+
return Napi::Array::New(env, 0);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
uint32_t rowCount = xnode_array_size(rows);
|
|
899
|
+
|
|
900
|
+
// Collect headers from the first row when it contains header cells.
|
|
901
|
+
std::vector<std::string> headers;
|
|
902
|
+
if (rowCount > 0) {
|
|
903
|
+
XNode *firstRow = xnode_array_get(rows, 0);
|
|
904
|
+
GumboVector *firstRowChildren = xnode_children(firstRow);
|
|
905
|
+
if (firstRowChildren) {
|
|
906
|
+
for (unsigned i = 0; i < firstRowChildren->length; i++) {
|
|
907
|
+
XNode *cell = static_cast<XNode*>(firstRowChildren->data[i]);
|
|
908
|
+
if ((cell->type == GUMBO_NODE_ELEMENT || cell->type == GUMBO_NODE_TEMPLATE) &&
|
|
909
|
+
cell->v.element.tag == GUMBO_TAG_TH) {
|
|
910
|
+
headers.push_back(get_inner_text(cell));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
uint32_t startRow = headers.empty() ? 0 : 1;
|
|
917
|
+
Napi::Array result = Napi::Array::New(env);
|
|
918
|
+
uint32_t resultIdx = 0;
|
|
919
|
+
|
|
920
|
+
for (uint32_t r = startRow; r < rowCount; r++) {
|
|
921
|
+
XNode *row = xnode_array_get(rows, r);
|
|
922
|
+
GumboVector *rowChildren = xnode_children(row);
|
|
923
|
+
if (!rowChildren) continue;
|
|
924
|
+
|
|
925
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
926
|
+
uint32_t colIdx = 0;
|
|
927
|
+
|
|
928
|
+
for (unsigned c = 0; c < rowChildren->length; c++) {
|
|
929
|
+
XNode *cell = static_cast<XNode*>(rowChildren->data[c]);
|
|
930
|
+
if (cell->type != GUMBO_NODE_ELEMENT && cell->type != GUMBO_NODE_TEMPLATE) continue;
|
|
931
|
+
if (!headers.empty() && cell->v.element.tag != GUMBO_TAG_TD) continue;
|
|
932
|
+
if (headers.empty() &&
|
|
933
|
+
cell->v.element.tag != GUMBO_TAG_TD &&
|
|
934
|
+
cell->v.element.tag != GUMBO_TAG_TH) continue;
|
|
935
|
+
|
|
936
|
+
std::string text = get_inner_text(cell);
|
|
937
|
+
|
|
938
|
+
if (colIdx < headers.size()) {
|
|
939
|
+
obj.Set(headers[colIdx], Napi::String::New(env, text));
|
|
940
|
+
} else {
|
|
941
|
+
obj.Set(colIdx, Napi::String::New(env, text));
|
|
942
|
+
}
|
|
943
|
+
colIdx++;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (colIdx > 0) {
|
|
947
|
+
result.Set(resultIdx++, obj);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return result;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
Napi::Value Element::GetTextContent(const Napi::CallbackInfo& info) {
|
|
955
|
+
Napi::Env env = info.Env();
|
|
956
|
+
Element* element = Element::Unwrap(info.This().As<Napi::Object>());
|
|
957
|
+
if (!HasNode(info, element)) {
|
|
958
|
+
return env.Undefined();
|
|
959
|
+
}
|
|
960
|
+
return Napi::String::New(env, get_inner_text(element->xnode_));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
} // namespace html
|