toolcraft 0.0.788 → 0.0.789
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/safe-fs/dist/core.js +1 -1
- package/node_modules/@poe-code/xml-ast/README.md +10 -1
- package/node_modules/@poe-code/xml-ast/dist/characters.d.ts +3 -0
- package/node_modules/@poe-code/xml-ast/dist/characters.js +49 -0
- package/node_modules/@poe-code/xml-ast/dist/index.d.ts +5 -0
- package/node_modules/@poe-code/xml-ast/dist/index.js +103 -79
- package/node_modules/@poe-code/xml-ast/dist/source.d.ts +18 -0
- package/node_modules/@poe-code/xml-ast/dist/source.js +47 -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.789",
|
|
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.789",
|
|
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.789",
|
|
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.789",
|
|
152
152
|
"license": "MIT"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -8,7 +8,7 @@ export * from "./fs/object-publication/index.js";
|
|
|
8
8
|
export * from "./fs/webdav/index.js";
|
|
9
9
|
export * from "./bridge/index.js";
|
|
10
10
|
export * from "./python/index.js";
|
|
11
|
-
export { parseXml, parseXmlSteps, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
11
|
+
export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
12
12
|
export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits } from "./xml.js";
|
|
13
13
|
export * from "./contracts/object.js";
|
|
14
14
|
export { ObjectAuthority } from "./fs/object-authority.js";
|
|
@@ -8,7 +8,7 @@ export * from "./fs/object-publication/index.js";
|
|
|
8
8
|
export * from "./fs/webdav/index.js";
|
|
9
9
|
export * from "./bridge/index.js";
|
|
10
10
|
export * from "./python/index.js";
|
|
11
|
-
export { parseXml, parseXmlSteps, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
11
|
+
export { parseXml, parseXmlSteps, parseXmlSourceSteps, normalizeXmlChunks, parseXmlStream, XmlLimitError } from "./xml.js";
|
|
12
12
|
export * from "./contracts/object.js";
|
|
13
13
|
export { ObjectAuthority } from "./fs/object-authority.js";
|
|
14
14
|
export * from "./fs/s3/index.js";
|
|
@@ -31,7 +31,7 @@ Resource limits and the prohibition on DTDs remain in force. With `retainTree: f
|
|
|
31
31
|
repaired closing events. Drain events between generator checkpoints to await
|
|
32
32
|
external storage; the source string and open parser frames remain resident.
|
|
33
33
|
|
|
34
|
-
`parseXmlStream` consumes each chunk before requesting the next and calls the optional asynchronous checkpoint with at most 512 UTF-16 units. It preserves the buffered parser’s tree shape, namespaces and retained content. The source is closed on parsing or checkpoint failure. Decode byte sources incrementally before passing their strings. This avoids a full source string; the resulting tree and individual XML tokens still reside in memory. Recovery
|
|
34
|
+
`parseXmlStream` consumes each chunk before requesting the next and calls the optional asynchronous checkpoint with at most 512 UTF-16 units. It preserves the buffered parser’s tree shape, namespaces and retained content. The source is closed on parsing or checkpoint failure. Decode byte sources incrementally before passing their strings. This avoids a full source string; the resulting tree and individual XML tokens still reside in memory. Recovery is available through `parseXmlSteps` or the externally backed source API.
|
|
35
35
|
|
|
36
36
|
For validation or root-name detection, pass `retainTree: false` to `parseXmlStream`. It validates the complete document and enforces the same limits, but returns a root element with empty data fields. Only open element frames, namespace scopes and current parser tokens remain resident; completed descendants, text, attributes and document content are not retained. `onElement` still observes each element.
|
|
37
37
|
|
|
@@ -45,3 +45,12 @@ window awaits the consumer before advancing. Event elements contain names,
|
|
|
45
45
|
attributes and namespace metadata; individual XML tokens and open ancestry still
|
|
46
46
|
remain resident. This mode lets a caller own document storage independently of
|
|
47
47
|
the codec. Consumer failures propagate unchanged and close the input iterator.
|
|
48
|
+
|
|
49
|
+
`normalizeXmlChunks(chunks)` validates XML characters and normalizes BOM/line endings
|
|
50
|
+
in windows of at most 513 UTF-16 units, preserving surrogate pairs across chunks.
|
|
51
|
+
Store those windows in caller-owned backing and drive `parseXmlSourceSteps(length,
|
|
52
|
+
limits)`: numbers are work checkpoints; read requests specify UTF-16 `offset` and
|
|
53
|
+
`length` (at most 4096 units). Fill each request's `value` before advancing the
|
|
54
|
+
iterator. The source must already be validated and normalized. This supports the
|
|
55
|
+
same repairs and events without a complete source string; tokens and open ancestry
|
|
56
|
+
still remain resident. Close the iterator and backing on errors or cancellation.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function validCharacter(point) {
|
|
2
|
+
return point === 9 || point === 10 || point === 13
|
|
3
|
+
|| (point >= 0x20 && point <= 0xd7ff)
|
|
4
|
+
|| (point >= 0xe000 && point <= 0xfffd)
|
|
5
|
+
|| (point >= 0x10000 && point <= 0x10ffff);
|
|
6
|
+
}
|
|
7
|
+
/** Validate scalar values and normalize XML line endings in fixed windows. */
|
|
8
|
+
export async function* normalizeXmlChunks(source) {
|
|
9
|
+
let first = true, afterCR = false, high = 0, output = '';
|
|
10
|
+
for await (const chunk of source) {
|
|
11
|
+
for (let index = 0; index < chunk.length; index++) {
|
|
12
|
+
const code = chunk.charCodeAt(index);
|
|
13
|
+
if (first) {
|
|
14
|
+
first = false;
|
|
15
|
+
if (code === 0xfeff)
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (high) {
|
|
19
|
+
if (code < 0xdc00 || code > 0xdfff)
|
|
20
|
+
throw new SyntaxError('Invalid XML: invalid character');
|
|
21
|
+
output += String.fromCharCode(high, code);
|
|
22
|
+
high = 0;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
if (afterCR) {
|
|
26
|
+
afterCR = false;
|
|
27
|
+
if (code === 10)
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
31
|
+
high = code;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!validCharacter(code))
|
|
35
|
+
throw new SyntaxError('Invalid XML: invalid character');
|
|
36
|
+
afterCR = code === 13;
|
|
37
|
+
output += code === 13 ? '\n' : chunk[index];
|
|
38
|
+
}
|
|
39
|
+
if (output.length >= 512) {
|
|
40
|
+
yield output;
|
|
41
|
+
output = '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (high)
|
|
46
|
+
throw new SyntaxError('Invalid XML: invalid character');
|
|
47
|
+
if (output)
|
|
48
|
+
yield output;
|
|
49
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { normalizeXmlChunks } from "./characters.js";
|
|
2
|
+
import { type XmlSourceStep } from "./source.js";
|
|
3
|
+
export type { XmlSourceRead, XmlSourceStep } from "./source.js";
|
|
1
4
|
import type { XmlStreamEvent } from "./stream.js";
|
|
2
5
|
export { XmlLimitError } from "./errors.js";
|
|
3
6
|
export interface XmlName {
|
|
@@ -48,6 +51,8 @@ export interface XmlStepLimits extends XmlLimits {
|
|
|
48
51
|
* false. Drive the step iterator to await external consumers between windows. */
|
|
49
52
|
readonly events?: (event: XmlStreamEvent) => void;
|
|
50
53
|
}
|
|
54
|
+
/** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
|
|
55
|
+
export declare function parseXmlSourceSteps(length: number, limits?: XmlStepLimits): Generator<XmlSourceStep, XmlElement, void>;
|
|
51
56
|
export declare function parseXmlSteps(input: string, limits?: XmlStepLimits): Generator<number, XmlElement, void>;
|
|
52
57
|
export declare function parseXml(input: string, limits?: XmlLimits): XmlElement;
|
|
53
58
|
export { parseXmlStream, type XmlStreamLimits, type XmlStreamEvent } from "./stream.js";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { validCharacter } from "./characters.js";
|
|
2
|
+
export { normalizeXmlChunks } from "./characters.js";
|
|
3
|
+
import { XmlSource } from "./source.js";
|
|
1
4
|
import { XmlLimitError } from "./errors.js";
|
|
2
5
|
export { XmlLimitError } from "./errors.js";
|
|
3
6
|
function* find(source, needle, start) {
|
|
4
|
-
const found = source.indexOf(needle, start);
|
|
7
|
+
const found = typeof source === "string" ? source.indexOf(needle, start) : yield* source.indexOf(needle, start);
|
|
5
8
|
const end = found < 0 ? source.length : found;
|
|
6
9
|
let remaining = end - start;
|
|
7
10
|
while (remaining >= 512) {
|
|
@@ -17,12 +20,6 @@ const xmlnsNamespace = "http://www.w3.org/2000/xmlns/";
|
|
|
17
20
|
function invalid(message) {
|
|
18
21
|
throw new SyntaxError(`Invalid XML: ${message}`);
|
|
19
22
|
}
|
|
20
|
-
function validCharacter(point) {
|
|
21
|
-
return point === 9 || point === 10 || point === 13
|
|
22
|
-
|| (point >= 0x20 && point <= 0xd7ff)
|
|
23
|
-
|| (point >= 0xe000 && point <= 0xfffd)
|
|
24
|
-
|| (point >= 0x10000 && point <= 0x10ffff);
|
|
25
|
-
}
|
|
26
23
|
function nameStart(point) {
|
|
27
24
|
return point === 95 || (point >= 65 && point <= 90) || (point >= 97 && point <= 122)
|
|
28
25
|
|| (point >= 0xc0 && point <= 0xd6) || (point >= 0xd8 && point <= 0xf6)
|
|
@@ -182,71 +179,39 @@ function* validDeclaration(content, expectedEncoding) {
|
|
|
182
179
|
}
|
|
183
180
|
return offset === content.length;
|
|
184
181
|
}
|
|
185
|
-
|
|
182
|
+
function validateLimits(limits) {
|
|
183
|
+
for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
|
|
184
|
+
if (limit !== undefined && ((limit !== Infinity && !Number.isSafeInteger(limit)) || limit < 1))
|
|
185
|
+
throw new RangeError("XML limits must be positive integers");
|
|
186
|
+
}
|
|
187
|
+
if (limits.maxTextLength !== undefined && ((limits.maxTextLength !== Infinity && !Number.isSafeInteger(limits.maxTextLength)) || limits.maxTextLength < 1))
|
|
188
|
+
throw new RangeError("XML limits must be positive integers");
|
|
189
|
+
if (limits.events && limits.retainTree !== false)
|
|
190
|
+
throw new TypeError("XML events require retainTree: false");
|
|
191
|
+
}
|
|
192
|
+
/** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
|
|
193
|
+
export function* parseXmlSourceSteps(length, limits = {}) {
|
|
194
|
+
validateLimits(limits);
|
|
195
|
+
const source = new XmlSource(length);
|
|
186
196
|
const maxDepth = limits.maxDepth ?? Infinity;
|
|
187
197
|
const maxNodes = limits.maxNodes ?? Infinity;
|
|
188
198
|
const maxAttributes = limits.maxAttributes ?? Infinity;
|
|
189
199
|
const retainContent = limits.retainContent !== false;
|
|
190
200
|
const retainTree = limits.retainTree !== false;
|
|
191
201
|
const retain = retainTree && retainContent;
|
|
192
|
-
if (limits.events && retainTree)
|
|
193
|
-
throw new TypeError("XML events require retainTree: false");
|
|
194
202
|
const maxContentNodes = limits.maxContentNodes ?? Infinity;
|
|
195
203
|
const emptyContent = Object.freeze([]);
|
|
196
204
|
const emptyAttributes = Object.freeze([]);
|
|
197
205
|
const emptyNamespaces = new Map();
|
|
198
206
|
const maxAttributesPerElement = limits.maxAttributesPerElement ?? Infinity;
|
|
199
207
|
const maxNamespaces = limits.maxNamespaces ?? Infinity;
|
|
200
|
-
for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
|
|
201
|
-
if (limit !== undefined && ((limit !== Infinity && !Number.isSafeInteger(limit)) || limit < 1))
|
|
202
|
-
throw new RangeError("XML limits must be positive integers");
|
|
203
|
-
}
|
|
204
208
|
const maxTextLength = limits.maxTextLength ?? Infinity;
|
|
205
|
-
if (limits.maxTextLength !== undefined && ((maxTextLength !== Infinity && !Number.isSafeInteger(maxTextLength)) || maxTextLength < 1))
|
|
206
|
-
throw new RangeError("XML limits must be positive integers");
|
|
207
209
|
let textLength = 0;
|
|
208
210
|
const admitText = (text) => {
|
|
209
211
|
if (text.length > maxTextLength - textLength)
|
|
210
212
|
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
211
213
|
textLength += text.length;
|
|
212
214
|
};
|
|
213
|
-
const chunks = [];
|
|
214
|
-
const start = input.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
215
|
-
let chunkStart = start;
|
|
216
|
-
let chunkLength = 0;
|
|
217
|
-
let normalizedChunk = "";
|
|
218
|
-
// Keep validated spans intact; only changed line endings need new strings.
|
|
219
|
-
for (let index = start; index < input.length; index++) {
|
|
220
|
-
const point = input.codePointAt(index);
|
|
221
|
-
if (!validCharacter(point))
|
|
222
|
-
invalid("invalid character");
|
|
223
|
-
if (point === 13) {
|
|
224
|
-
normalizedChunk += input.slice(chunkStart, index) + "\n";
|
|
225
|
-
if (input.charCodeAt(index + 1) === 10)
|
|
226
|
-
index++;
|
|
227
|
-
chunkStart = index + 1;
|
|
228
|
-
chunkLength++;
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
chunkLength += point > 0xffff ? 2 : 1;
|
|
232
|
-
if (point > 0xffff)
|
|
233
|
-
index++;
|
|
234
|
-
}
|
|
235
|
-
if (chunkLength >= 512) {
|
|
236
|
-
if (normalizedChunk) {
|
|
237
|
-
chunks.push(normalizedChunk + input.slice(chunkStart, index + 1));
|
|
238
|
-
normalizedChunk = "";
|
|
239
|
-
chunkStart = index + 1;
|
|
240
|
-
}
|
|
241
|
-
chunkLength = 0;
|
|
242
|
-
yield 512;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (chunkLength)
|
|
246
|
-
yield chunkLength;
|
|
247
|
-
if (chunks.length || normalizedChunk)
|
|
248
|
-
chunks.push(normalizedChunk + input.slice(chunkStart));
|
|
249
|
-
const source = chunks.length ? chunks.join("") : input.slice(start);
|
|
250
215
|
const stack = [];
|
|
251
216
|
let root;
|
|
252
217
|
const prolog = [];
|
|
@@ -266,7 +231,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
266
231
|
const skipWhitespace = function* () {
|
|
267
232
|
const start = offset;
|
|
268
233
|
while (offset < source.length) {
|
|
269
|
-
const c = source.charCodeAt(offset);
|
|
234
|
+
const c = (yield* source.charCodeAt(offset));
|
|
270
235
|
if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
|
|
271
236
|
break;
|
|
272
237
|
offset++;
|
|
@@ -281,7 +246,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
281
246
|
const scanName = function* () {
|
|
282
247
|
const start = offset;
|
|
283
248
|
while (offset < source.length) {
|
|
284
|
-
const c = source.charCodeAt(offset);
|
|
249
|
+
const c = (yield* source.charCodeAt(offset));
|
|
285
250
|
if (c === 32 || c === 9 || c === 13 || c === 10 || c === 47 || c === 61 || c === 62 || c === 63)
|
|
286
251
|
break;
|
|
287
252
|
offset++;
|
|
@@ -291,7 +256,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
291
256
|
pendingWork -= 512;
|
|
292
257
|
}
|
|
293
258
|
}
|
|
294
|
-
const name = source.slice(start, offset);
|
|
259
|
+
const name = (yield* source.slice(start, offset));
|
|
295
260
|
pendingWork += offset - start;
|
|
296
261
|
while (pendingWork >= 512) {
|
|
297
262
|
yield 512;
|
|
@@ -308,10 +273,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
308
273
|
pendingWork -= 512;
|
|
309
274
|
}
|
|
310
275
|
}
|
|
311
|
-
if (source.charCodeAt(offset) !== 60) {
|
|
312
|
-
const next = source.indexOf("<", offset);
|
|
276
|
+
if ((yield* source.charCodeAt(offset)) !== 60) {
|
|
277
|
+
const next = (yield* source.indexOf("<", offset));
|
|
313
278
|
const endPos = next < 0 ? source.length : next;
|
|
314
|
-
const text = source.slice(offset, endPos);
|
|
279
|
+
const text = (yield* source.slice(offset, endPos));
|
|
315
280
|
pendingWork += (endPos - offset) + text.length;
|
|
316
281
|
while (pendingWork >= 512) {
|
|
317
282
|
yield 512;
|
|
@@ -360,13 +325,13 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
360
325
|
}
|
|
361
326
|
offset = endPos;
|
|
362
327
|
}
|
|
363
|
-
else if (source.startsWith("<!--", offset)) {
|
|
328
|
+
else if ((yield* source.startsWith("<!--", offset))) {
|
|
364
329
|
const end = yield* find(source, "-->", offset + 4);
|
|
365
|
-
|
|
330
|
+
const text = end < 0 ? "" : yield* source.slice(offset + 4, end);
|
|
331
|
+
if (end < 0 || (yield* find(text, "--", 0)) >= 0 || text.endsWith("-")) {
|
|
366
332
|
invalid("malformed comment");
|
|
367
333
|
}
|
|
368
334
|
const parent = stack.at(-1);
|
|
369
|
-
const text = source.slice(offset + 4, end);
|
|
370
335
|
admitText(text);
|
|
371
336
|
admitContent();
|
|
372
337
|
if (retain) {
|
|
@@ -375,13 +340,13 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
375
340
|
limits.events?.({ type: "content", content: { kind: "comment", text }, parent: parent?.element });
|
|
376
341
|
offset = end + 3;
|
|
377
342
|
}
|
|
378
|
-
else if (source.startsWith("<![CDATA[", offset)) {
|
|
343
|
+
else if ((yield* source.startsWith("<![CDATA[", offset))) {
|
|
379
344
|
if (!stack.length)
|
|
380
345
|
invalid("CDATA outside root");
|
|
381
346
|
const end = yield* find(source, "]]>", offset + 9);
|
|
382
347
|
if (end < 0)
|
|
383
348
|
invalid("unterminated CDATA");
|
|
384
|
-
const cdataText = source.slice(offset + 9, end);
|
|
349
|
+
const cdataText = (yield* source.slice(offset + 9, end));
|
|
385
350
|
admitText(cdataText);
|
|
386
351
|
const parent = stack.at(-1);
|
|
387
352
|
if (retainTree)
|
|
@@ -392,7 +357,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
392
357
|
limits.events?.({ type: "content", content: { kind: "cdata", text: cdataText }, parent: parent.element });
|
|
393
358
|
offset = end + 3;
|
|
394
359
|
}
|
|
395
|
-
else if (source.startsWith("<?", offset)) {
|
|
360
|
+
else if ((yield* source.startsWith("<?", offset))) {
|
|
396
361
|
const start = offset;
|
|
397
362
|
offset += 2;
|
|
398
363
|
const [target] = yield* scanName();
|
|
@@ -403,14 +368,14 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
403
368
|
const end = yield* find(source, "?>", offset);
|
|
404
369
|
if (end < 0)
|
|
405
370
|
invalid("unterminated processing instruction");
|
|
406
|
-
const content = source.slice(offset, end);
|
|
371
|
+
const content = (yield* source.slice(offset, end));
|
|
407
372
|
if (target.length === 3 && target.toLowerCase() === "xml") {
|
|
408
373
|
if (start !== 0 || target !== "xml"
|
|
409
374
|
|| !(yield* validDeclaration(content, limits.expectedEncoding))) {
|
|
410
375
|
invalid("unsupported XML declaration");
|
|
411
376
|
}
|
|
412
377
|
if (retainContent)
|
|
413
|
-
declaration = source.slice(start, end + 2);
|
|
378
|
+
declaration = (yield* source.slice(start, end + 2));
|
|
414
379
|
}
|
|
415
380
|
else if (content && !" \t\n\r".includes(content[0]))
|
|
416
381
|
invalid("invalid processing instruction");
|
|
@@ -434,10 +399,10 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
434
399
|
}
|
|
435
400
|
offset = end + 2;
|
|
436
401
|
}
|
|
437
|
-
else if (source.startsWith("<!", offset)) {
|
|
402
|
+
else if ((yield* source.startsWith("<!", offset))) {
|
|
438
403
|
invalid("DTD and entity declarations are forbidden");
|
|
439
404
|
}
|
|
440
|
-
else if (source.charCodeAt(offset + 1) === 47) {
|
|
405
|
+
else if ((yield* source.charCodeAt(offset + 1)) === 47) {
|
|
441
406
|
offset += 2;
|
|
442
407
|
const [name] = yield* scanName();
|
|
443
408
|
yield* skipWhitespace();
|
|
@@ -445,7 +410,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
445
410
|
yield 512;
|
|
446
411
|
pendingWork -= 512;
|
|
447
412
|
}
|
|
448
|
-
const closed = source
|
|
413
|
+
const closed = (yield* source.slice(offset++, offset)) === ">" ? stack.pop() : undefined;
|
|
449
414
|
if (closed?.name !== name) {
|
|
450
415
|
if (!limits.recover)
|
|
451
416
|
invalid("mismatched closing tag");
|
|
@@ -456,7 +421,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
456
421
|
}
|
|
457
422
|
else {
|
|
458
423
|
offset++;
|
|
459
|
-
const repeated = previousEmpty && source.startsWith(previousEmpty.suffix, offset) ? previousEmpty : undefined;
|
|
424
|
+
const repeated = previousEmpty && (yield* source.startsWith(previousEmpty.suffix, offset)) ? previousEmpty : undefined;
|
|
460
425
|
const [name, prefix, localName] = repeated
|
|
461
426
|
? [repeated.name, repeated.prefix, repeated.localName]
|
|
462
427
|
: (yield* scanName());
|
|
@@ -479,7 +444,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
479
444
|
yield 512;
|
|
480
445
|
pendingWork -= 512;
|
|
481
446
|
}
|
|
482
|
-
const ch = source.charCodeAt(offset);
|
|
447
|
+
const ch = (yield* source.charCodeAt(offset));
|
|
483
448
|
if (ch === 47 || ch === 62 || limits.recover && offset === source.length)
|
|
484
449
|
break;
|
|
485
450
|
if (ws === 0)
|
|
@@ -496,13 +461,13 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
496
461
|
if ((attributes?.size ?? 0) >= maxAttributesPerElement)
|
|
497
462
|
throw new XmlLimitError("maxAttributesPerElement", "XML attribute limit exceeded");
|
|
498
463
|
yield* skipWhitespace();
|
|
499
|
-
if (source
|
|
464
|
+
if ((yield* source.slice(offset++, offset)) !== "=")
|
|
500
465
|
invalid("missing attribute equals");
|
|
501
466
|
yield* skipWhitespace();
|
|
502
|
-
const quote = source
|
|
467
|
+
const quote = (yield* source.slice(offset++, offset));
|
|
503
468
|
if (quote !== '"' && quote !== "'")
|
|
504
469
|
invalid("unquoted attribute");
|
|
505
|
-
const end = source.indexOf(quote, offset);
|
|
470
|
+
const end = (yield* source.indexOf(quote, offset));
|
|
506
471
|
const scanLen = (end < 0 ? source.length : end) - offset;
|
|
507
472
|
pendingWork += scanLen;
|
|
508
473
|
while (pendingWork >= 512) {
|
|
@@ -511,7 +476,7 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
511
476
|
}
|
|
512
477
|
if (end < 0)
|
|
513
478
|
invalid("unterminated attribute");
|
|
514
|
-
const raw = source.slice(offset, end);
|
|
479
|
+
const raw = (yield* source.slice(offset, end));
|
|
515
480
|
pendingWork += raw.length * 2;
|
|
516
481
|
while (pendingWork >= 512) {
|
|
517
482
|
yield 512;
|
|
@@ -608,17 +573,17 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
608
573
|
invalid("multiple root elements");
|
|
609
574
|
else
|
|
610
575
|
root = element;
|
|
611
|
-
const empty = source
|
|
576
|
+
const empty = (yield* source.slice(offset, offset + 1)) === "/";
|
|
612
577
|
if (empty)
|
|
613
578
|
offset++;
|
|
614
|
-
if (source
|
|
579
|
+
if ((yield* source.slice(offset++, offset)) !== ">") {
|
|
615
580
|
if (!limits.recover)
|
|
616
581
|
invalid("unterminated start tag");
|
|
617
582
|
limits.recover("unterminated start tag");
|
|
618
583
|
}
|
|
619
584
|
// Only cache compact, attribute-free spellings. Namespace resolution and
|
|
620
585
|
// every admission counter still run for each distinct physical element.
|
|
621
|
-
if (empty && !attributes && name.length <= 512 && source.slice(offset - name.length - 3, offset) === `<${name}/>`)
|
|
586
|
+
if (empty && !attributes && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
|
|
622
587
|
previousEmpty = { suffix: name + "/>", name, prefix, localName };
|
|
623
588
|
limits.events?.({ type: "open", element, parent: parent?.element });
|
|
624
589
|
if (!empty)
|
|
@@ -645,6 +610,65 @@ export function* parseXmlSteps(input, limits = {}) {
|
|
|
645
610
|
}
|
|
646
611
|
return root;
|
|
647
612
|
}
|
|
613
|
+
function* normalizeXmlSteps(input) {
|
|
614
|
+
const chunks = [];
|
|
615
|
+
const start = input.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
616
|
+
let chunkStart = start;
|
|
617
|
+
let chunkLength = 0;
|
|
618
|
+
let normalizedChunk = "";
|
|
619
|
+
// Keep validated spans intact; only changed line endings need new strings.
|
|
620
|
+
for (let index = start; index < input.length; index++) {
|
|
621
|
+
const point = input.codePointAt(index);
|
|
622
|
+
if (!validCharacter(point))
|
|
623
|
+
invalid("invalid character");
|
|
624
|
+
if (point === 13) {
|
|
625
|
+
normalizedChunk += input.slice(chunkStart, index) + "\n";
|
|
626
|
+
if (input.charCodeAt(index + 1) === 10)
|
|
627
|
+
index++;
|
|
628
|
+
chunkStart = index + 1;
|
|
629
|
+
chunkLength++;
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
chunkLength += point > 0xffff ? 2 : 1;
|
|
633
|
+
if (point > 0xffff)
|
|
634
|
+
index++;
|
|
635
|
+
}
|
|
636
|
+
if (chunkLength >= 512) {
|
|
637
|
+
if (normalizedChunk) {
|
|
638
|
+
chunks.push(normalizedChunk + input.slice(chunkStart, index + 1));
|
|
639
|
+
normalizedChunk = "";
|
|
640
|
+
chunkStart = index + 1;
|
|
641
|
+
}
|
|
642
|
+
chunkLength = 0;
|
|
643
|
+
yield 512;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (chunkLength)
|
|
647
|
+
yield chunkLength;
|
|
648
|
+
if (chunks.length || normalizedChunk)
|
|
649
|
+
chunks.push(normalizedChunk + input.slice(chunkStart));
|
|
650
|
+
return chunks.length ? chunks.join("") : input.slice(start);
|
|
651
|
+
}
|
|
652
|
+
export function* parseXmlSteps(input, limits = {}) {
|
|
653
|
+
validateLimits(limits);
|
|
654
|
+
const source = yield* normalizeXmlSteps(input);
|
|
655
|
+
const parser = parseXmlSourceSteps(source.length, limits);
|
|
656
|
+
let step = parser.next();
|
|
657
|
+
try {
|
|
658
|
+
while (!step.done) {
|
|
659
|
+
if (typeof step.value === "number")
|
|
660
|
+
yield step.value;
|
|
661
|
+
else
|
|
662
|
+
step.value.value = source.slice(step.value.offset, step.value.offset + step.value.length);
|
|
663
|
+
step = parser.next();
|
|
664
|
+
}
|
|
665
|
+
return step.value;
|
|
666
|
+
}
|
|
667
|
+
finally {
|
|
668
|
+
if (!step.done)
|
|
669
|
+
parser.return(undefined);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
648
672
|
export function parseXml(input, limits = {}) {
|
|
649
673
|
const parser = parseXmlSteps(input, limits);
|
|
650
674
|
let result = parser.next();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Fill value with exactly the requested UTF-16 span before advancing the parser. */
|
|
2
|
+
export interface XmlSourceRead {
|
|
3
|
+
readonly offset: number;
|
|
4
|
+
readonly length: number;
|
|
5
|
+
value?: string;
|
|
6
|
+
}
|
|
7
|
+
export type XmlSourceStep = number | XmlSourceRead;
|
|
8
|
+
/** One fixed window; individual returned tokens still have their own size cost. */
|
|
9
|
+
export declare class XmlSource {
|
|
10
|
+
readonly length: number;
|
|
11
|
+
private window;
|
|
12
|
+
private start;
|
|
13
|
+
constructor(length: number);
|
|
14
|
+
slice(start: number, end?: number): Generator<XmlSourceStep, string, void>;
|
|
15
|
+
charCodeAt(offset: number): Generator<XmlSourceStep, number, void>;
|
|
16
|
+
startsWith(value: string, offset: number): Generator<XmlSourceStep, boolean, void>;
|
|
17
|
+
indexOf(value: string, offset: number): Generator<XmlSourceStep, number, void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** One fixed window; individual returned tokens still have their own size cost. */
|
|
2
|
+
export class XmlSource {
|
|
3
|
+
length;
|
|
4
|
+
window = '';
|
|
5
|
+
start = -1;
|
|
6
|
+
constructor(length) {
|
|
7
|
+
this.length = length;
|
|
8
|
+
if (!Number.isSafeInteger(length) || length < 0)
|
|
9
|
+
throw new RangeError("Invalid XML source length");
|
|
10
|
+
}
|
|
11
|
+
*slice(start, end = this.length) {
|
|
12
|
+
start = start < 0 ? Math.max(0, this.length + start) : Math.min(start, this.length);
|
|
13
|
+
end = end < 0 ? Math.max(0, this.length + end) : Math.min(end, this.length);
|
|
14
|
+
let result = '';
|
|
15
|
+
while (start < end) {
|
|
16
|
+
if (start < this.start || start >= this.start + this.window.length) {
|
|
17
|
+
const offset = Math.floor(start / 4096) * 4096;
|
|
18
|
+
const read = { offset, length: Math.min(4096, this.length - offset) };
|
|
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
|
+
}
|
|
25
|
+
const stop = Math.min(end, this.start + this.window.length);
|
|
26
|
+
result += this.window.slice(start - this.start, stop - this.start);
|
|
27
|
+
start = stop;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
*charCodeAt(offset) {
|
|
32
|
+
return (yield* this.slice(offset, offset + 1)).charCodeAt(0);
|
|
33
|
+
}
|
|
34
|
+
*startsWith(value, offset) {
|
|
35
|
+
return (yield* this.slice(offset, offset + value.length)) === value;
|
|
36
|
+
}
|
|
37
|
+
*indexOf(value, offset) {
|
|
38
|
+
while (offset < this.length) {
|
|
39
|
+
const end = Math.min(this.length, offset + 4096 + value.length - 1);
|
|
40
|
+
const found = (yield* this.slice(offset, end)).indexOf(value);
|
|
41
|
+
if (found >= 0)
|
|
42
|
+
return offset + found;
|
|
43
|
+
offset += 4096;
|
|
44
|
+
}
|
|
45
|
+
return -1;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.789",
|
|
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.789",
|
|
181
181
|
"toolcraft-design": "*",
|
|
182
182
|
"@poe-code/frontmatter": "*",
|
|
183
183
|
"@poe-code/agent-mcp-config": "*",
|