@poe-platform/safe-fs 0.1.813 → 0.1.815

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.
@@ -54,7 +54,9 @@ export interface XmlStepLimits extends XmlLimits {
54
54
  readonly events?: (event: XmlStreamEvent) => void;
55
55
  }
56
56
  export interface XmlSourceLimits extends XmlStepLimits {
57
- /** Emit text and CDATA in bounded fragments, marking continuations of the same logical
57
+ /** Keep normalized declaration fields instead of the complete source spelling. */
58
+ readonly compactDeclaration?: boolean;
59
+ /** Emit content bodies in bounded fragments, marking continuations of the same logical
58
60
  * node. Requires retainTree: false; consumers must preserve fragment identity. */
59
61
  readonly fragmentContent?: boolean;
60
62
  /** Store active parser frames through host-serviced requests. Requires retainTree: false. */
@@ -176,11 +176,10 @@ function* entities(text, recover) {
176
176
  }
177
177
  return result;
178
178
  }
179
- function* validDeclaration(content, expectedEncoding) {
180
- let offset = 0;
179
+ function* readDeclaration(source, offset, end, expectedEncoding) {
181
180
  const whitespace = function* () {
182
181
  const start = offset;
183
- while (offset < content.length && " \t\n\r".includes(content[offset])) {
182
+ while (offset < end && " \t\n\r".includes(yield* source.slice(offset, offset + 1))) {
184
183
  offset++;
185
184
  if ((offset - start) % 512 === 0)
186
185
  yield 512;
@@ -189,53 +188,58 @@ function* validDeclaration(content, expectedEncoding) {
189
188
  yield (offset - start) % 512;
190
189
  return offset - start;
191
190
  };
192
- const field = function* (name) {
193
- if (content.slice(offset, offset + name.length) !== name)
191
+ const field = function* (name, maxLength) {
192
+ if ((yield* source.slice(offset, Math.min(end, offset + name.length))) !== name)
194
193
  return undefined;
195
194
  offset += name.length;
196
195
  yield name.length;
197
196
  yield* whitespace();
198
- if (content[offset++] !== "=")
197
+ if (offset >= end || (yield* source.charCodeAt(offset++)) !== 61)
199
198
  return undefined;
200
199
  yield* whitespace();
201
- const quote = content[offset++];
200
+ if (offset >= end)
201
+ return undefined;
202
+ const quote = yield* source.slice(offset++, offset);
202
203
  if (quote !== "'" && quote !== '"')
203
204
  return undefined;
204
205
  const start = offset;
205
- while (offset < content.length && content[offset] !== quote) {
206
+ while (offset < end && (yield* source.slice(offset, offset + 1)) !== quote) {
206
207
  offset++;
207
208
  if ((offset - start) % 512 === 0)
208
209
  yield 512;
209
210
  }
210
211
  if ((offset - start) % 512)
211
212
  yield (offset - start) % 512;
212
- if (offset >= content.length)
213
+ if (offset >= end || offset - start > maxLength)
213
214
  return undefined;
214
- return content.slice(start, offset++);
215
+ return yield* source.slice(start, offset++);
215
216
  };
216
- if (!(yield* whitespace()) || (yield* field("version")) !== "1.0")
217
- return false;
217
+ if (!(yield* whitespace()) || (yield* field("version", 3)) !== "1.0")
218
+ return undefined;
219
+ let result = '<?xml version="1.0"';
218
220
  let spacing = yield* whitespace();
219
- if (content.slice(offset, offset + 8) === "encoding") {
221
+ if ((yield* source.slice(offset, Math.min(end, offset + 8))) === "encoding") {
220
222
  if (!spacing)
221
- return false;
222
- const encoding = yield* field("encoding");
223
- if (encoding === undefined || encoding.length > 8 || !["utf-8", "utf-16", "utf-16le", "utf-16be"].includes(encoding.toLowerCase()))
224
- return false;
223
+ return undefined;
224
+ const encoding = yield* field("encoding", 8);
225
+ if (encoding === undefined || !["utf-8", "utf-16", "utf-16le", "utf-16be"].includes(encoding.toLowerCase()))
226
+ return undefined;
225
227
  if (expectedEncoding !== undefined && encoding.toLowerCase() !== expectedEncoding.toLowerCase()
226
228
  && !(encoding.toLowerCase() === "utf-16" && (expectedEncoding === "UTF-16LE" || expectedEncoding === "UTF-16BE")))
227
- return false;
229
+ return undefined;
230
+ result += ` encoding="${encoding}"`;
228
231
  spacing = yield* whitespace();
229
232
  }
230
- if (content.slice(offset, offset + 10) === "standalone") {
233
+ if ((yield* source.slice(offset, Math.min(end, offset + 10))) === "standalone") {
231
234
  if (!spacing)
232
- return false;
233
- const standalone = yield* field("standalone");
234
- if (standalone === undefined || standalone.length > 3 || !["yes", "no"].includes(standalone))
235
- return false;
235
+ return undefined;
236
+ const standalone = yield* field("standalone", 3);
237
+ if (standalone === undefined || !["yes", "no"].includes(standalone))
238
+ return undefined;
239
+ result += ` standalone="${standalone}"`;
236
240
  yield* whitespace();
237
241
  }
238
- return offset === content.length;
242
+ return offset === end ? result + "?>" : undefined;
239
243
  }
240
244
  function validateLimits(limits) {
241
245
  for (const limit of [limits.maxDepth, limits.maxNodes, limits.maxAttributes, limits.maxContentNodes, limits.maxAttributesPerElement, limits.maxNamespaces]) {
@@ -326,6 +330,33 @@ export function* parseXmlSourceSteps(length, limits = {}) {
326
330
  const [prefix, localName] = qualifiedNameSync(name, qualifiedNames);
327
331
  return [name, prefix, localName];
328
332
  };
333
+ const rawContent = function* (kind, start, end, parent, target = "") {
334
+ if (end - start > maxTextLength - textLength)
335
+ throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
336
+ textLength += end - start;
337
+ admitContent();
338
+ let cursor = start, continuation = false;
339
+ do {
340
+ let finish = limits.fragmentContent ? Math.min(end, cursor + 512) : end;
341
+ if (finish < end) {
342
+ const last = yield* source.charCodeAt(finish - 1);
343
+ if (last >= 0xd800 && last <= 0xdbff)
344
+ finish--;
345
+ }
346
+ const text = yield* source.slice(cursor, finish);
347
+ const content = kind === "processing-instruction" ? { kind, target, text } : { kind, text };
348
+ if (kind === "cdata" && retainTree)
349
+ parent.element.text += text;
350
+ if (retain)
351
+ (parent?.content ?? (root ? epilog : prolog)).push(content);
352
+ limits.events?.({ type: "content", content, parent: parent?.element,
353
+ ...(continuation ? { continuation: true } : {}) });
354
+ if (limits.fragmentContent)
355
+ yield Math.max(1, text.length);
356
+ continuation = true;
357
+ cursor = finish;
358
+ } while (cursor < end);
359
+ };
329
360
  while (yield* source.has(offset)) {
330
361
  pendingWork += 1;
331
362
  if (pendingWork >= 512) {
@@ -431,17 +462,10 @@ export function* parseXmlSourceSteps(length, limits = {}) {
431
462
  }
432
463
  else if ((yield* source.startsWith("<!--", offset))) {
433
464
  const end = yield* find(source, "-->", offset + 4);
434
- const text = end < 0 ? "" : yield* source.slice(offset + 4, end);
435
- if (end < 0 || (yield* find(text, "--", 0)) >= 0 || text.endsWith("-")) {
465
+ if (end < 0 || (yield* source.indexOf("--", offset + 4, end)) >= 0
466
+ || (end > offset + 4 && (yield* source.charCodeAt(end - 1)) === 45))
436
467
  invalid("malformed comment");
437
- }
438
- const parent = (yield* stack.peek());
439
- admitText(text);
440
- admitContent();
441
- if (retain) {
442
- (parent?.content ?? (root ? epilog : prolog)).push({ kind: "comment", text });
443
- }
444
- limits.events?.({ type: "content", content: { kind: "comment", text }, parent: parent?.element });
468
+ yield* rawContent("comment", offset + 4, end, yield* stack.peek());
445
469
  offset = end + 3;
446
470
  }
447
471
  else if ((yield* source.startsWith("<![CDATA[", offset))) {
@@ -450,32 +474,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
450
474
  const end = yield* find(source, "]]>", offset + 9);
451
475
  if (end < 0)
452
476
  invalid("unterminated CDATA");
453
- if (end - offset - 9 > maxTextLength - textLength)
454
- throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
455
- const parent = (yield* stack.peek());
456
- admitContent();
457
- let cursor = offset + 9, continuation = false;
458
- do {
459
- let finish = limits.fragmentContent ? Math.min(end, cursor + 512) : end;
460
- if (finish < end) {
461
- const last = yield* source.charCodeAt(finish - 1);
462
- if (last >= 0xd800 && last <= 0xdbff)
463
- finish--;
464
- }
465
- const text = yield* source.slice(cursor, finish);
466
- admitText(text);
467
- if (retainTree)
468
- parent.element.text += text;
469
- if (retain)
470
- parent.content.push({ kind: "cdata", text });
471
- limits.events?.({ type: "content", content: { kind: "cdata", text }, parent: parent.element,
472
- ...(continuation ? { continuation: true } : {}) });
473
- // Let external consumers persist each fragment before producing another.
474
- if (limits.fragmentContent)
475
- yield Math.max(1, text.length);
476
- continuation = true;
477
- cursor = finish;
478
- } while (cursor < end);
477
+ yield* rawContent("cdata", offset + 9, end, yield* stack.peek());
479
478
  offset = end + 3;
480
479
  }
481
480
  else if ((yield* source.startsWith("<?", offset))) {
@@ -489,34 +488,32 @@ export function* parseXmlSourceSteps(length, limits = {}) {
489
488
  const end = yield* find(source, "?>", offset);
490
489
  if (end < 0)
491
490
  invalid("unterminated processing instruction");
492
- const content = (yield* source.slice(offset, end));
493
491
  if (target.length === 3 && target.toLowerCase() === "xml") {
494
- if (start !== 0 || target !== "xml"
495
- || !(yield* validDeclaration(content, limits.expectedEncoding))) {
492
+ if (start !== 0 || target !== "xml")
493
+ invalid("unsupported XML declaration");
494
+ const parsed = yield* readDeclaration(source, offset, end, limits.expectedEncoding);
495
+ if (parsed === undefined)
496
496
  invalid("unsupported XML declaration");
497
- }
498
497
  if (retainContent)
499
- declaration = (yield* source.slice(start, end + 2));
498
+ declaration = limits.compactDeclaration ? parsed : yield* source.slice(start, end + 2);
500
499
  }
501
- else if (content && !" \t\n\r".includes(content[0]))
502
- invalid("invalid processing instruction");
503
- if (!(target.length === 3 && target.toLowerCase() === "xml")) {
504
- const parent = (yield* stack.peek());
505
- let wsStart = 0;
506
- while (wsStart < content.length && " \t\n\r".includes(content[wsStart]))
507
- wsStart++;
508
- pendingWork += wsStart;
500
+ else {
501
+ if (offset < end && !" \t\n\r".includes(yield* source.slice(offset, offset + 1)))
502
+ invalid("invalid processing instruction");
503
+ let body = offset;
504
+ while (body < end && " \t\n\r".includes(yield* source.slice(body, body + 1))) {
505
+ body++;
506
+ pendingWork++;
507
+ if (pendingWork >= 512) {
508
+ yield 512;
509
+ pendingWork -= 512;
510
+ }
511
+ }
509
512
  while (pendingWork >= 512) {
510
513
  yield 512;
511
514
  pendingWork -= 512;
512
515
  }
513
- const text = content.slice(wsStart);
514
- admitText(text);
515
- admitContent();
516
- if (retain) {
517
- (parent?.content ?? (root ? epilog : prolog)).push({ kind: "processing-instruction", target, text });
518
- }
519
- limits.events?.({ type: "content", content: { kind: "processing-instruction", target, text }, parent: parent?.element });
516
+ yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target);
520
517
  }
521
518
  offset = end + 2;
522
519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-fs",
3
- "version": "0.1.813",
3
+ "version": "0.1.815",
4
4
  "description": "Composable filesystem with a portable core and explicit Node adapters",
5
5
  "type": "module",
6
6
  "license": "MIT",