rusteerio 0.0.0-stage → 0.1.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/CONTRIBUTING.md +73 -0
- package/Cargo.lock +1051 -0
- package/Cargo.toml +36 -0
- package/LICENSE +21 -0
- package/README.md +252 -2
- package/RELEASING.md +138 -0
- package/THIRD_PARTY_NOTICES.md +7443 -0
- package/build.rs +3 -0
- package/dist/chunk-DKULX6VO.mjs +3185 -0
- package/dist/chunk-DKULX6VO.mjs.map +1 -0
- package/dist/chunk-OHPGEY3E.mjs +32 -0
- package/dist/chunk-OHPGEY3E.mjs.map +1 -0
- package/dist/index.cjs +3230 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.mjs +38 -0
- package/dist/index.mjs.map +1 -0
- package/dist/slim.cjs +2441 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.mts +11 -0
- package/dist/slim.d.ts +11 -0
- package/dist/slim.mjs +19 -0
- package/dist/slim.mjs.map +1 -0
- package/dist/types-BP6TpxE5.d.mts +532 -0
- package/dist/types-BP6TpxE5.d.ts +532 -0
- package/dist/utils.cjs +61 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.mts +13 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.mjs +17 -0
- package/dist/utils.mjs.map +1 -0
- package/native/src/arena.rs +1781 -0
- package/native/src/attributes.rs +923 -0
- package/native/src/codec.rs +172 -0
- package/native/src/diagnostics.rs +1249 -0
- package/native/src/extraction.rs +226 -0
- package/native/src/gc.rs +166 -0
- package/native/src/hook_sink.rs +727 -0
- package/native/src/hook_stream.rs +513 -0
- package/native/src/lib.rs +2095 -0
- package/native/src/location.rs +392 -0
- package/native/src/runtime.rs +189 -0
- package/native/src/selector.rs +1287 -0
- package/native/src/sink.rs +261 -0
- package/native/src/stream.rs +334 -0
- package/native/src/tokenizer.rs +355 -0
- package/native/src/tracker.rs +232 -0
- package/native/vendor/README.md +25 -0
- package/native/vendor/html5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/html5ever-0.35.0/Cargo.lock +828 -0
- package/native/vendor/html5ever-0.35.0/Cargo.toml +85 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/html5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/html5ever-0.35.0/README.md +60 -0
- package/native/vendor/html5ever-0.35.0/benches/html5ever.rs +86 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum-zh.html +19 -0
- package/native/vendor/html5ever-0.35.0/data/bench/lipsum.html +40 -0
- package/native/vendor/html5ever-0.35.0/data/bench/medium-fragment.html +24 -0
- package/native/vendor/html5ever-0.35.0/data/bench/small-fragment.html +7 -0
- package/native/vendor/html5ever-0.35.0/data/bench/strong.html +1 -0
- package/native/vendor/html5ever-0.35.0/data/bench/tiny-fragment.html +1 -0
- package/native/vendor/html5ever-0.35.0/examples/arena.rs +356 -0
- package/native/vendor/html5ever-0.35.0/examples/capi/tokenize.c +74 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tokenize.rs +47 -0
- package/native/vendor/html5ever-0.35.0/examples/noop-tree-builder.rs +132 -0
- package/native/vendor/html5ever-0.35.0/examples/print-tree-actions.rs +185 -0
- package/native/vendor/html5ever-0.35.0/examples/tokenize.rs +111 -0
- package/native/vendor/html5ever-0.35.0/src/driver.rs +148 -0
- package/native/vendor/html5ever-0.35.0/src/lib.rs +30 -0
- package/native/vendor/html5ever-0.35.0/src/macros.rs +34 -0
- package/native/vendor/html5ever-0.35.0/src/serialize/mod.rs +255 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/char_ref/mod.rs +451 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/interface.rs +99 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/mod.rs +2305 -0
- package/native/vendor/html5ever-0.35.0/src/tokenizer/states.rs +97 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/data.rs +170 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/mod.rs +1848 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/rules.rs +1494 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/tag_sets.rs +114 -0
- package/native/vendor/html5ever-0.35.0/src/tree_builder/types.rs +91 -0
- package/native/vendor/html5ever-0.35.0/src/util/str.rs +52 -0
- package/native/vendor/html5gum-0.8.4/Cargo.lock +1006 -0
- package/native/vendor/html5gum-0.8.4/Cargo.toml +109 -0
- package/native/vendor/html5gum-0.8.4/LICENSE +19 -0
- package/native/vendor/html5gum-0.8.4/README.md +110 -0
- package/native/vendor/html5gum-0.8.4/benches/files/0050.html +2131 -0
- package/native/vendor/html5gum-0.8.4/benches/files/6D30.html +1004 -0
- package/native/vendor/html5gum-0.8.4/benches/files.rs +32 -0
- package/native/vendor/html5gum-0.8.4/benches/patterns.rs +31 -0
- package/native/vendor/html5gum-0.8.4/src/arrayvec.rs +29 -0
- package/native/vendor/html5gum-0.8.4/src/char_validator.rs +134 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/callback.rs +676 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/default.rs +207 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/emitter.rs +344 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/html5ever.rs +306 -0
- package/native/vendor/html5gum-0.8.4/src/emitters/mod.rs +25 -0
- package/native/vendor/html5gum-0.8.4/src/entities.rs +3342 -0
- package/native/vendor/html5gum-0.8.4/src/error.rs +96 -0
- package/native/vendor/html5gum-0.8.4/src/htmlstring.rs +130 -0
- package/native/vendor/html5gum-0.8.4/src/lib.rs +66 -0
- package/native/vendor/html5gum-0.8.4/src/machine.rs +2136 -0
- package/native/vendor/html5gum-0.8.4/src/machine_helper.rs +277 -0
- package/native/vendor/html5gum-0.8.4/src/read_helper.rs +244 -0
- package/native/vendor/html5gum-0.8.4/src/reader.rs +440 -0
- package/native/vendor/html5gum-0.8.4/src/span.rs +98 -0
- package/native/vendor/html5gum-0.8.4/src/state.rs +16 -0
- package/native/vendor/html5gum-0.8.4/src/testutils.rs +71 -0
- package/native/vendor/html5gum-0.8.4/src/tokenizer.rs +189 -0
- package/native/vendor/html5gum-0.8.4/src/utils.rs +88 -0
- package/native/vendor/markup5ever-0.35.0/.cargo_vcs_info.json +6 -0
- package/native/vendor/markup5ever-0.35.0/Cargo.toml +45 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-APACHE +201 -0
- package/native/vendor/markup5ever-0.35.0/LICENSE-MIT +25 -0
- package/native/vendor/markup5ever-0.35.0/interface/mod.rs +422 -0
- package/native/vendor/markup5ever-0.35.0/interface/tree_builder.rs +280 -0
- package/native/vendor/markup5ever-0.35.0/lib.rs +59 -0
- package/native/vendor/markup5ever-0.35.0/serialize.rs +77 -0
- package/native/vendor/markup5ever-0.35.0/util/buffer_queue.rs +333 -0
- package/native/vendor/markup5ever-0.35.0/util/smallcharset.rs +88 -0
- package/package.json +137 -4
- package/rusteerio.darwin-arm64.node +0 -0
- package/rusteerio.darwin-x64.node +0 -0
- package/rusteerio.linux-arm64-gnu.node +0 -0
- package/rusteerio.linux-arm64-musl.node +0 -0
- package/rusteerio.linux-x64-gnu.node +0 -0
- package/rusteerio.linux-x64-musl.node +0 -0
- package/rusteerio.win32-arm64-msvc.node +0 -0
- package/rusteerio.win32-x64-msvc.node +0 -0
- package/scripts/check-native.mjs +15 -0
- package/scripts/check-release.mjs +235 -0
- package/scripts/fix-declarations.mjs +27 -0
- package/scripts/generate-notices.mjs +176 -0
- package/scripts/notices/README.md +13 -0
- package/scripts/notices/cheerio-MIT.txt +21 -0
- package/scripts/notices/html5ever-AUTHORS.txt +20 -0
- package/scripts/notices/html5ever-COPYRIGHT.txt +8 -0
- package/scripts/notices/html5ever-LICENSE-APACHE.txt +201 -0
- package/scripts/notices/html5ever-LICENSE-MIT.txt +25 -0
- package/scripts/notices/napi-rs-MIT.txt +43 -0
- package/scripts/notices/neon-MIT.txt +19 -0
- package/scripts/notices/runtime-01c266bced4a434da005.txt +3 -0
- package/scripts/notices/runtime-0a54e647fe54104658b5.txt +14 -0
- package/scripts/notices/runtime-0b74dfa0bcee5c420c6b.txt +25 -0
- package/scripts/notices/runtime-0d542e0c8804e39aa7f3.txt +177 -0
- package/scripts/notices/runtime-0f96a83840e146e43c0e.txt +21 -0
- package/scripts/notices/runtime-123a331b5dbf04c30097.txt +25 -0
- package/scripts/notices/runtime-172020dbfd5b53a226df.txt +34 -0
- package/scripts/notices/runtime-209fbbe0ad52d9235e37.txt +26 -0
- package/scripts/notices/runtime-23f18e03dc49df91622f.txt +23 -0
- package/scripts/notices/runtime-268872b9816f90fd8e85.txt +220 -0
- package/scripts/notices/runtime-35242e7a83f69875e6ed.txt +176 -0
- package/scripts/notices/runtime-378f5840b258e2779c39.txt +25 -0
- package/scripts/notices/runtime-4108245a1f2df9d4e94d.txt +25 -0
- package/scripts/notices/runtime-4455bf75a91154108304.txt +21 -0
- package/scripts/notices/runtime-553fffcd9b1cb158bc3e.txt +13 -0
- package/scripts/notices/runtime-62c7a1e35f56406896d7.txt +176 -0
- package/scripts/notices/runtime-6485b8ed310d3f0340bf.txt +25 -0
- package/scripts/notices/runtime-69fef7b0f322a6555415.txt +201 -0
- package/scripts/notices/runtime-6df43f6f4b5d4587f3d8.txt +187 -0
- package/scripts/notices/runtime-799e9ca9d179295ef372.txt +24 -0
- package/scripts/notices/runtime-7b63ecd5f1902af1b637.txt +25 -0
- package/scripts/notices/runtime-7bc7d5de2a0b793d4e1a.txt +21 -0
- package/scripts/notices/runtime-7e12e5df4bae12cb2158.txt +24 -0
- package/scripts/notices/runtime-861399f8c21c042b1105.txt +12 -0
- package/scripts/notices/runtime-8ada45cd9f843acf64e4.txt +201 -0
- package/scripts/notices/runtime-90eb64f0279b0d9432ac.txt +12 -0
- package/scripts/notices/runtime-a60eea817514531668d7.txt +201 -0
- package/scripts/notices/runtime-a7c936ff1ed8fa340172.txt +25 -0
- package/scripts/notices/runtime-ab6eec6caf0fa5775e41.txt +275 -0
- package/scripts/notices/runtime-b1181a40b2a7b25cf66f.txt +19 -0
- package/scripts/notices/runtime-b71bd43a069ca0641a9e.txt +25 -0
- package/scripts/notices/runtime-c16f8dcf1a368b83be78.txt +201 -0
- package/scripts/notices/runtime-c2cfccb812fe482101a8.txt +21 -0
- package/scripts/notices/runtime-e99d88d232bf57d70f0f.txt +25 -0
- package/scripts/notices/runtime-f32fb3b417a194167cfa.txt +9 -0
- package/scripts/notices/runtime-f5062c9a188d81dfe66b.txt +39 -0
- package/scripts/notices/runtime-f9070a832833fa7488fd.txt +4 -0
- package/scripts/notices/runtime-fab3dd6bdab226f1c086.txt +373 -0
- package/scripts/notices/runtime-fba3318d0c600380177e.txt +62 -0
- package/scripts/notices/runtime-ff8f68cb076caf8cefe7.txt +25 -0
- package/scripts/notices/runtime-ff92bed461f50338dd70.txt +74 -0
- package/scripts/notices/runtime.json +1632 -0
- package/scripts/notices/sources.json +555 -0
- package/scripts/notices/xray-MIT.txt +21 -0
- package/src/attributes.ts +271 -0
- package/src/codec.ts +74 -0
- package/src/dom.ts +87 -0
- package/src/extract-plan.ts +97 -0
- package/src/extract.ts +130 -0
- package/src/hooks.ts +220 -0
- package/src/index.ts +223 -0
- package/src/io.ts +491 -0
- package/src/native.ts +135 -0
- package/src/nodes.ts +290 -0
- package/src/parse.ts +53 -0
- package/src/parser-types.ts +157 -0
- package/src/router.ts +327 -0
- package/src/selection.ts +566 -0
- package/src/slim.ts +12 -0
- package/src/transport.ts +104 -0
- package/src/types.ts +186 -0
- package/src/utils.ts +29 -0
- package/src/workers.ts +21 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +16 -0
package/src/router.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { requireNative, type NativeDocument, type NativeNodeRecord } from './native.js';
|
|
2
|
+
import { hasParserHooks, parseNativeDocument } from './hooks.js';
|
|
3
|
+
import type { CheerioOptions } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface ManagedNativeDocument extends NativeDocument {
|
|
6
|
+
retain?(ids: number[]): void;
|
|
7
|
+
release?(ids: number[]): void;
|
|
8
|
+
collectGarbage?(): { live: number; slots: number; retained: number; reclaimed: number };
|
|
9
|
+
}
|
|
10
|
+
interface PinRecord { global: number; document: WeakRef<ManagedNativeDocument>; local: number }
|
|
11
|
+
interface Handle { global: number; document: ManagedNativeDocument; local: number; data?: object; pin: PinRecord }
|
|
12
|
+
interface HandleEntry { reference: WeakRef<Handle>; pin: PinRecord }
|
|
13
|
+
interface Registry {
|
|
14
|
+
next: number;
|
|
15
|
+
space: object;
|
|
16
|
+
handles: Map<number, WeakRef<Handle>>;
|
|
17
|
+
byDocument: WeakMap<NativeDocument, Map<number, HandleEntry>>;
|
|
18
|
+
finalizer: FinalizationRegistry<PinRecord>;
|
|
19
|
+
snapshots: WeakMap<NativeDocument, Map<number, NativeNodeRecord | null>>;
|
|
20
|
+
}
|
|
21
|
+
const registryKey = Symbol.for('rusteerio.native-handles.v2');
|
|
22
|
+
const globals = globalThis as typeof globalThis & { [key: symbol]: Registry | undefined };
|
|
23
|
+
const registry: Registry = globals[registryKey] ??= (() => {
|
|
24
|
+
const handles = new Map<number, WeakRef<Handle>>();
|
|
25
|
+
const byDocument = new WeakMap<NativeDocument, Map<number, HandleEntry>>();
|
|
26
|
+
const snapshots = new WeakMap<NativeDocument, Map<number, NativeNodeRecord | null>>();
|
|
27
|
+
const pending: PinRecord[] = [];
|
|
28
|
+
let scheduled = false;
|
|
29
|
+
const flush = () => {
|
|
30
|
+
scheduled = false;
|
|
31
|
+
const batches = new Map<ManagedNativeDocument, number[]>();
|
|
32
|
+
for (const pin of pending.splice(0)) {
|
|
33
|
+
const document = pin.document.deref();
|
|
34
|
+
if (!document) continue;
|
|
35
|
+
const entries = byDocument.get(document);
|
|
36
|
+
if (entries?.get(pin.local)?.pin === pin) entries.delete(pin.local);
|
|
37
|
+
const ids = batches.get(document) ?? [];
|
|
38
|
+
ids.push(pin.local); batches.set(document, ids);
|
|
39
|
+
}
|
|
40
|
+
for (const [document, ids] of batches) {
|
|
41
|
+
snapshots.delete(document);
|
|
42
|
+
document.release?.(ids);
|
|
43
|
+
document.collectGarbage?.();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return { next: 1, space: {}, handles, byDocument, snapshots, finalizer: new FinalizationRegistry<PinRecord>((pin) => {
|
|
47
|
+
if (!handles.get(pin.global)?.deref()) handles.delete(pin.global);
|
|
48
|
+
pending.push(pin);
|
|
49
|
+
if (!scheduled) { scheduled = true; queueMicrotask(flush); }
|
|
50
|
+
}) };
|
|
51
|
+
})();
|
|
52
|
+
|
|
53
|
+
function rawOperation<T = any>(document: NativeDocument, command: string, args: any): T {
|
|
54
|
+
const result = document.op(command, JSON.stringify(args));
|
|
55
|
+
return result === '' || result === undefined ? undefined as T : JSON.parse(result);
|
|
56
|
+
}
|
|
57
|
+
function localMap(document: NativeDocument): Map<number, HandleEntry> {
|
|
58
|
+
let map = registry.byDocument.get(document);
|
|
59
|
+
if (!map) registry.byDocument.set(document, map = new Map());
|
|
60
|
+
return map;
|
|
61
|
+
}
|
|
62
|
+
function handleFor(document: ManagedNativeDocument, local: number): Handle {
|
|
63
|
+
const map = localMap(document);
|
|
64
|
+
let handle = map.get(local)?.reference.deref();
|
|
65
|
+
if (!handle || handle.document !== document || handle.local !== local) {
|
|
66
|
+
const pin: PinRecord = { global: registry.next++, document: new WeakRef(document), local };
|
|
67
|
+
handle = { global: pin.global, document, local, pin };
|
|
68
|
+
document.retain?.([local]);
|
|
69
|
+
const reference = new WeakRef(handle);
|
|
70
|
+
map.set(local, { reference, pin }); registry.handles.set(handle.global, reference);
|
|
71
|
+
registry.finalizer.register(handle, pin);
|
|
72
|
+
}
|
|
73
|
+
return handle;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Keep numeric native IDs alive for precisely the lifetime of their JS owner. */
|
|
77
|
+
export function retainNodes(document: NativeDocument, ids: number[]): object {
|
|
78
|
+
return (document as NativeDocument & { retainNodes?(ids: number[]): object }).retainNodes?.(ids) ?? {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Maps stable JS handles to their current native arena. Native adoption moves
|
|
83
|
+
* the DOM; this bridge only translates IDs and routes commands to its owner.
|
|
84
|
+
* Global tables contain weak references. Live APIs and node handles retain the
|
|
85
|
+
* ownership records they use, including records moved to another arena.
|
|
86
|
+
*/
|
|
87
|
+
export class RoutedDocument implements NativeDocument {
|
|
88
|
+
readonly nodeSpace = registry.space;
|
|
89
|
+
readonly options: CheerioOptions;
|
|
90
|
+
private readonly anchor: Handle;
|
|
91
|
+
get raw(): ManagedNativeDocument { return this.anchor.document; }
|
|
92
|
+
|
|
93
|
+
constructor(document: NativeDocument, options: CheerioOptions = {}, anchor?: Handle) {
|
|
94
|
+
this.options = options;
|
|
95
|
+
this.anchor = anchor ?? handleFor(document, document.rootId());
|
|
96
|
+
}
|
|
97
|
+
private keep(handle: Handle): number { return handle.global; }
|
|
98
|
+
private lookup(id: number): Handle {
|
|
99
|
+
const handle = registry.handles.get(id)?.deref();
|
|
100
|
+
if (!handle) throw new RangeError(`Unknown native node handle ${id}`);
|
|
101
|
+
return handle;
|
|
102
|
+
}
|
|
103
|
+
private global(document: NativeDocument, local: number): number { return this.keep(handleFor(document, local)); }
|
|
104
|
+
private globals(document: NativeDocument, ids: number[]): number[] { return ids.map((id) => this.global(document, id)); }
|
|
105
|
+
retainNode(id: number): void { this.lookup(id); }
|
|
106
|
+
retainNodes(ids: number[]): object { return ids.filter((id) => id !== undefined).map((id) => this.lookup(id)); }
|
|
107
|
+
nodeDocument(id: number): NativeDocument { return new RoutedDocument(this.raw, this.options, this.lookup(id)); }
|
|
108
|
+
collectGarbage(): { live: number; slots: number; retained: number; reclaimed: number } | undefined { registry.snapshots.delete(this.raw); return this.raw.collectGarbage?.(); }
|
|
109
|
+
retainNodeData(id: number, data: object): void { this.lookup(id).data = data; }
|
|
110
|
+
nodeData(id: number): object | undefined { return this.lookup(id).data; }
|
|
111
|
+
nodeDataOperation(id: number): (command: string, args: any) => any {
|
|
112
|
+
const handle = this.lookup(id);
|
|
113
|
+
const options = this.options;
|
|
114
|
+
return (command, args) => rawOperation(handle.document, command, { ...args, ids: [handle.local], options });
|
|
115
|
+
}
|
|
116
|
+
localNodeId(id: number): number { return this.global(this.raw, id); }
|
|
117
|
+
rootId(): number { return this.global(this.raw, this.raw.rootId()); }
|
|
118
|
+
|
|
119
|
+
private record(document: NativeDocument, record: NativeNodeRecord | null): NativeNodeRecord | null {
|
|
120
|
+
if (!record) return null;
|
|
121
|
+
const view = { ...record, id: this.global(document, record.id) };
|
|
122
|
+
// Translate links only when read. A text callback must not allocate/pin
|
|
123
|
+
// handles for every unobserved child and sibling in its ancestor records.
|
|
124
|
+
Object.defineProperty(view, 'children', { enumerable: true, get: () => this.globals(document, record.children ?? []) });
|
|
125
|
+
for (const key of ['parent', 'prev', 'next']) Object.defineProperty(view, key, { enumerable: true, get: () => record[key] == null ? null : this.global(document, record[key]) });
|
|
126
|
+
return view;
|
|
127
|
+
}
|
|
128
|
+
private groups(ids: number[]): { document: NativeDocument; ids: number[]; global: number[] }[] {
|
|
129
|
+
const groups: { document: NativeDocument; ids: number[]; global: number[] }[] = [];
|
|
130
|
+
for (const id of ids) {
|
|
131
|
+
const handle = this.lookup(id);
|
|
132
|
+
const last = groups[groups.length - 1];
|
|
133
|
+
if (last?.document === handle.document) { last.ids.push(handle.local); last.global.push(id); }
|
|
134
|
+
else groups.push({ document: handle.document, ids: [handle.local], global: [id] });
|
|
135
|
+
}
|
|
136
|
+
return groups;
|
|
137
|
+
}
|
|
138
|
+
private call<T = any>(document: NativeDocument, command: string, args: any): T {
|
|
139
|
+
if (command === 'extractPlan' && JSON.stringify(args.plan).includes('innerHTML')) registry.snapshots.delete(document);
|
|
140
|
+
if (Object.hasOwn(args, 'value') || ['create', 'createNode', 'setNode', 'import', 'insert', 'clone', 'remove', 'removeAttr', 'removeData', 'addClass', 'removeClass', 'toggleClass'].includes(command)) registry.snapshots.delete(document);
|
|
141
|
+
return rawOperation(document, command, { ...args, options: args.options ?? this.options });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private move(target: NativeDocument, ids: number[], copy = false): number[] {
|
|
145
|
+
if (!copy) {
|
|
146
|
+
const groups = new Map<NativeDocument, Handle[]>();
|
|
147
|
+
for (const id of ids) { const handle = this.lookup(id); const group = groups.get(handle.document) ?? []; group.push(handle); groups.set(handle.document, group); }
|
|
148
|
+
for (const [source, handles] of groups) {
|
|
149
|
+
if (source === target) continue;
|
|
150
|
+
if (!target.adopt) throw new Error('The native addon does not support cross-document adoption. Rebuild the Rust addon.');
|
|
151
|
+
registry.snapshots.delete(source); registry.snapshots.delete(target);
|
|
152
|
+
const adopted: { mapping: [number, number][] } = JSON.parse(target.adopt(source, handles.map((handle) => handle.local), true));
|
|
153
|
+
for (const [oldLocal, newLocal] of adopted.mapping) {
|
|
154
|
+
const entry = localMap(source).get(oldLocal);
|
|
155
|
+
if (!entry) continue;
|
|
156
|
+
const handle = entry.reference.deref();
|
|
157
|
+
if (handle && (handle.document !== source || handle.local !== oldLocal)) continue;
|
|
158
|
+
localMap(source).delete(oldLocal);
|
|
159
|
+
entry.pin.document = new WeakRef(target); entry.pin.local = newLocal;
|
|
160
|
+
if (handle) { handle.document = target; handle.local = newLocal; }
|
|
161
|
+
localMap(target).set(newLocal, entry);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return ids.map((id) => this.lookup(id).local);
|
|
165
|
+
}
|
|
166
|
+
const result: number[] = [];
|
|
167
|
+
for (const group of this.groups(ids)) {
|
|
168
|
+
if (copy && group.document === target) {
|
|
169
|
+
result.push(...this.call<number[]>(target, 'clone', { ids: group.ids, deep: true }));
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (!target.adopt) throw new Error('The native addon does not support cross-document adoption. Rebuild the Rust addon.');
|
|
173
|
+
registry.snapshots.delete(target);
|
|
174
|
+
const adopted: { roots: number[]; mapping: [number, number][] } = JSON.parse(target.adopt(group.document, group.ids, !copy));
|
|
175
|
+
result.push(...adopted.roots);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private scratch(command: string, args: any, ids: number[]): any {
|
|
181
|
+
const documents = [...new Set(ids.map((id) => this.lookup(id).document))];
|
|
182
|
+
const scratch = new (requireNative().NativeDocument)('', false, JSON.stringify(args.options ?? this.options));
|
|
183
|
+
const toScratch = new Map<number, number>();
|
|
184
|
+
const fromScratch = new Map<number, number>();
|
|
185
|
+
for (const document of documents) {
|
|
186
|
+
const records: NativeNodeRecord[] = JSON.parse(document.exportTree());
|
|
187
|
+
const roots = records.filter((record) => record.parent == null).map((record) => record.id);
|
|
188
|
+
if (!scratch.adopt) throw new Error('The native addon does not support native document copies.');
|
|
189
|
+
const copied: { mapping: [number, number][] } = JSON.parse(scratch.adopt(document, roots, false));
|
|
190
|
+
for (const [local, copy] of copied.mapping) {
|
|
191
|
+
const global = this.global(document, local);
|
|
192
|
+
toScratch.set(global, copy); fromScratch.set(copy, global);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const translated = { ...args };
|
|
196
|
+
for (const key of ['ids', 'contexts']) if (Array.isArray(args[key])) translated[key] = args[key].map((id: number) => toScratch.get(id)).filter((id: number | undefined) => id !== undefined);
|
|
197
|
+
if (args.pseudoMatches) translated.pseudoMatches = Object.fromEntries(Object.entries(args.pseudoMatches).map(([key, values]) => [key, (values as number[]).map((id) => toScratch.get(id)).filter((id) => id !== undefined)]));
|
|
198
|
+
const result = this.call(scratch, command, translated);
|
|
199
|
+
return ['select', 'matches', 'uniqueSort'].includes(command) ? (result as number[]).map((id) => fromScratch.get(id)!).filter((id) => id !== undefined) : result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
select(selector: string, contexts?: number[]): number[] {
|
|
203
|
+
const ids = contexts ?? [this.rootId()];
|
|
204
|
+
if (!ids.length) return this.call(this.raw, 'select', { selector, contexts: [] });
|
|
205
|
+
const groups = this.groups(ids);
|
|
206
|
+
if (new Set(groups.map((group) => group.document)).size > 1) return this.scratch('select', { selector, contexts: ids }, ids);
|
|
207
|
+
const group = groups[0];
|
|
208
|
+
const found = group.document === this.raw ? group.document.select(selector, group.ids) : this.call<number[]>(group.document, 'select', { selector, contexts: group.ids });
|
|
209
|
+
return this.globals(group.document, found);
|
|
210
|
+
}
|
|
211
|
+
text(ids: number[]): string { return this.groups(ids).map((group) => group.document.text(group.ids)).join(''); }
|
|
212
|
+
innerHtml(id: number): string { const handle = this.lookup(id); registry.snapshots.delete(handle.document); return handle.document === this.raw ? handle.document.innerHtml(handle.local) : this.call(handle.document, 'html', { ids: [handle.local] }); }
|
|
213
|
+
outerHtml(ids: number[]): string { return this.groups(ids).map((group) => group.document === this.raw ? group.document.outerHtml(group.ids) : this.call<string>(group.document, 'render', { ids: group.ids })).join(''); }
|
|
214
|
+
attr(id: number, name: string): string | undefined { const handle = this.lookup(id); return this.call(handle.document, 'attr', { ids: [handle.local], name }) ?? undefined; }
|
|
215
|
+
tagName(id: number): string | undefined { const handle = this.lookup(id); return rawOperation<NativeNodeRecord | null>(handle.document, 'node', { id: handle.local })?.name; }
|
|
216
|
+
exportTree(): string { return JSON.stringify((JSON.parse(this.raw.exportTree()) as NativeNodeRecord[]).map((record) => this.record(this.raw, record))); }
|
|
217
|
+
create(html: string, contextId?: number): number[] { return JSON.parse(this.op('create', JSON.stringify({ html, contextId }))); }
|
|
218
|
+
|
|
219
|
+
op(command: string, json: string): string { return JSON.stringify(this.operate(command, JSON.parse(json)) ?? null); }
|
|
220
|
+
|
|
221
|
+
operate(command: string, args: any): any {
|
|
222
|
+
const options = args.options ?? this.options;
|
|
223
|
+
let result: any;
|
|
224
|
+
if (command === 'node') {
|
|
225
|
+
const handle = this.lookup(args.id);
|
|
226
|
+
let snapshots = registry.snapshots.get(handle.document);
|
|
227
|
+
if (!snapshots) registry.snapshots.set(handle.document, snapshots = new Map());
|
|
228
|
+
if (!snapshots.has(handle.local)) snapshots.set(handle.local, rawOperation(handle.document, 'node', { id: handle.local }));
|
|
229
|
+
result = this.record(handle.document, snapshots.get(handle.local)!);
|
|
230
|
+
} else if (command === 'createNode') result = this.global(this.raw, this.call<number>(this.raw, command, args));
|
|
231
|
+
else if (command === 'create') {
|
|
232
|
+
const context = args.contextId == null ? undefined : this.lookup(args.contextId);
|
|
233
|
+
const document = context?.document ?? this.raw;
|
|
234
|
+
if (hasParserHooks(options)) {
|
|
235
|
+
const contextRecord = context ? rawOperation<NativeNodeRecord>(context.document, 'node', { id: context.local }) : undefined;
|
|
236
|
+
const parsed = parseNativeDocument(args.html, { ...options, ...(contextRecord ? { __contextName: contextRecord.name, __contextNamespace: contextRecord.namespace } : {}) }, false);
|
|
237
|
+
const children = rawOperation<NativeNodeRecord>(parsed, 'node', { id: parsed.rootId() }).children;
|
|
238
|
+
this.move(document, children);
|
|
239
|
+
result = children;
|
|
240
|
+
} else result = this.globals(document, this.call(document, command, { ...args, contextId: context?.local }));
|
|
241
|
+
} else if (command === 'import') result = this.globals(this.raw, this.call(this.raw, command, args));
|
|
242
|
+
else if (command === 'setNode') {
|
|
243
|
+
let handle = this.lookup(args.id);
|
|
244
|
+
let value = args.value;
|
|
245
|
+
if (args.field === 'children') value = this.move(handle.document, value ?? []);
|
|
246
|
+
else if (['parent', 'parentNode', 'prev', 'previousSibling', 'next', 'nextSibling'].includes(args.field) && value != null) {
|
|
247
|
+
const pointer = this.lookup(value);
|
|
248
|
+
if (pointer.document !== handle.document) { this.move(pointer.document, [args.id]); handle = this.lookup(args.id); }
|
|
249
|
+
value = this.lookup(args.value).local;
|
|
250
|
+
}
|
|
251
|
+
result = this.call(handle.document, command, { ...args, id: handle.local, value });
|
|
252
|
+
} else if (command === 'insert') {
|
|
253
|
+
const targets: number[] = args.targetIds ?? [];
|
|
254
|
+
const nodes: number[] = args.nodeIds ?? [];
|
|
255
|
+
result = [];
|
|
256
|
+
for (let index = 0; index < targets.length; index++) {
|
|
257
|
+
const target = this.lookup(targets[index]);
|
|
258
|
+
const local = this.move(target.document, nodes, index + 1 < targets.length);
|
|
259
|
+
const inserted = this.call<number[]>(target.document, command, { ...args, targetIds: [target.local], nodeIds: local });
|
|
260
|
+
result.push(...this.globals(target.document, inserted));
|
|
261
|
+
}
|
|
262
|
+
} else if (command === 'html' && Object.hasOwn(args, 'value') && hasParserHooks(options)) {
|
|
263
|
+
result = null;
|
|
264
|
+
for (const id of args.ids ?? []) {
|
|
265
|
+
const target = this.lookup(id);
|
|
266
|
+
const record = rawOperation<NativeNodeRecord>(target.document, 'node', { id: target.local });
|
|
267
|
+
if (!['root', 'tag', 'script', 'style', 'cdata'].includes(record.type)) continue;
|
|
268
|
+
const parsed = parseNativeDocument(String(args.value), { ...options, __contextName: record.name, __contextNamespace: record.namespace }, false);
|
|
269
|
+
const children = rawOperation<NativeNodeRecord>(parsed, 'node', { id: parsed.rootId() }).children;
|
|
270
|
+
const local = this.move(target.document, children);
|
|
271
|
+
result = this.call(target.document, 'setNode', { id: target.local, field: 'children', value: local, options });
|
|
272
|
+
}
|
|
273
|
+
} else if (command === 'contains') {
|
|
274
|
+
const parent = this.lookup(args.container ?? args.parent);
|
|
275
|
+
const child = this.lookup(args.contained ?? args.child);
|
|
276
|
+
result = parent.document === child.document && this.call(parent.document, command, { container: parent.local, contained: child.local });
|
|
277
|
+
} else if (command === 'prop' && Object.hasOwn(args, 'value') && ['children', 'childNodes', 'parent', 'parentNode', 'prev', 'previousSibling', 'next', 'nextSibling'].includes(args.name)) {
|
|
278
|
+
result = null;
|
|
279
|
+
for (const id of args.ids ?? []) {
|
|
280
|
+
let handle = this.lookup(id);
|
|
281
|
+
let value = args.value;
|
|
282
|
+
if (args.name === 'children' || args.name === 'childNodes') value = this.move(handle.document, value ?? []);
|
|
283
|
+
else if (value != null) {
|
|
284
|
+
const pointer = this.lookup(value);
|
|
285
|
+
if (pointer.document !== handle.document) { this.move(pointer.document, [id]); handle = this.lookup(id); }
|
|
286
|
+
value = this.lookup(args.value).local;
|
|
287
|
+
}
|
|
288
|
+
result = this.call(handle.document, command, { ...args, ids: [handle.local], value });
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
const ids: number[] = command === 'select' ? args.contexts ?? [this.rootId()] : args.ids ?? [];
|
|
292
|
+
const groups = this.groups(ids);
|
|
293
|
+
const mixed = new Set(groups.map((group) => group.document)).size > 1;
|
|
294
|
+
if (mixed && ['select', 'matches', 'uniqueSort', 'serializeArray', 'serialize', 'extractPlan'].includes(command)) result = this.scratch(command, { ...args, options }, ids);
|
|
295
|
+
else if (command === 'extractPlan') {
|
|
296
|
+
const group = groups[0];
|
|
297
|
+
result = this.call(group?.document ?? this.raw, command, { ...args, options, ids: group?.ids ?? [] });
|
|
298
|
+
}
|
|
299
|
+
else if (['select', 'matches', 'uniqueSort', 'traverse', 'clone'].includes(command)) {
|
|
300
|
+
result = [];
|
|
301
|
+
if (!groups.length && (command === 'select' || command === 'matches')) result = this.call(this.raw, command, { ...args, options, ids: [], ...(command === 'select' ? { contexts: [] } : {}) });
|
|
302
|
+
for (const group of groups) {
|
|
303
|
+
const values = this.call<number[]>(group.document, command, { ...args, options, ids: group.ids, ...(command === 'select' ? { contexts: group.ids } : {}), ...(args.pseudoMatches ? { pseudoMatches: Object.fromEntries(Object.entries(args.pseudoMatches).map(([key, values]) => [key, (values as number[]).map((id) => this.lookup(id)).filter((handle) => handle.document === group.document).map((handle) => handle.local)])) } : {}) });
|
|
304
|
+
result.push(...this.globals(group.document, values));
|
|
305
|
+
}
|
|
306
|
+
} else if (['render', 'outerHtml', 'text'].includes(command) && !Object.hasOwn(args, 'value')) result = groups.map((group) => this.call<string>(group.document, command, { ...args, options, ids: group.ids })).join('');
|
|
307
|
+
else if (command === 'hasClass') result = groups.some((group) => this.call<boolean>(group.document, command, { ...args, options, ids: group.ids }));
|
|
308
|
+
else if (['serializeArray', 'serialize'].includes(command)) result = groups.length ? this.call(groups[0].document, command, { ...args, options, ids: groups[0].ids }) : command === 'serializeArray' ? [] : '';
|
|
309
|
+
else {
|
|
310
|
+
if (command === 'html') for (const group of groups) registry.snapshots.delete(group.document);
|
|
311
|
+
const writes = Object.hasOwn(args, 'value') || ['remove', 'removeAttr', 'removeData', 'addClass', 'removeClass', 'toggleClass'].includes(command);
|
|
312
|
+
const active = writes ? groups : groups.slice(0, 1);
|
|
313
|
+
result = null;
|
|
314
|
+
for (const group of active) result = this.call(group.document, command, { ...args, options, ids: group.ids });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return result ?? null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function routeDocument(document: NativeDocument, options: CheerioOptions = {}): NativeDocument {
|
|
322
|
+
return document.nodeSpace === registry.space ? document : new RoutedDocument(document, options);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function sameNodeSpace(first: NativeDocument, second: NativeDocument): boolean {
|
|
326
|
+
return first === second || !!first.nodeSpace && first.nodeSpace === second.nodeSpace;
|
|
327
|
+
}
|