gumbo-html 0.3.0 → 0.3.1

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/API.md ADDED
@@ -0,0 +1,439 @@
1
+ # API
2
+
3
+ `gumbo-html` is a native Node.js addon that parses HTML with Gumbo and exposes
4
+ CSS-selector helpers for querying, traversal, and common scraping tasks.
5
+
6
+ The package ships TypeScript declarations in `index.d.ts`. The examples below
7
+ use both CommonJS and TypeScript-style imports where useful.
8
+
9
+ ## Importing
10
+
11
+ ```js
12
+ const { parse } = require('gumbo-html');
13
+ ```
14
+
15
+ ```ts
16
+ import { parse, type XDocument, type XElement } from 'gumbo-html';
17
+ ```
18
+
19
+ ## `parse(html, options?)`
20
+
21
+ ```ts
22
+ function parse(html: string, options?: { baseUrl?: string }): XDocument;
23
+ ```
24
+
25
+ Parses an HTML string and returns an `XDocument`.
26
+
27
+ ```js
28
+ const { parse } = require('gumbo-html');
29
+
30
+ const doc = parse(`
31
+ <article class="post">
32
+ <h1>Hello</h1>
33
+ <a href="/posts/hello">Read more</a>
34
+ </article>
35
+ `);
36
+
37
+ console.log(doc.firstOrThrow('h1').innerText); // "Hello"
38
+ ```
39
+
40
+ `html` must be a string. Passing `undefined`, `null`, or another type throws a
41
+ `TypeError` with the message `html must be a string`.
42
+
43
+ ### `options.baseUrl`
44
+
45
+ `baseUrl` is used by URL helper methods to resolve relative URLs.
46
+
47
+ ```js
48
+ const doc = parse('<a href="/docs">Docs</a>', {
49
+ baseUrl: 'https://example.com/app/',
50
+ });
51
+
52
+ console.log(doc.url('a', 'href')); // "https://example.com/docs"
53
+ console.log(doc.links()); // [{ text: "Docs", href: "https://example.com/docs" }]
54
+ ```
55
+
56
+ ## Selector Support
57
+
58
+ Selectors are CSS-like and scoped to the document or element you call from.
59
+
60
+ Supported selector features:
61
+
62
+ - Type selectors: `article`, `h1`
63
+ - Universal selector: `*`
64
+ - ID selectors: `#main`
65
+ - Class selectors: `.post`, `article.featured`
66
+ - Attribute selectors: `[href]`, `[rel="canonical"]`, `[class~="active"]`
67
+ - Attribute operators: `=`, `~=`, `|=`, `^=`, `$=`, `*=`
68
+ - Combinators: descendant `A B`, child `A > B`, adjacent sibling `A + B`,
69
+ general sibling `A ~ B`
70
+ - Relative selectors beginning with a combinator, such as `> li`, when querying
71
+ from a specific context
72
+
73
+ Unsupported selector features include selector lists (`a, img`), pseudo-classes
74
+ (`:first-child`, `:not(...)`, `:nth-child(...)`), pseudo-elements, namespaces,
75
+ and general CSS identifier escaping.
76
+
77
+ Invalid selectors throw `Error: Bad selector.`. Missing or non-string selector
78
+ arguments throw `TypeError: selector must be a string`.
79
+
80
+ Tag and attribute names are matched case-insensitively. Attribute values are
81
+ matched case-sensitively.
82
+
83
+ ## Return Conventions
84
+
85
+ Many methods have optional and throwing forms:
86
+
87
+ - Optional element lookups return `null` when no matching element exists.
88
+ - Optional attribute lookups return `undefined` when the attribute is missing.
89
+ - Throwing lookup methods end in `OrThrow` and throw when the required match is
90
+ missing or not unique.
91
+ - Query methods return JavaScript arrays.
92
+ - `childNodes` can include text, whitespace, comment, CDATA, and element nodes.
93
+
94
+ ## Types
95
+
96
+ ```ts
97
+ type NodeType =
98
+ | 'DOCUMENT'
99
+ | 'ELEMENT'
100
+ | 'TEXT'
101
+ | 'CDATA'
102
+ | 'COMMENT'
103
+ | 'WHITESPACE'
104
+ | 'TEMPLATE'
105
+ | 'UNKNOWN';
106
+
107
+ type TextOptions = {
108
+ normalize?: boolean;
109
+ separator?: string;
110
+ };
111
+ ```
112
+
113
+ `normalize: true` trims leading/trailing whitespace and collapses ASCII
114
+ whitespace runs into single spaces. `separator` joins non-empty descendant text
115
+ nodes with the provided separator.
116
+
117
+ ## `XDocument`
118
+
119
+ An `XDocument` represents the parsed document. `doc.documentElement` is the root
120
+ HTML element produced by Gumbo.
121
+
122
+ ### Properties
123
+
124
+ | Property | Type | Description |
125
+ | --- | --- | --- |
126
+ | `documentElement` | `XElement` | Root element, usually `<html>`. |
127
+ | `innerText` | `string` | Text from `documentElement`. |
128
+ | `textContent` | `string` | Same text extraction as `innerText`. |
129
+ | `outerHTML` | `string` | Serialized root element HTML. |
130
+ | `tagName` | `null` | Documents do not have a tag name. |
131
+ | `nodeType` | `'DOCUMENT'` | Document node type. |
132
+
133
+ ### Query Methods
134
+
135
+ | Method | Returns | Description |
136
+ | --- | --- | --- |
137
+ | `find(selector)` | `XElement[]` | All matching descendants. |
138
+ | `first(selector)` | `XElement \| null` | First matching descendant or `null`. |
139
+ | `firstOrThrow(selector)` | `XElement` | First match, or throws `No element found`. |
140
+ | `only(selector)` | `XElement \| null` | Match only when exactly one element exists; otherwise `null`. |
141
+ | `onlyOrThrow(selector)` | `XElement` | Exactly one match, or throws `Not a single element`. |
142
+
143
+ ```js
144
+ const doc = parse('<main><p>A</p><p>B</p></main>');
145
+
146
+ console.log(doc.find('p').length); // 2
147
+ console.log(doc.first('h1')); // null
148
+ console.log(doc.only('main').tagName); // "main"
149
+ ```
150
+
151
+ ### Convenience Methods
152
+
153
+ | Method | Returns | Description |
154
+ | --- | --- | --- |
155
+ | `text(selector, opts?)` | `string \| null` | Text of the first match, or `null`. |
156
+ | `textOrThrow(selector)` | `string` | Text of the first match, or throws `No element found`. |
157
+ | `attr(selector, name)` | `string \| undefined` | Attribute from the first match. |
158
+ | `attrOrThrow(selector, name)` | `string` | Attribute from the first match, or throws `Attribute not found`. |
159
+ | `exists(selector)` | `boolean` | Whether at least one element matches. |
160
+ | `count(selector)` | `number` | Number of matching elements. |
161
+ | `url(selector, attr)` | `string \| undefined` | Resolved URL attribute from the first match. |
162
+
163
+ ```js
164
+ const doc = parse(`
165
+ <article>
166
+ <h1> Hello World </h1>
167
+ <a href="/hello">Read</a>
168
+ </article>
169
+ `, { baseUrl: 'https://example.com/' });
170
+
171
+ console.log(doc.text('h1', { normalize: true })); // "Hello World"
172
+ console.log(doc.attr('a', 'href')); // "/hello"
173
+ console.log(doc.url('a', 'href')); // "https://example.com/hello"
174
+ console.log(doc.exists('article')); // true
175
+ console.log(doc.count('a')); // 1
176
+ ```
177
+
178
+ ### Scraping Helpers
179
+
180
+ | Method | Returns | Description |
181
+ | --- | --- | --- |
182
+ | `meta()` | `{ [key: string]: string }` | Metadata from `meta[name]`, `meta[property]`, and `meta[http-equiv]` using `content` values. |
183
+ | `links()` | `{ text: string; href: string }[]` | All `a[href]` elements. `href` is resolved with `baseUrl` when provided. |
184
+ | `images()` | `{ alt: string; src: string }[]` | All `img[src]` elements. Missing `alt` becomes `''`; `src` is resolved with `baseUrl` when provided. |
185
+ | `forms()` | `XElement[]` | All `form` elements. |
186
+ | `tables()` | `XElement[]` | All `table` elements. |
187
+ | `table(selector?)` | `Array<{ [header: string]: string }>` | Rows from the first matching table, or the first table when no selector is given. |
188
+ | `title()` | `string \| null` | Text from the first `title` element. |
189
+ | `description()` | `string \| undefined` | `content` from `meta[name="description"]`. |
190
+ | `canonicalUrl()` | `string \| undefined` | Raw `href` from `link[rel="canonical"]`. |
191
+
192
+ ```js
193
+ const doc = parse(`
194
+ <head>
195
+ <title>Example</title>
196
+ <meta name="description" content="Demo page">
197
+ <meta property="og:title" content="Example OG">
198
+ </head>
199
+ <body>
200
+ <a href="/a">A</a>
201
+ <img src="/a.png" alt="A">
202
+ </body>
203
+ `, { baseUrl: 'https://example.com/' });
204
+
205
+ console.log(doc.title()); // "Example"
206
+ console.log(doc.description()); // "Demo page"
207
+ console.log(doc.meta()); // { description: "Demo page", "og:title": "Example OG" }
208
+ console.log(doc.links()); // [{ text: "A", href: "https://example.com/a" }]
209
+ console.log(doc.images()); // [{ alt: "A", src: "https://example.com/a.png" }]
210
+ ```
211
+
212
+ ### Structured Extraction
213
+
214
+ ```ts
215
+ type ExtractSchema = {
216
+ [key: string]: [string, string | ExtractSchema | 'exists' | 'text' | 'count'];
217
+ };
218
+ ```
219
+
220
+ `extract(schema)` evaluates each field relative to the current document or
221
+ element.
222
+
223
+ Schema actions:
224
+
225
+ - `[selector, 'text']` returns text from the first match, or `null`.
226
+ - `[selector, 'exists']` returns a boolean.
227
+ - `[selector, 'count']` returns a number.
228
+ - `[selector, attrName]` returns an attribute from the first match, or
229
+ `undefined`.
230
+ - `[selector, nestedSchema]` returns an array by applying `nestedSchema` to each
231
+ matching element.
232
+
233
+ ```js
234
+ const doc = parse(`
235
+ <article class="post">
236
+ <h2>First</h2>
237
+ <a href="/first">Read</a>
238
+ </article>
239
+ <article class="post">
240
+ <h2>Second</h2>
241
+ </article>
242
+ `);
243
+
244
+ const data = doc.extract({
245
+ postCount: ['article.post', 'count'],
246
+ posts: ['article.post', {
247
+ heading: ['h2', 'text'],
248
+ href: ['a', 'href'],
249
+ hasLink: ['a', 'exists'],
250
+ }],
251
+ });
252
+
253
+ console.log(data);
254
+ // {
255
+ // postCount: 2,
256
+ // posts: [
257
+ // { heading: "First", href: "/first", hasLink: true },
258
+ // { heading: "Second", href: undefined, hasLink: false }
259
+ // ]
260
+ // }
261
+ ```
262
+
263
+ Attribute values returned by `extract` are raw values. Use `doc.url()` or
264
+ `el.urlAttr()` when you need URL resolution.
265
+
266
+ ## `XElement`
267
+
268
+ An `XElement` represents an element or another Gumbo node returned by
269
+ `childNodes`.
270
+
271
+ ### Properties
272
+
273
+ | Property | Type | Description |
274
+ | --- | --- | --- |
275
+ | `childNodes` | `XElement[]` | Direct child nodes. Includes non-element nodes. |
276
+ | `nodeType` | `NodeType` | Gumbo node type. |
277
+ | `parent` | `XElement \| null` | Parent element, or `null` for the document root. |
278
+ | `outerHTML` | `string` | Source HTML for this node. |
279
+ | `innerText` | `string` | Descendant text for text/element/template nodes. |
280
+ | `textContent` | `string` | Same text extraction as `innerText`. |
281
+ | `tagName` | `string \| null` | Normalized tag name for element/template nodes; `null` for non-elements. |
282
+
283
+ ```js
284
+ const doc = parse('<div><!-- note -->Text <b>bold</b></div>');
285
+ const div = doc.firstOrThrow('div');
286
+
287
+ console.log(div.childNodes.map((node) => node.nodeType));
288
+ // ["COMMENT", "TEXT", "ELEMENT"]
289
+ ```
290
+
291
+ ### Query Methods
292
+
293
+ Element query methods have the same behavior as document query methods, but the
294
+ search is scoped to descendants of the element.
295
+
296
+ | Method | Returns |
297
+ | --- | --- |
298
+ | `find(selector)` | `XElement[]` |
299
+ | `first(selector)` | `XElement \| null` |
300
+ | `firstOrThrow(selector)` | `XElement` |
301
+ | `only(selector)` | `XElement \| null` |
302
+ | `onlyOrThrow(selector)` | `XElement` |
303
+
304
+ ```js
305
+ const article = parse(`
306
+ <article>
307
+ <h2>Title</h2>
308
+ <p>Summary</p>
309
+ </article>
310
+ `).firstOrThrow('article');
311
+
312
+ console.log(article.firstOrThrow('h2').innerText); // "Title"
313
+ ```
314
+
315
+ ### Attribute Methods
316
+
317
+ | Method | Returns | Description |
318
+ | --- | --- | --- |
319
+ | `attr(name)` | `string \| undefined` | Attribute value, or `undefined`. |
320
+ | `attr_s(name)` | `string` | Attribute value, or throws `Attribute not found`. |
321
+ | `attrOrThrow(name)` | `string` | Alias for `attr_s`. |
322
+ | `hasClass(name)` | `boolean` | Whether the `class` attribute contains `name`. |
323
+ | `hasAttribute(name)` | `boolean` | Whether the attribute exists. |
324
+ | `urlAttr(name)` | `string \| undefined` | Resolved URL attribute when the document was parsed with `baseUrl`. |
325
+
326
+ ```js
327
+ const doc = parse('<a class="button primary" href="/start">Start</a>', {
328
+ baseUrl: 'https://example.com/',
329
+ });
330
+
331
+ const link = doc.firstOrThrow('a');
332
+ console.log(link.attr('href')); // "/start"
333
+ console.log(link.urlAttr('href')); // "https://example.com/start"
334
+ console.log(link.hasClass('primary')); // true
335
+ ```
336
+
337
+ ### Text Methods
338
+
339
+ | Method | Returns | Description |
340
+ | --- | --- | --- |
341
+ | `text(opts?)` | `string` | Text for this element. |
342
+ | `text(selector, opts?)` | `string \| null` | Text for the first matching descendant. |
343
+ | `textOrThrow(selector)` | `string` | Text for the first matching descendant, or throws `No element found`. |
344
+
345
+ ```js
346
+ const el = parse('<div><span>Hello</span><b>World</b></div>').firstOrThrow('div');
347
+
348
+ console.log(el.text()); // "HelloWorld"
349
+ console.log(el.text({ separator: ' ' })); // "Hello World"
350
+ console.log(el.text('span')); // "Hello"
351
+ ```
352
+
353
+ ### Traversal and Matching
354
+
355
+ | Method | Returns | Description |
356
+ | --- | --- | --- |
357
+ | `prev(selector?)` | `XElement \| null` | Previous element sibling, optionally filtered. |
358
+ | `next(selector?)` | `XElement \| null` | Next element sibling, optionally filtered. |
359
+ | `closest(selector)` | `XElement \| null` | Nearest ancestor matching `selector`. This starts at the parent, not the element itself. |
360
+ | `children(selector?)` | `XElement[]` | Direct element children, optionally filtered. Non-element nodes are excluded. |
361
+ | `siblings(selector?)` | `XElement[]` | Element siblings, optionally filtered. The current element is excluded. |
362
+ | `matches(selector)` | `boolean` | Whether this element matches the full selector. |
363
+ | `is(selector)` | `boolean` | Alias for `matches`. |
364
+
365
+ ```js
366
+ const doc = parse(`
367
+ <section>
368
+ <article class="post featured"><h2>One</h2></article>
369
+ <article class="post"><h2>Two</h2></article>
370
+ </section>
371
+ `);
372
+
373
+ const first = doc.firstOrThrow('article.featured');
374
+ const second = first.next('article.post');
375
+
376
+ console.log(second.prev('.featured') === null); // false
377
+ console.log(first.children('h2').length); // 1
378
+ console.log(first.matches('section article')); // true
379
+ console.log(first.closest('section').tagName); // "section"
380
+ ```
381
+
382
+ ### Table Rows
383
+
384
+ `rows()` extracts rows from a table-like element.
385
+
386
+ - If the first row contains `th` cells, those values become object keys and the
387
+ first row is treated as a header row.
388
+ - With headers, data rows use `td` cells.
389
+ - Without headers, cells are assigned numeric object keys.
390
+
391
+ ```js
392
+ const doc = parse(`
393
+ <table>
394
+ <tr><th>Name</th><th>Age</th></tr>
395
+ <tr><td>Alice</td><td>30</td></tr>
396
+ <tr><td>Bob</td><td>25</td></tr>
397
+ </table>
398
+ `);
399
+
400
+ console.log(doc.firstOrThrow('table').rows());
401
+ // [
402
+ // { Name: "Alice", Age: "30" },
403
+ // { Name: "Bob", Age: "25" }
404
+ // ]
405
+ ```
406
+
407
+ `doc.table(selector?)` is a convenience wrapper around
408
+ `doc.first(selector || 'table')?.rows()`, returning `[]` when no table matches.
409
+
410
+ ### Element Extraction
411
+
412
+ `el.extract(schema)` uses the same structured extraction rules as
413
+ `doc.extract(schema)`, scoped to the element.
414
+
415
+ ```js
416
+ const cards = parse(`
417
+ <div class="card"><h3>A</h3><span>New</span></div>
418
+ <div class="card"><h3>B</h3></div>
419
+ `).find('.card');
420
+
421
+ const data = cards.map((card) => card.extract({
422
+ title: ['h3', 'text'],
423
+ hasBadge: ['span', 'exists'],
424
+ }));
425
+
426
+ console.log(data);
427
+ // [{ title: "A", hasBadge: true }, { title: "B", hasBadge: false }]
428
+ ```
429
+
430
+ ## Error Summary
431
+
432
+ | Operation | Error |
433
+ | --- | --- |
434
+ | `parse()` without a string | `TypeError: html must be a string` |
435
+ | Query method without a string selector | `TypeError: selector must be a string` |
436
+ | Invalid selector syntax | `Error: Bad selector.` |
437
+ | `firstOrThrow()` with no match | `Error: No element found` |
438
+ | `onlyOrThrow()` with zero or multiple matches | `Error: Not a single element` |
439
+ | `attr_s()` / `attrOrThrow()` with missing attribute | `Error: Attribute not found` |
package/README.md CHANGED
@@ -37,6 +37,9 @@ doc.find('.bar').forEach((el) => {
37
37
  });
38
38
  ```
39
39
 
40
+ For the complete API reference, including selector support, traversal helpers,
41
+ structured extraction, table parsing, and URL resolution, see [API.md](API.md).
42
+
40
43
  ## License
41
44
 
42
45
  MIT. Bundles [google/gumbo-parser](https://github.com/google/gumbo-parser)
@@ -45,25 +45,25 @@ posts.forEach((post, index) => {
45
45
  console.log(' has slug:', post.hasAttribute('data-slug'));
46
46
  });
47
47
 
48
- // first_s(selector) is the throwing version of first(selector).
48
+ // firstOrThrow(selector) is the throwing version of first(selector).
49
49
  // Use it when the element is required for the rest of your code.
50
- const content = doc.first_s('#content');
50
+ const content = doc.firstOrThrow('#content');
51
51
  console.log('Main outerHTML starts with:', content.outerHTML.slice(0, 20));
52
52
 
53
53
  // only(selector) returns the match only when exactly one element is found.
54
54
  const featuredPost = doc.only('article.featured');
55
55
  console.log('Featured slug:', featuredPost.attr_s('data-slug'));
56
56
 
57
- // only_s(selector) throws unless exactly one element is found.
57
+ // onlyOrThrow(selector) throws unless exactly one element is found.
58
58
  try {
59
- doc.only_s('article.post');
59
+ doc.onlyOrThrow('article.post');
60
60
  } catch (error) {
61
- console.log('only_s on many posts:', error.message);
61
+ console.log('onlyOrThrow on many posts:', error.message);
62
62
  }
63
63
 
64
64
  // Element-scoped queries search only inside that element.
65
- const firstPost = doc.first_s('article.post');
66
- console.log('CTA href:', firstPost.first_s('a.cta').attr_s('href'));
65
+ const firstPost = doc.firstOrThrow('article.post');
66
+ console.log('CTA href:', firstPost.firstOrThrow('a.cta').attr_s('href'));
67
67
 
68
68
  // next(selector) and prev(selector) walk element siblings.
69
69
  const secondPost = firstPost.next('article.post');
@@ -4,7 +4,7 @@
4
4
  * gumbo-html examples
5
5
  *
6
6
  * Demonstrates all the new features including:
7
- * - Friendly aliases (firstOrThrow, onlyOrThrow, attrOrThrow)
7
+ * - Required-value helpers (firstOrThrow, onlyOrThrow, attrOrThrow)
8
8
  * - Convenience methods (exists, count, text, attr with selector)
9
9
  * - Traversal (closest, children, siblings, matches, is)
10
10
  * - Table extraction (rows, table)
@@ -82,11 +82,11 @@ const HTML = `
82
82
  const doc = html.parse(HTML, { baseUrl: 'https://example.com/blog/' });
83
83
 
84
84
  // ============================================================
85
- // 1. Friendly Required/Optional Aliases
85
+ // 1. Required/Optional Helpers
86
86
  // ============================================================
87
- console.log('=== 1. Friendly Aliases ===');
87
+ console.log('=== 1. Required/Optional Helpers ===');
88
88
 
89
- // firstOrThrow - like first_s but more readable
89
+ // firstOrThrow - get the first match or throw when it is missing
90
90
  const firstArticle = doc.firstOrThrow('article');
91
91
  console.log('firstOrThrow article text:', firstArticle.text('h1'));
92
92
 
package/index.d.ts CHANGED
@@ -22,10 +22,8 @@ export declare type XElement = {
22
22
  attr_s: (name: string) => string;
23
23
  find: (selector: string) => XElement[];
24
24
  first: (selector: string) => XElement | null;
25
- first_s: (selector: string) => XElement;
26
25
  firstOrThrow: (selector: string) => XElement;
27
26
  only: (selector: string) => XElement | null;
28
- only_s: (selector: string) => XElement;
29
27
  onlyOrThrow: (selector: string) => XElement;
30
28
  hasClass: (name: string) => boolean;
31
29
  hasAttribute: (name: string) => boolean;
@@ -58,10 +56,8 @@ export declare type XDocument = {
58
56
 
59
57
  find: (selector: string) => XElement[];
60
58
  first: (selector: string) => XElement | null;
61
- first_s: (selector: string) => XElement;
62
59
  firstOrThrow: (selector: string) => XElement;
63
60
  only: (selector: string) => XElement | null;
64
- only_s: (selector: string) => XElement;
65
61
  onlyOrThrow: (selector: string) => XElement;
66
62
 
67
63
  // New convenience methods
package/lib/wrapper.js CHANGED
@@ -37,10 +37,8 @@ function annotateWithBaseUrl(value, baseUrl) {
37
37
  const ELEMENT_RETURN_METHODS = [
38
38
  'find',
39
39
  'first',
40
- 'first_s',
41
40
  'firstOrThrow',
42
41
  'only',
43
- 'only_s',
44
42
  'onlyOrThrow',
45
43
  'next',
46
44
  'prev',
@@ -52,10 +50,8 @@ const ELEMENT_RETURN_METHODS = [
52
50
  const DOCUMENT_RETURN_METHODS = [
53
51
  'find',
54
52
  'first',
55
- 'first_s',
56
53
  'firstOrThrow',
57
54
  'only',
58
- 'only_s',
59
55
  'onlyOrThrow',
60
56
  'forms',
61
57
  'tables',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gumbo-html",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Node.js CSS Selector Based on Gumbo Parser",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,8 @@
27
27
  "src/gumbo-parser/src/*.h",
28
28
  "src/gumbo-parser/COPYING",
29
29
  "LICENSE",
30
- "README.md"
30
+ "README.md",
31
+ "API.md"
31
32
  ],
32
33
  "scripts": {
33
34
  "install": "node-gyp rebuild",
@@ -80,9 +80,7 @@ void Document::Init(Napi::Env env) {
80
80
  Napi::Function func = DefineClass(env, "Document", {
81
81
  InstanceMethod("find", &Document::Find),
82
82
  InstanceMethod("first", &Document::First),
83
- InstanceMethod("first_s", &Document::FirstSafe),
84
83
  InstanceMethod("only", &Document::Only),
85
- InstanceMethod("only_s", &Document::OnlySafe),
86
84
  InstanceAccessor("documentElement", &Document::GetDocumentElement, nullptr),
87
85
 
88
86
  InstanceMethod("firstOrThrow", &Document::FirstOrThrow),
@@ -292,9 +292,7 @@ void Element::Init(Napi::Env env) {
292
292
  InstanceMethod("attr_s", &Element::AttrSafe),
293
293
  InstanceMethod("find", &Element::Find),
294
294
  InstanceMethod("first", &Element::First),
295
- InstanceMethod("first_s", &Element::FirstSafe),
296
295
  InstanceMethod("only", &Element::Only),
297
- InstanceMethod("only_s", &Element::OnlySafe),
298
296
  InstanceMethod("hasClass", &Element::HasClass),
299
297
  InstanceMethod("hasAttribute", &Element::HasAttribute),
300
298
  InstanceMethod("next", &Element::Next),