toolcraft 0.0.789 → 0.0.791
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/composition.json +2 -2
- package/dist/composition.json +2 -2
- package/node_modules/@poe-code/safe-fs/dist/core.d.ts +1 -1
- package/node_modules/@poe-code/xml-ast/README.md +25 -0
- package/node_modules/@poe-code/xml-ast/dist/characters.d.ts +1 -1
- package/node_modules/@poe-code/xml-ast/dist/characters.js +5 -1
- package/node_modules/@poe-code/xml-ast/dist/frames.d.ts +28 -0
- package/node_modules/@poe-code/xml-ast/dist/frames.js +45 -0
- package/node_modules/@poe-code/xml-ast/dist/index.d.ts +14 -1
- package/node_modules/@poe-code/xml-ast/dist/index.js +198 -85
- package/node_modules/@poe-code/xml-ast/dist/source.d.ts +10 -4
- package/node_modules/@poe-code/xml-ast/dist/source.js +45 -21
- package/node_modules/@poe-code/xml-ast/dist/stream.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.791",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.791",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
package/dist/composition.json
CHANGED
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "toolcraft",
|
|
141
|
-
"version": "0.0.
|
|
141
|
+
"version": "0.0.791",
|
|
142
142
|
"license": "MIT"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "toolcraft-schema",
|
|
151
|
-
"version": "0.0.
|
|
151
|
+
"version": "0.0.791",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -9,7 +9,7 @@ export * from "./fs/webdav/index.js";
|
|
|
9
9
|
export * from "./bridge/index.js";
|
|
10
10
|
export * from "./python/index.js";
|
|
11
11
|
export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
12
|
-
export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
|
|
12
|
+
export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits, XmlParserFrame, XmlFrameRequest } from "./xml.js";
|
|
13
13
|
export * from "./contracts/object.js";
|
|
14
14
|
export { ObjectAuthority } from "./fs/object-authority.js";
|
|
15
15
|
export * from "./fs/s3/index.js";
|
|
@@ -54,3 +54,28 @@ limits)`: numbers are work checkpoints; read requests specify UTF-16 `offset` an
|
|
|
54
54
|
iterator. The source must already be validated and normalized. This supports the
|
|
55
55
|
same repairs and events without a complete source string; tokens and open ancestry
|
|
56
56
|
still remain resident. Close the iterator and backing on errors or cancellation.
|
|
57
|
+
|
|
58
|
+
For tree-free external parsing, set `storeFrames: true` alongside `retainTree: false`.
|
|
59
|
+
The iterator also yields `frameOperation` requests: `push` saves the supplied frame,
|
|
60
|
+
`pop` removes the latest frame, and `peek` needs its `frame` filled with the saved
|
|
61
|
+
metadata. Keep the host stack in bounded backing storage. The parser caches one
|
|
62
|
+
current frame; individual frame metadata and namespace scopes still have their own
|
|
63
|
+
size cost. The default string and stream APIs retain their existing ancestry behavior.
|
|
64
|
+
|
|
65
|
+
Pass `undefined` as the source length to parse incrementally. Such read requests
|
|
66
|
+
have `streaming: true` and request at most 512 UTF-16 units. Return any nonempty
|
|
67
|
+
prefix available without pulling more input; set `complete: true` only when that
|
|
68
|
+
read reaches EOF (an empty EOF read is valid). Earlier offsets may be requested
|
|
69
|
+
again, so retain source spans in caller-backed storage. With `normalizeXmlChunks`,
|
|
70
|
+
pass `true` as the second argument to flush at input-chunk boundaries and preserve
|
|
71
|
+
early parser failures. The string parser and known-length protocol are unchanged.
|
|
72
|
+
|
|
73
|
+
With `retainTree: false`, `parseXmlSourceSteps` accepts `fragmentContent: true`
|
|
74
|
+
to emit text, CDATA, comment and processing-instruction bodies in at most 512 UTF-16 units without splitting surrogate
|
|
75
|
+
pairs. Content events with `continuation: true` belong to the preceding logical
|
|
76
|
+
node; store their bodies separately and retain one node identity. Yielded steps
|
|
77
|
+
let the host await each fragment before parsing more. Text validation and entity
|
|
78
|
+
decoding replay bounded source windows before publishing a logical node, retaining
|
|
79
|
+
its diagnostic and limit ordering. Numeric entity references are decoded without
|
|
80
|
+
materializing the token. Names, attribute values, namespace scopes and XML
|
|
81
|
+
declarations still require their own memory budget.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function validCharacter(point: number): boolean;
|
|
2
2
|
/** Validate scalar values and normalize XML line endings in fixed windows. */
|
|
3
|
-
export declare function normalizeXmlChunks(source: AsyncIterable<string> | Iterable<string
|
|
3
|
+
export declare function normalizeXmlChunks(source: AsyncIterable<string> | Iterable<string>, flushChunks?: boolean): AsyncGenerator<string>;
|
|
@@ -5,7 +5,7 @@ export function validCharacter(point) {
|
|
|
5
5
|
|| (point >= 0x10000 && point <= 0x10ffff);
|
|
6
6
|
}
|
|
7
7
|
/** Validate scalar values and normalize XML line endings in fixed windows. */
|
|
8
|
-
export async function* normalizeXmlChunks(source) {
|
|
8
|
+
export async function* normalizeXmlChunks(source, flushChunks = false) {
|
|
9
9
|
let first = true, afterCR = false, high = 0, output = '';
|
|
10
10
|
for await (const chunk of source) {
|
|
11
11
|
for (let index = 0; index < chunk.length; index++) {
|
|
@@ -41,6 +41,10 @@ export async function* normalizeXmlChunks(source) {
|
|
|
41
41
|
output = '';
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
if (flushChunks && output) {
|
|
45
|
+
yield output;
|
|
46
|
+
output = '';
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
if (high)
|
|
46
50
|
throw new SyntaxError('Invalid XML: invalid character');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { XmlContent, XmlElement } from './index.js';
|
|
2
|
+
export interface XmlParserFrame {
|
|
3
|
+
element: XmlElement;
|
|
4
|
+
content: XmlContent[] | undefined;
|
|
5
|
+
name: string;
|
|
6
|
+
namespaces: Map<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export type XmlFrameRequest = {
|
|
9
|
+
readonly frameOperation: 'push';
|
|
10
|
+
readonly frame: XmlParserFrame;
|
|
11
|
+
} | {
|
|
12
|
+
readonly frameOperation: 'pop';
|
|
13
|
+
} | {
|
|
14
|
+
readonly frameOperation: 'peek';
|
|
15
|
+
frame?: XmlParserFrame;
|
|
16
|
+
};
|
|
17
|
+
/** External mode caches only the current frame. The host owns the linked stack. */
|
|
18
|
+
export declare class XmlFrames {
|
|
19
|
+
private readonly external;
|
|
20
|
+
private readonly frames;
|
|
21
|
+
private depth;
|
|
22
|
+
private current;
|
|
23
|
+
constructor(external: boolean);
|
|
24
|
+
get length(): number;
|
|
25
|
+
peek(): Generator<XmlFrameRequest, XmlParserFrame | undefined, void>;
|
|
26
|
+
push(frame: XmlParserFrame): Generator<XmlFrameRequest, void, void>;
|
|
27
|
+
pop(): Generator<XmlFrameRequest, XmlParserFrame | undefined, void>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** External mode caches only the current frame. The host owns the linked stack. */
|
|
2
|
+
export class XmlFrames {
|
|
3
|
+
external;
|
|
4
|
+
frames = [];
|
|
5
|
+
depth = 0;
|
|
6
|
+
current;
|
|
7
|
+
constructor(external) {
|
|
8
|
+
this.external = external;
|
|
9
|
+
}
|
|
10
|
+
get length() { return this.external ? this.depth : this.frames.length; }
|
|
11
|
+
*peek() {
|
|
12
|
+
if (!this.external)
|
|
13
|
+
return this.frames.at(-1);
|
|
14
|
+
if (!this.depth)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!this.current) {
|
|
17
|
+
const request = { frameOperation: 'peek' };
|
|
18
|
+
yield request;
|
|
19
|
+
if (!request.frame)
|
|
20
|
+
throw new TypeError('Incomplete XML frame read');
|
|
21
|
+
this.current = request.frame;
|
|
22
|
+
}
|
|
23
|
+
return this.current;
|
|
24
|
+
}
|
|
25
|
+
*push(frame) {
|
|
26
|
+
if (!this.external) {
|
|
27
|
+
this.frames.push(frame);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
yield { frameOperation: 'push', frame };
|
|
31
|
+
this.current = frame;
|
|
32
|
+
this.depth++;
|
|
33
|
+
}
|
|
34
|
+
*pop() {
|
|
35
|
+
if (!this.external)
|
|
36
|
+
return this.frames.pop();
|
|
37
|
+
if (!this.depth)
|
|
38
|
+
return undefined;
|
|
39
|
+
const frame = yield* this.peek();
|
|
40
|
+
yield { frameOperation: 'pop' };
|
|
41
|
+
this.depth--;
|
|
42
|
+
this.current = undefined;
|
|
43
|
+
return frame;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type XmlFrameRequest } from "./frames.js";
|
|
2
|
+
export type { XmlParserFrame, XmlFrameRequest } from "./frames.js";
|
|
1
3
|
export { normalizeXmlChunks } from "./characters.js";
|
|
2
4
|
import { type XmlSourceStep } from "./source.js";
|
|
3
5
|
export type { XmlSourceRead, XmlSourceStep } from "./source.js";
|
|
@@ -51,8 +53,19 @@ export interface XmlStepLimits extends XmlLimits {
|
|
|
51
53
|
* false. Drive the step iterator to await external consumers between windows. */
|
|
52
54
|
readonly events?: (event: XmlStreamEvent) => void;
|
|
53
55
|
}
|
|
56
|
+
export interface XmlSourceLimits extends XmlStepLimits {
|
|
57
|
+
/** Emit content bodies in bounded fragments, marking continuations of the same logical
|
|
58
|
+
* node. Requires retainTree: false; consumers must preserve fragment identity. */
|
|
59
|
+
readonly fragmentContent?: boolean;
|
|
60
|
+
/** Store active parser frames through host-serviced requests. Requires retainTree: false. */
|
|
61
|
+
readonly storeFrames?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export type XmlParseStep = XmlSourceStep | XmlFrameRequest;
|
|
54
64
|
/** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
|
|
55
|
-
export declare function parseXmlSourceSteps(length: number, limits?: XmlStepLimits
|
|
65
|
+
export declare function parseXmlSourceSteps(length: number | undefined, limits?: XmlStepLimits & {
|
|
66
|
+
readonly storeFrames?: false;
|
|
67
|
+
}): Generator<XmlSourceStep, XmlElement, void>;
|
|
68
|
+
export declare function parseXmlSourceSteps(length: number | undefined, limits: XmlSourceLimits): Generator<XmlParseStep, XmlElement, void>;
|
|
56
69
|
export declare function parseXmlSteps(input: string, limits?: XmlStepLimits): Generator<number, XmlElement, void>;
|
|
57
70
|
export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
|
|
58
71
|
export { parseXmlStream, type XmlStreamLimits, type XmlStreamEvent } from "./stream.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { XmlFrames } from "./frames.js";
|
|
1
2
|
import { validCharacter } from "./characters.js";
|
|
2
3
|
export { normalizeXmlChunks } from "./characters.js";
|
|
3
4
|
import { XmlSource } from "./source.js";
|
|
@@ -66,6 +67,95 @@ function qualifiedNameSync(name, cache) {
|
|
|
66
67
|
}
|
|
67
68
|
return parts;
|
|
68
69
|
}
|
|
70
|
+
function* entity(source, start, end, recover) {
|
|
71
|
+
// Inspect only a bounded prefix, even for arbitrarily long numeric references.
|
|
72
|
+
const prefix = typeof source === "string" ? source.slice(start, Math.min(end, start + 4))
|
|
73
|
+
: yield* source.slice(start, Math.min(end, start + 4));
|
|
74
|
+
const predefined = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
75
|
+
if (end - start <= 4 && Object.hasOwn(predefined, prefix))
|
|
76
|
+
return predefined[prefix];
|
|
77
|
+
const hexadecimal = prefix.startsWith("#x"), digits = start + (hexadecimal ? 2 : 1);
|
|
78
|
+
if (!prefix.startsWith("#") || digits >= end) {
|
|
79
|
+
if (!recover)
|
|
80
|
+
invalid("undeclared entity");
|
|
81
|
+
recover("undeclared entity");
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
let point = 0;
|
|
85
|
+
for (let index = digits; index < end; index++) {
|
|
86
|
+
const code = typeof source === "string" ? source.charCodeAt(index) : yield* source.charCodeAt(index);
|
|
87
|
+
const digit = code >= 48 && code <= 57 ? code - 48 : hexadecimal && code >= 65 && code <= 70 ? code - 55
|
|
88
|
+
: hexadecimal && code >= 97 && code <= 102 ? code - 87 : -1;
|
|
89
|
+
if (digit < 0)
|
|
90
|
+
invalid("undeclared entity");
|
|
91
|
+
point = point * (hexadecimal ? 16 : 10) + digit;
|
|
92
|
+
if (point > 0x10ffff)
|
|
93
|
+
invalid("invalid character reference");
|
|
94
|
+
if ((index - digits + 1) % 512 === 0)
|
|
95
|
+
yield 512;
|
|
96
|
+
}
|
|
97
|
+
if ((end - digits) % 512)
|
|
98
|
+
yield (end - digits) % 512;
|
|
99
|
+
if (!validCharacter(point))
|
|
100
|
+
invalid("invalid character reference");
|
|
101
|
+
return String.fromCodePoint(point);
|
|
102
|
+
}
|
|
103
|
+
/** Decoded text fragments; source read/work requests remain host-serviced. */
|
|
104
|
+
function* textParts(source, start, end, decode, recover) {
|
|
105
|
+
let cursor = start;
|
|
106
|
+
while (cursor < end) {
|
|
107
|
+
let finish = Math.min(end, cursor + 512);
|
|
108
|
+
if (finish < end) {
|
|
109
|
+
const last = yield* source.charCodeAt(finish - 1);
|
|
110
|
+
if (last >= 0xd800 && last <= 0xdbff)
|
|
111
|
+
finish--;
|
|
112
|
+
}
|
|
113
|
+
const raw = yield* source.slice(cursor, finish);
|
|
114
|
+
const found = decode ? raw.indexOf("&") : -1;
|
|
115
|
+
if (found < 0) {
|
|
116
|
+
yield raw;
|
|
117
|
+
yield raw.length;
|
|
118
|
+
cursor = finish;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const amp = cursor + found;
|
|
122
|
+
if (found) {
|
|
123
|
+
yield raw.slice(0, found);
|
|
124
|
+
yield found;
|
|
125
|
+
}
|
|
126
|
+
const semicolon = yield* source.indexOf(";", amp + 1, end);
|
|
127
|
+
if (semicolon < 0) {
|
|
128
|
+
if (!recover)
|
|
129
|
+
invalid("unterminated entity");
|
|
130
|
+
recover("unterminated entity");
|
|
131
|
+
cursor = amp + 1;
|
|
132
|
+
decode = false;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const part = yield* entity(source, amp + 1, semicolon, recover);
|
|
136
|
+
if (part)
|
|
137
|
+
yield part;
|
|
138
|
+
yield 1;
|
|
139
|
+
cursor = semicolon + 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function* textFragments(source, start, end, decode, recover) {
|
|
143
|
+
let pending = "";
|
|
144
|
+
for (const part of textParts(source, start, end, decode, recover)) {
|
|
145
|
+
if (typeof part !== "string") {
|
|
146
|
+
yield part;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
pending += part;
|
|
150
|
+
while (pending.length >= 512) {
|
|
151
|
+
const last = pending.charCodeAt(511), width = last >= 0xd800 && last <= 0xdbff ? 511 : 512;
|
|
152
|
+
yield pending.slice(0, width);
|
|
153
|
+
pending = pending.slice(width);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (pending)
|
|
157
|
+
yield pending;
|
|
158
|
+
}
|
|
69
159
|
function* entities(text, recover) {
|
|
70
160
|
let result = "";
|
|
71
161
|
let offset = 0;
|
|
@@ -81,39 +171,7 @@ function* entities(text, recover) {
|
|
|
81
171
|
recover("unterminated entity");
|
|
82
172
|
return result + text.slice(start + 1);
|
|
83
173
|
}
|
|
84
|
-
|
|
85
|
-
const predefined = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
86
|
-
if (Object.hasOwn(predefined, entity))
|
|
87
|
-
result += predefined[entity];
|
|
88
|
-
else {
|
|
89
|
-
const hexadecimal = entity.startsWith("#x");
|
|
90
|
-
const digits = entity.slice(hexadecimal ? 2 : 1);
|
|
91
|
-
if (!entity.startsWith("#") || !digits.length) {
|
|
92
|
-
if (!recover)
|
|
93
|
-
invalid("undeclared entity");
|
|
94
|
-
recover("undeclared entity");
|
|
95
|
-
offset = end + 1;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
let point = 0;
|
|
99
|
-
for (let index = 0; index < digits.length; index++) {
|
|
100
|
-
const code = digits.charCodeAt(index);
|
|
101
|
-
const digit = code >= 48 && code <= 57 ? code - 48 : hexadecimal && code >= 65 && code <= 70 ? code - 55
|
|
102
|
-
: hexadecimal && code >= 97 && code <= 102 ? code - 87 : -1;
|
|
103
|
-
if (digit < 0)
|
|
104
|
-
invalid("undeclared entity");
|
|
105
|
-
point = point * (hexadecimal ? 16 : 10) + digit;
|
|
106
|
-
if (point > 0x10ffff)
|
|
107
|
-
invalid("invalid character reference");
|
|
108
|
-
if ((index + 1) % 512 === 0)
|
|
109
|
-
yield 512;
|
|
110
|
-
}
|
|
111
|
-
if (digits.length % 512)
|
|
112
|
-
yield digits.length % 512;
|
|
113
|
-
if (!validCharacter(point))
|
|
114
|
-
invalid("invalid character reference");
|
|
115
|
-
result += String.fromCodePoint(point);
|
|
116
|
-
}
|
|
174
|
+
result += yield* entity(text, start + 1, end, recover);
|
|
117
175
|
offset = end + 1;
|
|
118
176
|
}
|
|
119
177
|
return result;
|
|
@@ -189,9 +247,12 @@ function validateLimits(limits) {
|
|
|
189
247
|
if (limits.events && limits.retainTree !== false)
|
|
190
248
|
throw new TypeError("XML events require retainTree: false");
|
|
191
249
|
}
|
|
192
|
-
/** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
|
|
193
250
|
export function* parseXmlSourceSteps(length, limits = {}) {
|
|
194
251
|
validateLimits(limits);
|
|
252
|
+
if (limits.storeFrames && limits.retainTree !== false)
|
|
253
|
+
throw new TypeError("Stored XML frames require retainTree: false");
|
|
254
|
+
if (limits.fragmentContent && limits.retainTree !== false)
|
|
255
|
+
throw new TypeError("XML content fragments require retainTree: false");
|
|
195
256
|
const source = new XmlSource(length);
|
|
196
257
|
const maxDepth = limits.maxDepth ?? Infinity;
|
|
197
258
|
const maxNodes = limits.maxNodes ?? Infinity;
|
|
@@ -212,7 +273,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
212
273
|
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
213
274
|
textLength += text.length;
|
|
214
275
|
};
|
|
215
|
-
const stack =
|
|
276
|
+
const stack = new XmlFrames(limits.storeFrames === true);
|
|
216
277
|
let root;
|
|
217
278
|
const prolog = [];
|
|
218
279
|
const epilog = [];
|
|
@@ -230,7 +291,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
230
291
|
let pendingWork = 0;
|
|
231
292
|
const skipWhitespace = function* () {
|
|
232
293
|
const start = offset;
|
|
233
|
-
while (
|
|
294
|
+
while (yield* source.has(offset)) {
|
|
234
295
|
const c = (yield* source.charCodeAt(offset));
|
|
235
296
|
if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
|
|
236
297
|
break;
|
|
@@ -245,7 +306,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
245
306
|
};
|
|
246
307
|
const scanName = function* () {
|
|
247
308
|
const start = offset;
|
|
248
|
-
while (
|
|
309
|
+
while (yield* source.has(offset)) {
|
|
249
310
|
const c = (yield* source.charCodeAt(offset));
|
|
250
311
|
if (c === 32 || c === 9 || c === 13 || c === 10 || c === 47 || c === 61 || c === 62 || c === 63)
|
|
251
312
|
break;
|
|
@@ -265,7 +326,34 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
265
326
|
const [prefix, localName] = qualifiedNameSync(name, qualifiedNames);
|
|
266
327
|
return [name, prefix, localName];
|
|
267
328
|
};
|
|
268
|
-
|
|
329
|
+
const rawContent = function* (kind, start, end, parent, target = "") {
|
|
330
|
+
if (end - start > maxTextLength - textLength)
|
|
331
|
+
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
332
|
+
textLength += end - start;
|
|
333
|
+
admitContent();
|
|
334
|
+
let cursor = start, continuation = false;
|
|
335
|
+
do {
|
|
336
|
+
let finish = limits.fragmentContent ? Math.min(end, cursor + 512) : end;
|
|
337
|
+
if (finish < end) {
|
|
338
|
+
const last = yield* source.charCodeAt(finish - 1);
|
|
339
|
+
if (last >= 0xd800 && last <= 0xdbff)
|
|
340
|
+
finish--;
|
|
341
|
+
}
|
|
342
|
+
const text = yield* source.slice(cursor, finish);
|
|
343
|
+
const content = kind === "processing-instruction" ? { kind, target, text } : { kind, text };
|
|
344
|
+
if (kind === "cdata" && retainTree)
|
|
345
|
+
parent.element.text += text;
|
|
346
|
+
if (retain)
|
|
347
|
+
(parent?.content ?? (root ? epilog : prolog)).push(content);
|
|
348
|
+
limits.events?.({ type: "content", content, parent: parent?.element,
|
|
349
|
+
...(continuation ? { continuation: true } : {}) });
|
|
350
|
+
if (limits.fragmentContent)
|
|
351
|
+
yield Math.max(1, text.length);
|
|
352
|
+
continuation = true;
|
|
353
|
+
cursor = finish;
|
|
354
|
+
} while (cursor < end);
|
|
355
|
+
};
|
|
356
|
+
while (yield* source.has(offset)) {
|
|
269
357
|
pendingWork += 1;
|
|
270
358
|
if (pendingWork >= 512) {
|
|
271
359
|
while (pendingWork >= 512) {
|
|
@@ -276,6 +364,49 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
276
364
|
if ((yield* source.charCodeAt(offset)) !== 60) {
|
|
277
365
|
const next = (yield* source.indexOf("<", offset));
|
|
278
366
|
const endPos = next < 0 ? source.length : next;
|
|
367
|
+
if (limits.fragmentContent) {
|
|
368
|
+
if ((yield* source.indexOf("]]>", offset, endPos)) >= 0)
|
|
369
|
+
invalid("CDATA terminator in text");
|
|
370
|
+
const parent = yield* stack.peek();
|
|
371
|
+
let size = 0, outsideText = false;
|
|
372
|
+
// Validate before emitting any part of the logical node. This preserves
|
|
373
|
+
// entity/recovery diagnostics and limit precedence without retaining it.
|
|
374
|
+
for (const part of textFragments(source, offset, endPos, !!parent, limits.recover)) {
|
|
375
|
+
if (typeof part !== "string") {
|
|
376
|
+
yield part;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
size += part.length;
|
|
380
|
+
if (!parent)
|
|
381
|
+
for (const character of part) {
|
|
382
|
+
if (!" \t\r\n".includes(character))
|
|
383
|
+
outsideText = true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (size > maxTextLength - textLength)
|
|
387
|
+
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
388
|
+
textLength += size;
|
|
389
|
+
if (outsideText)
|
|
390
|
+
invalid("text outside the root");
|
|
391
|
+
if (size) {
|
|
392
|
+
admitContent();
|
|
393
|
+
if (limits.events) {
|
|
394
|
+
let continuation = false;
|
|
395
|
+
for (const part of textFragments(source, offset, endPos, !!parent, limits.recover ? () => { } : undefined)) {
|
|
396
|
+
if (typeof part !== "string") {
|
|
397
|
+
yield part;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
limits.events({ type: "content", content: { kind: "text", text: part }, parent: parent?.element,
|
|
401
|
+
...(continuation ? { continuation: true } : {}) });
|
|
402
|
+
continuation = true;
|
|
403
|
+
yield 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
offset = endPos;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
279
410
|
const text = (yield* source.slice(offset, endPos));
|
|
280
411
|
pendingWork += (endPos - offset) + text.length;
|
|
281
412
|
while (pendingWork >= 512) {
|
|
@@ -290,7 +421,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
290
421
|
pendingWork -= 512;
|
|
291
422
|
}
|
|
292
423
|
admitText(resolved);
|
|
293
|
-
const parent = stack.
|
|
424
|
+
const parent = (yield* stack.peek());
|
|
294
425
|
if (parent) {
|
|
295
426
|
if (retainTree)
|
|
296
427
|
parent.element.text += resolved;
|
|
@@ -327,17 +458,10 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
327
458
|
}
|
|
328
459
|
else if ((yield* source.startsWith("<!--", offset))) {
|
|
329
460
|
const end = yield* find(source, "-->", offset + 4);
|
|
330
|
-
|
|
331
|
-
|
|
461
|
+
if (end < 0 || (yield* source.indexOf("--", offset + 4, end)) >= 0
|
|
462
|
+
|| (end > offset + 4 && (yield* source.charCodeAt(end - 1)) === 45))
|
|
332
463
|
invalid("malformed comment");
|
|
333
|
-
|
|
334
|
-
const parent = stack.at(-1);
|
|
335
|
-
admitText(text);
|
|
336
|
-
admitContent();
|
|
337
|
-
if (retain) {
|
|
338
|
-
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "comment", text });
|
|
339
|
-
}
|
|
340
|
-
limits.events?.({ type: "content", content: { kind: "comment", text }, parent: parent?.element });
|
|
464
|
+
yield* rawContent("comment", offset + 4, end, yield* stack.peek());
|
|
341
465
|
offset = end + 3;
|
|
342
466
|
}
|
|
343
467
|
else if ((yield* source.startsWith("<![CDATA[", offset))) {
|
|
@@ -346,15 +470,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
346
470
|
const end = yield* find(source, "]]>", offset + 9);
|
|
347
471
|
if (end < 0)
|
|
348
472
|
invalid("unterminated CDATA");
|
|
349
|
-
|
|
350
|
-
admitText(cdataText);
|
|
351
|
-
const parent = stack.at(-1);
|
|
352
|
-
if (retainTree)
|
|
353
|
-
parent.element.text += cdataText;
|
|
354
|
-
admitContent();
|
|
355
|
-
if (retain)
|
|
356
|
-
parent.content.push({ kind: "cdata", text: cdataText });
|
|
357
|
-
limits.events?.({ type: "content", content: { kind: "cdata", text: cdataText }, parent: parent.element });
|
|
473
|
+
yield* rawContent("cdata", offset + 9, end, yield* stack.peek());
|
|
358
474
|
offset = end + 3;
|
|
359
475
|
}
|
|
360
476
|
else if ((yield* source.startsWith("<?", offset))) {
|
|
@@ -368,34 +484,31 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
368
484
|
const end = yield* find(source, "?>", offset);
|
|
369
485
|
if (end < 0)
|
|
370
486
|
invalid("unterminated processing instruction");
|
|
371
|
-
const content = (yield* source.slice(offset, end));
|
|
372
487
|
if (target.length === 3 && target.toLowerCase() === "xml") {
|
|
488
|
+
const content = yield* source.slice(offset, end);
|
|
373
489
|
if (start !== 0 || target !== "xml"
|
|
374
|
-
|| !(yield* validDeclaration(content, limits.expectedEncoding)))
|
|
490
|
+
|| !(yield* validDeclaration(content, limits.expectedEncoding)))
|
|
375
491
|
invalid("unsupported XML declaration");
|
|
376
|
-
}
|
|
377
492
|
if (retainContent)
|
|
378
|
-
declaration =
|
|
493
|
+
declaration = yield* source.slice(start, end + 2);
|
|
379
494
|
}
|
|
380
|
-
else
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
495
|
+
else {
|
|
496
|
+
if (offset < end && !" \t\n\r".includes(yield* source.slice(offset, offset + 1)))
|
|
497
|
+
invalid("invalid processing instruction");
|
|
498
|
+
let body = offset;
|
|
499
|
+
while (body < end && " \t\n\r".includes(yield* source.slice(body, body + 1))) {
|
|
500
|
+
body++;
|
|
501
|
+
pendingWork++;
|
|
502
|
+
if (pendingWork >= 512) {
|
|
503
|
+
yield 512;
|
|
504
|
+
pendingWork -= 512;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
388
507
|
while (pendingWork >= 512) {
|
|
389
508
|
yield 512;
|
|
390
509
|
pendingWork -= 512;
|
|
391
510
|
}
|
|
392
|
-
|
|
393
|
-
admitText(text);
|
|
394
|
-
admitContent();
|
|
395
|
-
if (retain) {
|
|
396
|
-
(parent?.content ?? (root ? epilog : prolog)).push({ kind: "processing-instruction", target, text });
|
|
397
|
-
}
|
|
398
|
-
limits.events?.({ type: "content", content: { kind: "processing-instruction", target, text }, parent: parent?.element });
|
|
511
|
+
yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target);
|
|
399
512
|
}
|
|
400
513
|
offset = end + 2;
|
|
401
514
|
}
|
|
@@ -410,14 +523,14 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
410
523
|
yield 512;
|
|
411
524
|
pendingWork -= 512;
|
|
412
525
|
}
|
|
413
|
-
const closed = (yield* source.slice(offset++, offset)) === ">" ? stack.pop() : undefined;
|
|
526
|
+
const closed = (yield* source.slice(offset++, offset)) === ">" ? (yield* stack.pop()) : undefined;
|
|
414
527
|
if (closed?.name !== name) {
|
|
415
528
|
if (!limits.recover)
|
|
416
529
|
invalid("mismatched closing tag");
|
|
417
530
|
limits.recover("mismatched closing tag");
|
|
418
531
|
}
|
|
419
532
|
if (closed)
|
|
420
|
-
limits.events?.({ type: "close", element: closed.element, parent: stack.
|
|
533
|
+
limits.events?.({ type: "close", element: closed.element, parent: (yield* stack.peek())?.element });
|
|
421
534
|
}
|
|
422
535
|
else {
|
|
423
536
|
offset++;
|
|
@@ -436,7 +549,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
436
549
|
}
|
|
437
550
|
let attributes;
|
|
438
551
|
const attrMeta = [];
|
|
439
|
-
let namespaces = stack.
|
|
552
|
+
let namespaces = (yield* stack.peek())?.namespaces ?? new Map([["xml", xmlNamespace]]);
|
|
440
553
|
let ownsNamespaces = stack.length === 0;
|
|
441
554
|
while (!repeated) {
|
|
442
555
|
const ws = (yield* skipWhitespace());
|
|
@@ -543,7 +656,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
543
656
|
if (stack.length + 1 > maxDepth)
|
|
544
657
|
throw new XmlLimitError("maxDepth", "XML resource limit exceeded");
|
|
545
658
|
const namespace = namespaces.get(prefix) ?? "";
|
|
546
|
-
limits.onElement?.({ name, namespace, localName }, stack.
|
|
659
|
+
limits.onElement?.({ name, namespace, localName }, (yield* stack.peek())?.element, stack.length + 1);
|
|
547
660
|
admitContent();
|
|
548
661
|
const retainedAttributes = [];
|
|
549
662
|
if (retainContent && attributes) {
|
|
@@ -562,7 +675,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
562
675
|
}
|
|
563
676
|
const content = retain ? [] : undefined;
|
|
564
677
|
const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
|
|
565
|
-
const parent = stack.
|
|
678
|
+
const parent = (yield* stack.peek());
|
|
566
679
|
if (parent) {
|
|
567
680
|
if (retainTree) {
|
|
568
681
|
parent.element.children.push(element);
|
|
@@ -587,7 +700,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
587
700
|
previousEmpty = { suffix: name + "/>", name, prefix, localName };
|
|
588
701
|
limits.events?.({ type: "open", element, parent: parent?.element });
|
|
589
702
|
if (!empty)
|
|
590
|
-
stack.push({ element, content, name, namespaces });
|
|
703
|
+
yield* stack.push({ element, content, name, namespaces });
|
|
591
704
|
else
|
|
592
705
|
limits.events?.({ type: "close", element, parent: parent?.element });
|
|
593
706
|
}
|
|
@@ -603,8 +716,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
603
716
|
invalid("incomplete document");
|
|
604
717
|
limits.recover("incomplete document");
|
|
605
718
|
while (stack.length) {
|
|
606
|
-
const closed = stack.pop();
|
|
607
|
-
limits.events?.({ type: "close", element: closed.element, parent: stack.
|
|
719
|
+
const closed = (yield* stack.pop());
|
|
720
|
+
limits.events?.({ type: "close", element: closed.element, parent: (yield* stack.peek())?.element });
|
|
608
721
|
yield 1;
|
|
609
722
|
}
|
|
610
723
|
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
/** Fill value
|
|
1
|
+
/** Fill value before advancing. Streaming reads may be short; mark complete at EOF. */
|
|
2
2
|
export interface XmlSourceRead {
|
|
3
3
|
readonly offset: number;
|
|
4
4
|
readonly length: number;
|
|
5
|
+
readonly streaming?: boolean;
|
|
5
6
|
value?: string;
|
|
7
|
+
complete?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export type XmlSourceStep = number | XmlSourceRead;
|
|
8
10
|
/** One fixed window; individual returned tokens still have their own size cost. */
|
|
9
11
|
export declare class XmlSource {
|
|
10
|
-
readonly length: number;
|
|
11
12
|
private window;
|
|
12
13
|
private start;
|
|
13
|
-
|
|
14
|
+
private end;
|
|
15
|
+
private readonly streaming;
|
|
16
|
+
constructor(length: number | undefined);
|
|
17
|
+
get length(): number;
|
|
18
|
+
private load;
|
|
19
|
+
has(offset: number): Generator<XmlSourceStep, boolean, void>;
|
|
14
20
|
slice(start: number, end?: number): Generator<XmlSourceStep, string, void>;
|
|
15
21
|
charCodeAt(offset: number): Generator<XmlSourceStep, number, void>;
|
|
16
22
|
startsWith(value: string, offset: number): Generator<XmlSourceStep, boolean, void>;
|
|
17
|
-
indexOf(value: string, offset: number): Generator<XmlSourceStep, number, void>;
|
|
23
|
+
indexOf(value: string, offset: number, end?: number): Generator<XmlSourceStep, number, void>;
|
|
18
24
|
}
|
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
/** One fixed window; individual returned tokens still have their own size cost. */
|
|
2
2
|
export class XmlSource {
|
|
3
|
-
length;
|
|
4
3
|
window = '';
|
|
5
4
|
start = -1;
|
|
5
|
+
end;
|
|
6
|
+
streaming;
|
|
6
7
|
constructor(length) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
if (length !== undefined && (!Number.isSafeInteger(length) || length < 0))
|
|
9
|
+
throw new RangeError('Invalid XML source length');
|
|
10
|
+
this.streaming = length === undefined;
|
|
11
|
+
this.end = length ?? Infinity;
|
|
12
|
+
}
|
|
13
|
+
get length() { return this.end; }
|
|
14
|
+
*load(offset) {
|
|
15
|
+
if (offset >= this.end || offset >= this.start && offset < this.start + this.window.length)
|
|
16
|
+
return;
|
|
17
|
+
const start = this.streaming ? offset : Math.floor(offset / 4096) * 4096;
|
|
18
|
+
const request = { offset: start, length: Math.min(this.streaming ? 512 : 4096, this.end - start),
|
|
19
|
+
...(this.streaming ? { streaming: true } : {}) };
|
|
20
|
+
yield request;
|
|
21
|
+
if (typeof request.value !== 'string' || request.value.length > request.length ||
|
|
22
|
+
(!this.streaming && request.value.length !== request.length) ||
|
|
23
|
+
(!request.value.length && !request.complete && this.streaming))
|
|
24
|
+
throw new TypeError('Incomplete XML source read');
|
|
25
|
+
if (this.streaming && request.complete)
|
|
26
|
+
this.end = start + request.value.length;
|
|
27
|
+
this.window = request.value;
|
|
28
|
+
this.start = start;
|
|
29
|
+
}
|
|
30
|
+
*has(offset) {
|
|
31
|
+
yield* this.load(offset);
|
|
32
|
+
return offset < this.end;
|
|
10
33
|
}
|
|
11
34
|
*slice(start, end = this.length) {
|
|
12
35
|
start = start < 0 ? Math.max(0, this.length + start) : Math.min(start, this.length);
|
|
13
36
|
end = end < 0 ? Math.max(0, this.length + end) : Math.min(end, this.length);
|
|
14
37
|
let result = '';
|
|
15
|
-
while (start < end) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
yield read;
|
|
20
|
-
if (typeof read.value !== 'string' || read.value.length !== read.length)
|
|
21
|
-
throw new TypeError('Incomplete XML source read');
|
|
22
|
-
this.window = read.value;
|
|
23
|
-
this.start = offset;
|
|
24
|
-
}
|
|
38
|
+
while (start < Math.min(end, this.length)) {
|
|
39
|
+
yield* this.load(start);
|
|
40
|
+
if (start >= this.end)
|
|
41
|
+
break;
|
|
25
42
|
const stop = Math.min(end, this.start + this.window.length);
|
|
26
43
|
result += this.window.slice(start - this.start, stop - this.start);
|
|
27
44
|
start = stop;
|
|
@@ -32,15 +49,22 @@ export class XmlSource {
|
|
|
32
49
|
return (yield* this.slice(offset, offset + 1)).charCodeAt(0);
|
|
33
50
|
}
|
|
34
51
|
*startsWith(value, offset) {
|
|
35
|
-
|
|
52
|
+
for (let index = 0; index < value.length; index++) {
|
|
53
|
+
if ((yield* this.charCodeAt(offset + index)) !== value.charCodeAt(index))
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
36
57
|
}
|
|
37
|
-
*indexOf(value, offset) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
58
|
+
*indexOf(value, offset, end = Infinity) {
|
|
59
|
+
let tail = '';
|
|
60
|
+
while (offset < end && (yield* this.has(offset))) {
|
|
61
|
+
const stop = Math.min(end, this.start + this.window.length);
|
|
62
|
+
const part = tail + this.window.slice(offset - this.start, stop - this.start);
|
|
63
|
+
const found = part.indexOf(value);
|
|
41
64
|
if (found >= 0)
|
|
42
|
-
return offset + found;
|
|
43
|
-
|
|
65
|
+
return offset - tail.length + found;
|
|
66
|
+
tail = value.length > 1 ? part.slice(-(value.length - 1)) : '';
|
|
67
|
+
offset = stop;
|
|
44
68
|
}
|
|
45
69
|
return -1;
|
|
46
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.791",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
"saxes"
|
|
178
178
|
],
|
|
179
179
|
"optionalDependencies": {
|
|
180
|
-
"toolcraft-schema": "0.0.
|
|
180
|
+
"toolcraft-schema": "0.0.791",
|
|
181
181
|
"toolcraft-design": "*",
|
|
182
182
|
"@poe-code/frontmatter": "*",
|
|
183
183
|
"@poe-code/agent-mcp-config": "*",
|