toolcraft 0.0.799 → 0.0.800

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 CHANGED
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.799",
141
+ "version": "0.0.800",
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.799",
151
+ "version": "0.0.800",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.799",
141
+ "version": "0.0.800",
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.799",
151
+ "version": "0.0.800",
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, XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest, XmlAttributeRequest, XmlAttributeRecord } from "./xml.js";
12
+ export type { XmlName, XmlAttribute, XmlContent, XmlElement, XmlLimits, XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest, XmlSourceRead, XmlSourceSpan, XmlAttributeRequest, XmlAttributeRecord } 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";
@@ -64,31 +64,48 @@ size cost. The default string and stream APIs retain their existing ancestry beh
64
64
 
65
65
  Also set `storeNamespaces: true` to resolve namespace scopes through host-backed
66
66
  immutable storage. `namespaceOperation: "get"` supplies a scope and prefix; fill
67
- `value` when present and set `complete: true`. For `"set"`, bind the supplied prefix
67
+ `value` when present and set `complete: true`. For `"has"`, fill `found` using
68
+ membership without reading the URI value. For `"set"`, bind the supplied prefix
68
69
  and value in a new scope, then fill `result` with its storage `reference` and the
69
70
  number of distinct bindings (`size`). Frames carry this scope handle so shadowing
70
71
  never changes ancestor scopes. In this mode event elements have an empty
71
72
  `namespaces` map; their names and attributes still carry resolved namespace URIs.
72
- The codec remains filesystem-independent, and individual names, attribute values
73
- and namespace URIs still need a token storage strategy.
73
+ The codec remains filesystem-independent. With `deferNamespaces: true`, resolved
74
+ URI metadata uses optional `namespaceReference` handles instead of complete strings.
75
+ Service `namespaceOperation: "reference"` by supplying the token `reference` when
76
+ present and setting `complete: true`. These handles belong to the host scope store;
77
+ retain or stream them before retiring that store. Built-in xml/xmlns names keep
78
+ their short namespace strings. Individual names still retain their token cost;
79
+ attribute values can use the fragment protocol below.
74
80
 
75
81
  Set `storeAttributes: true` to move current-tag attribute values, source order,
76
82
  and duplicate-name state behind `attributeOperation` requests (`has`, `append`,
77
83
  `read`, and `expanded`). Service the request's `found` or `result` field as described
78
- by `XmlAttributeRequest`, keeping records in caller-owned backing. With this flag,
84
+ by `XmlAttributeRequest`, keeping records in caller-owned backing. When namespace
85
+ storage is also enabled, `expanded` supplies the local name and a `namespace` scope
86
+ and prefix. Check the pair of resolved URI and local name without joining them in
87
+ memory; an empty prefix means no attribute namespace, regardless of the default
88
+ element namespace. With this flag,
79
89
  element `attributes` arrays are empty. Supply `onAttribute(attribute, element)` to
80
90
  receive resolved attributes in source order, after the open event and before any
81
91
  content or close event. The parser yields between attributes so a host can await
82
92
  storage or a consumer. Namespace declarations may follow prefixed attributes;
83
93
  resolution and duplicate-expanded-name validation still happen after the complete
84
- start tag. With `fragmentAttributes: true`, ordinary values are validated and
85
- stored as source spans on attribute records, then replayed in at most 512 UTF-16
94
+ start tag. With `fragmentAttributes: true`, ordinary values are validated through source spans; all attribute
95
+ values are stored as spans on attribute records and replayed in at most 512 UTF-16
86
96
  units. Hosts must preserve the optional `source` field in append/read requests.
87
97
  The third `onAttribute` argument is `true` for continuations of the same logical
88
98
  attribute; await each fragment before requesting the next step. Empty attributes
89
99
  still emit once. Entity diagnostics are emitted during validation, not replay.
90
- This mode requires `storeAttributes: true`. Namespace declaration values and
91
- individual names still retain their token cost.
100
+ This mode requires `storeAttributes: true`. Combining it with `storeNamespaces`
101
+ also validates namespace declarations in bounded fragments. A namespace `set`
102
+ request's `value` may then be a generator: strings are decoded value fragments,
103
+ numbers are work checkpoints, and objects are source-read requests. Service each
104
+ step before advancing, consume the complete value before filling `result`, and
105
+ close the producer if storage fails. Reserved URI checks preserve their diagnostic
106
+ order without joining fragments. Expanded-name events return complete URI strings
107
+ unless deferred namespace metadata is enabled; individual names still retain
108
+ their token cost.
92
109
 
93
110
  Pass `undefined` as the source length to parse incrementally. Such read requests
94
111
  have `streaming: true` and request at most 512 UTF-16 units. Return any nonempty
@@ -107,4 +124,12 @@ decoding replay bounded source windows before publishing a logical node, retaini
107
124
  its diagnostic and limit ordering. Numeric entity references are decoded without
108
125
  materializing the token. Set `compactDeclaration: true` to retain normalized declaration fields without
109
126
  copying long whitespace spans; validation always reads bounded windows. The
110
- buffered API keeps the original declaration spelling. Names and namespace declaration values still require their own memory budget.
127
+ buffered API keeps the original declaration spelling.
128
+
129
+ Combine `fragmentContent` with `deferContentNames: true` to validate long
130
+ processing-instruction targets through source windows. Their events have an empty
131
+ `target` and a `targetSource` span; replay that span before retiring the source.
132
+ All body continuations carry the same target span. Short targets remain strings.
133
+ The stored document engine transfers these spans to its own token store and
134
+ streams targets during output. Element and attribute names still require their
135
+ own memory budget.
@@ -1,3 +1,4 @@
1
+ import type { XmlNamespaceScope } from "./frames.js";
1
2
  export interface XmlAttributeRecord {
2
3
  name: string;
3
4
  prefix: string;
@@ -20,6 +21,10 @@ export type XmlAttributeRequest = {
20
21
  state: XmlAttributeState;
21
22
  name: string;
22
23
  found?: boolean;
24
+ namespace?: {
25
+ scope: XmlNamespaceScope;
26
+ prefix: string;
27
+ };
23
28
  } | {
24
29
  attributeOperation: 'append';
25
30
  state: XmlAttributeState;
@@ -50,5 +55,8 @@ export declare class XmlAttributes {
50
55
  end: number;
51
56
  }): Generator<XmlAttributeRequest, void, void>;
52
57
  read(reference: number, values: boolean): Generator<XmlAttributeRequest, XmlAttributeRecord, void>;
53
- expanded(name: string): Generator<XmlAttributeRequest, boolean, void>;
58
+ expanded(name: string, namespace?: {
59
+ scope: XmlNamespaceScope;
60
+ prefix: string;
61
+ }): Generator<XmlAttributeRequest, boolean, void>;
54
62
  }
@@ -47,13 +47,13 @@ export class XmlAttributes {
47
47
  throw new TypeError('Incomplete XML attribute read');
48
48
  return request.result;
49
49
  }
50
- *expanded(name) {
50
+ *expanded(name, namespace) {
51
51
  if (!this.external) {
52
52
  const found = this.expandedNames.has(name);
53
53
  this.expandedNames.add(name);
54
54
  return found;
55
55
  }
56
- const request = { attributeOperation: 'expanded', state: this.state, name };
56
+ const request = { attributeOperation: 'expanded', state: this.state, name, ...(namespace ? { namespace } : {}) };
57
57
  yield request;
58
58
  if (request.found === undefined)
59
59
  throw new TypeError('Incomplete XML expanded attribute lookup');
@@ -1,4 +1,5 @@
1
- import type { XmlContent, XmlElement } from './index.js';
1
+ import type { XmlSourceStep } from './source.js';
2
+ import type { XmlContent, XmlElement, XmlName } from './index.js';
2
3
  export interface XmlParserFrame {
3
4
  element: XmlElement;
4
5
  content: XmlContent[] | undefined;
@@ -33,6 +34,17 @@ export interface XmlNamespaceScope {
33
34
  readonly size: number;
34
35
  }
35
36
  export type XmlNamespaceRequest = {
37
+ readonly namespaceOperation: 'reference';
38
+ readonly scope: XmlNamespaceScope;
39
+ readonly prefix: string;
40
+ reference?: number;
41
+ complete?: true;
42
+ } | {
43
+ readonly namespaceOperation: 'has';
44
+ readonly scope: XmlNamespaceScope;
45
+ readonly prefix: string;
46
+ found?: boolean;
47
+ } | {
36
48
  readonly namespaceOperation: 'get';
37
49
  readonly scope: XmlNamespaceScope;
38
50
  readonly prefix: string;
@@ -42,8 +54,10 @@ export type XmlNamespaceRequest = {
42
54
  readonly namespaceOperation: 'set';
43
55
  readonly scope: XmlNamespaceScope;
44
56
  readonly prefix: string;
45
- readonly value: string;
57
+ readonly value: string | Generator<XmlSourceStep | string, void, void>;
46
58
  result?: XmlNamespaceScope;
47
59
  };
48
60
  export declare function namespaceValue(scope: Map<string, string> | XmlNamespaceScope, prefix: string): Generator<XmlNamespaceRequest, string | undefined, void>;
49
- export declare function bindNamespace(scope: Map<string, string> | XmlNamespaceScope, prefix: string, value: string): Generator<XmlNamespaceRequest, Map<string, string> | XmlNamespaceScope, void>;
61
+ export declare function namespaceMetadata(scope: Map<string, string> | XmlNamespaceScope, prefix: string, deferred?: boolean): Generator<XmlNamespaceRequest, Pick<XmlName, 'namespace' | 'namespaceReference'>, void>;
62
+ export declare function hasNamespace(scope: Map<string, string> | XmlNamespaceScope, prefix: string): Generator<XmlNamespaceRequest, boolean, void>;
63
+ export declare function bindNamespace(scope: Map<string, string> | XmlNamespaceScope, prefix: string, value: string | Generator<XmlSourceStep | string, void, void>): Generator<XmlNamespaceRequest, Map<string, string> | XmlNamespaceScope, void>;
@@ -52,8 +52,30 @@ export function* namespaceValue(scope, prefix) {
52
52
  throw new TypeError('Incomplete XML namespace read');
53
53
  return request.value;
54
54
  }
55
+ export function* namespaceMetadata(scope, prefix, deferred = false) {
56
+ if (!deferred || scope instanceof Map)
57
+ return { namespace: (yield* namespaceValue(scope, prefix)) ?? '' };
58
+ if (prefix === 'xml')
59
+ return { namespace: 'http://www.w3.org/XML/1998/namespace' };
60
+ const request = { namespaceOperation: 'reference', scope, prefix };
61
+ yield request;
62
+ if (!request.complete)
63
+ throw new TypeError('Incomplete XML namespace reference lookup');
64
+ return { namespace: '', ...(request.reference === undefined ? {} : { namespaceReference: request.reference }) };
65
+ }
66
+ export function* hasNamespace(scope, prefix) {
67
+ if (scope instanceof Map)
68
+ return scope.has(prefix);
69
+ const request = { namespaceOperation: 'has', scope, prefix };
70
+ yield request;
71
+ if (request.found === undefined)
72
+ throw new TypeError('Incomplete XML namespace membership lookup');
73
+ return request.found;
74
+ }
55
75
  export function* bindNamespace(scope, prefix, value) {
56
76
  if (scope instanceof Map) {
77
+ if (typeof value !== 'string')
78
+ throw new TypeError('Streamed XML namespace values require external scopes');
57
79
  scope.set(prefix, value);
58
80
  return scope;
59
81
  }
@@ -3,13 +3,14 @@ export type { XmlAttributeRequest, XmlAttributeRecord, XmlAttributeState } from
3
3
  import { type XmlNamespaceRequest, type XmlFrameRequest } from "./frames.js";
4
4
  export type { XmlParserFrame, XmlFrameRequest, XmlNamespaceScope, XmlNamespaceRequest } from "./frames.js";
5
5
  export { normalizeXmlChunks } from "./characters.js";
6
- import { type XmlSourceStep } from "./source.js";
7
- export type { XmlSourceRead, XmlSourceStep } from "./source.js";
6
+ import { type XmlSourceSpan, type XmlSourceStep } from "./source.js";
7
+ export type { XmlSourceRead, XmlSourceSpan, XmlSourceStep } from "./source.js";
8
8
  import type { XmlStreamEvent } from "./stream.js";
9
9
  export { XmlLimitError } from "./errors.js";
10
10
  export interface XmlName {
11
11
  readonly name: string;
12
12
  readonly namespace: string;
13
+ readonly namespaceReference?: number;
13
14
  readonly localName: string;
14
15
  }
15
16
  export interface XmlAttribute extends XmlName {
@@ -21,6 +22,8 @@ export type XmlContent = XmlElement | {
21
22
  } | {
22
23
  readonly kind: "processing-instruction";
23
24
  readonly target: string;
25
+ readonly targetSource?: XmlSourceSpan;
26
+ readonly targetReference?: number;
24
27
  readonly text: string;
25
28
  };
26
29
  export interface XmlElement extends XmlName {
@@ -65,6 +68,10 @@ export interface XmlSourceLimits extends XmlStepLimits {
65
68
  readonly storeFrames?: boolean;
66
69
  /** Resolve namespace scopes through host-serviced requests. Requires retainTree: false. */
67
70
  readonly storeNamespaces?: boolean;
71
+ /** Carry host namespace token references in event metadata. Requires stored namespaces. */
72
+ readonly deferNamespaces?: boolean;
73
+ /** Emit long processing-instruction targets as source spans. Requires fragmentContent. */
74
+ readonly deferContentNames?: boolean;
68
75
  /** Store attribute collections through host requests and emit resolved attributes individually. */
69
76
  readonly storeAttributes?: boolean;
70
77
  readonly fragmentAttributes?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { XmlAttributes } from "./attributes.js";
2
- import { XmlFrames, bindNamespace, namespaceValue } from "./frames.js";
2
+ import { XmlFrames, bindNamespace, namespaceValue, namespaceMetadata, hasNamespace } from "./frames.js";
3
3
  import { validCharacter } from "./characters.js";
4
4
  export { normalizeXmlChunks } from "./characters.js";
5
5
  import { XmlSource } from "./source.js";
@@ -68,6 +68,40 @@ function qualifiedNameSync(name, cache) {
68
68
  }
69
69
  return parts;
70
70
  }
71
+ /** Validate a name span without retaining its spelling. */
72
+ function* validateNameSpan(source, start, end) {
73
+ let first = true, colon = false, work = 0;
74
+ for (let offset = start; offset < end;) {
75
+ let point = yield* source.charCodeAt(offset);
76
+ if (point >= 0xd800 && point <= 0xdbff && offset + 1 < end) {
77
+ const low = yield* source.charCodeAt(offset + 1);
78
+ if (low >= 0xdc00 && low <= 0xdfff)
79
+ point = 0x10000 + (point - 0xd800) * 1024 + low - 0xdc00;
80
+ }
81
+ if (point === 58) {
82
+ if (colon || first)
83
+ invalid("invalid qualified name");
84
+ colon = true;
85
+ first = true;
86
+ }
87
+ else {
88
+ if (!(first ? nameStart(point) : namePart(point)))
89
+ invalid("invalid qualified name");
90
+ first = false;
91
+ }
92
+ const size = point > 0xffff ? 2 : 1;
93
+ offset += size;
94
+ work += size;
95
+ if (work >= 512) {
96
+ yield 512;
97
+ work -= 512;
98
+ }
99
+ }
100
+ if (first)
101
+ invalid("invalid qualified name");
102
+ if (work)
103
+ yield work;
104
+ }
71
105
  function* entity(source, start, end, recover) {
72
106
  // Inspect only a bounded prefix, even for arbitrarily long numeric references.
73
107
  const prefix = typeof source === "string" ? source.slice(start, Math.min(end, start + 4))
@@ -260,12 +294,16 @@ function validateLimits(limits) {
260
294
  }
261
295
  export function* parseXmlSourceSteps(length, limits = {}) {
262
296
  validateLimits(limits);
297
+ if (limits.deferContentNames && !limits.fragmentContent)
298
+ throw new TypeError("Deferred XML content names require fragmentContent: true");
263
299
  if (limits.fragmentAttributes && !limits.storeAttributes)
264
300
  throw new TypeError("XML attribute fragments require storeAttributes: true");
265
301
  if (limits.storeFrames && limits.retainTree !== false)
266
302
  throw new TypeError("Stored XML frames require retainTree: false");
267
303
  if (limits.storeAttributes && limits.retainTree !== false)
268
304
  throw new TypeError("Stored XML attributes require retainTree: false");
305
+ if (limits.deferNamespaces && !limits.storeNamespaces)
306
+ throw new TypeError("Deferred XML namespaces require stored namespaces");
269
307
  if (limits.storeNamespaces && limits.retainTree !== false)
270
308
  throw new TypeError("Stored XML namespaces require retainTree: false");
271
309
  if (limits.fragmentContent && limits.retainTree !== false)
@@ -322,7 +360,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
322
360
  }
323
361
  return offset - start;
324
362
  };
325
- const scanName = function* () {
363
+ const scanName = function* (defer = false) {
326
364
  const start = offset;
327
365
  while (yield* source.has(offset)) {
328
366
  const c = (yield* source.charCodeAt(offset));
@@ -335,6 +373,10 @@ export function* parseXmlSourceSteps(length, limits = {}) {
335
373
  pendingWork -= 512;
336
374
  }
337
375
  }
376
+ if (defer && offset - start > 512) {
377
+ yield* validateNameSpan(source, start, offset);
378
+ return ["", "", "", { start, end: offset }];
379
+ }
338
380
  const name = (yield* source.slice(start, offset));
339
381
  pendingWork += offset - start;
340
382
  while (pendingWork >= 512) {
@@ -344,7 +386,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
344
386
  const [prefix, localName] = qualifiedNameSync(name, qualifiedNames);
345
387
  return [name, prefix, localName];
346
388
  };
347
- const rawContent = function* (kind, start, end, parent, target = "") {
389
+ const rawContent = function* (kind, start, end, parent, target = "", targetSource) {
348
390
  if (end - start > maxTextLength - textLength)
349
391
  throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
350
392
  textLength += end - start;
@@ -358,7 +400,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
358
400
  finish--;
359
401
  }
360
402
  const text = yield* source.slice(cursor, finish);
361
- const content = kind === "processing-instruction" ? { kind, target, text } : { kind, text };
403
+ const content = kind === "processing-instruction" ? { kind, target, text, ...(targetSource ? { targetSource } : {}) } : { kind, text };
362
404
  if (kind === "cdata" && retainTree)
363
405
  parent.element.text += text;
364
406
  if (retain)
@@ -494,7 +536,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
494
536
  else if ((yield* source.startsWith("<?", offset))) {
495
537
  const start = offset;
496
538
  offset += 2;
497
- const [target] = yield* scanName();
539
+ const [target, , , targetSource] = yield* scanName(limits.deferContentNames);
498
540
  while (pendingWork >= 512) {
499
541
  yield 512;
500
542
  pendingWork -= 512;
@@ -527,7 +569,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
527
569
  yield 512;
528
570
  pendingWork -= 512;
529
571
  }
530
- yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target);
572
+ yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target, targetSource);
531
573
  }
532
574
  offset = end + 2;
533
575
  }
@@ -608,18 +650,23 @@ export function* parseXmlSourceSteps(length, limits = {}) {
608
650
  }
609
651
  if (end < 0)
610
652
  invalid("unterminated attribute");
611
- const fragmented = limits.fragmentAttributes && attribute !== "xmlns" && attrPrefix !== "xmlns";
612
- let value = "";
653
+ const fragmented = limits.fragmentAttributes && (limits.storeNamespaces || attribute !== "xmlns" && attrPrefix !== "xmlns");
654
+ const valueStart = offset;
655
+ let value = "", size = 0, matchesXml = true, matchesXmlns = true;
613
656
  if (fragmented) {
614
657
  if ((yield* source.indexOf("<", offset, end)) >= 0)
615
658
  invalid("less-than in attribute");
616
- let size = 0;
617
659
  for (const part of textFragments(source, offset, end, true, limits.recover, true)) {
618
660
  if (typeof part !== "string")
619
661
  yield part;
620
- else
662
+ else {
663
+ matchesXml = matchesXml && part === xmlNamespace.slice(size, size + part.length);
664
+ matchesXmlns = matchesXmlns && part === xmlnsNamespace.slice(size, size + part.length);
621
665
  size += part.length;
666
+ }
622
667
  }
668
+ matchesXml = matchesXml && size === xmlNamespace.length;
669
+ matchesXmlns = matchesXmlns && size === xmlnsNamespace.length;
623
670
  if (size > maxTextLength - textLength)
624
671
  throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
625
672
  textLength += size;
@@ -646,13 +693,13 @@ export function* parseXmlSourceSteps(length, limits = {}) {
646
693
  }
647
694
  admitText(value);
648
695
  }
649
- yield* attributes.append(attribute, attrPrefix, attrLocal, value, fragmented ? { start: offset, end } : undefined);
696
+ yield* attributes.append(attribute, attrPrefix, attrLocal, value, limits.fragmentAttributes ? { start: offset, end } : undefined);
650
697
  offset = end + 1;
651
698
  if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
652
699
  const nsPrefix = attribute === "xmlns" ? "" : attribute.slice(6);
653
- if (nsPrefix === "xmlns" || value === xmlnsNamespace
654
- || (nsPrefix === "xml") !== (value === xmlNamespace)
655
- || (nsPrefix !== "" && value === ""))
700
+ if (nsPrefix === "xmlns" || (fragmented ? matchesXmlns : value === xmlnsNamespace)
701
+ || (nsPrefix === "xml") !== (fragmented ? matchesXml : value === xmlNamespace)
702
+ || (nsPrefix !== "" && (fragmented ? size === 0 : value === "")))
656
703
  invalid("invalid namespace binding");
657
704
  if (!ownsNamespaces && namespaces instanceof Map) {
658
705
  const copy = new Map();
@@ -663,7 +710,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
663
710
  namespaces = copy;
664
711
  ownsNamespaces = true;
665
712
  }
666
- namespaces = yield* bindNamespace(namespaces, nsPrefix, value);
713
+ namespaces = yield* bindNamespace(namespaces, nsPrefix, fragmented
714
+ ? textFragments(source, valueStart, end, true, limits.recover ? () => { } : undefined, true) : value);
667
715
  if (namespaces.size > maxNamespaces)
668
716
  throw new XmlLimitError("maxNamespaces", "XML namespace scope limit exceeded");
669
717
  }
@@ -676,11 +724,12 @@ export function* parseXmlSourceSteps(length, limits = {}) {
676
724
  if (attribute === "xmlns" || attribute.startsWith("xmlns:"))
677
725
  continue;
678
726
  pendingWork += attribute.length;
679
- if (attrPrefix && (yield* namespaceValue(namespaces, attrPrefix)) === undefined)
727
+ if (attrPrefix && !(yield* hasNamespace(namespaces, attrPrefix)))
680
728
  invalid("unbound attribute prefix");
681
729
  if (attributes.length > 1) {
682
- const key = `${attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : ""}\0${attrLocal}`;
683
- if (yield* attributes.expanded(key))
730
+ const backed = limits.storeAttributes && !(namespaces instanceof Map) ? namespaces : undefined;
731
+ const key = backed ? attrLocal : `${attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : ""}\0${attrLocal}`;
732
+ if (yield* attributes.expanded(key, backed ? { scope: backed, prefix: attrPrefix } : undefined))
684
733
  invalid("duplicate expanded attribute");
685
734
  }
686
735
  }
@@ -690,14 +739,14 @@ export function* parseXmlSourceSteps(length, limits = {}) {
690
739
  yield 512;
691
740
  pendingWork -= 512;
692
741
  }
693
- if (prefix === "xmlns" || (prefix && (yield* namespaceValue(namespaces, prefix)) === undefined))
742
+ if (prefix === "xmlns" || (prefix && !(yield* hasNamespace(namespaces, prefix))))
694
743
  invalid("unbound element prefix");
695
744
  if (++nodes > maxNodes)
696
745
  throw new XmlLimitError("maxNodes", "XML resource limit exceeded");
697
746
  if (stack.length + 1 > maxDepth)
698
747
  throw new XmlLimitError("maxDepth", "XML resource limit exceeded");
699
- const namespace = (yield* namespaceValue(namespaces, prefix)) ?? "";
700
- limits.onElement?.({ name, namespace, localName }, (yield* stack.peek())?.element, stack.length + 1);
748
+ const namespace = yield* namespaceMetadata(namespaces, prefix, limits.deferNamespaces);
749
+ limits.onElement?.({ name, ...namespace, localName }, (yield* stack.peek())?.element, stack.length + 1);
701
750
  admitContent();
702
751
  const retainedAttributes = [];
703
752
  if (retainContent) {
@@ -717,7 +766,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
717
766
  pendingWork -= 512;
718
767
  }
719
768
  const content = retain ? [] : undefined;
720
- const element = { kind: "element", name, namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent && namespaces instanceof Map ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
769
+ const element = { kind: "element", name, ...namespace, localName, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent && namespaces instanceof Map ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
721
770
  const parent = (yield* stack.peek());
722
771
  if (parent) {
723
772
  if (retainTree) {
@@ -747,8 +796,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
747
796
  for (let cursor = attributes.first; cursor;) {
748
797
  const attribute = yield* attributes.read(cursor, true);
749
798
  cursor = attribute.next;
750
- const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? xmlnsNamespace
751
- : attribute.prefix ? (yield* namespaceValue(namespaces, attribute.prefix)) : "";
799
+ const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? { namespace: xmlnsNamespace }
800
+ : attribute.prefix ? yield* namespaceMetadata(namespaces, attribute.prefix, limits.deferNamespaces) : { namespace: "" };
752
801
  let continuation = false;
753
802
  if (attribute.source) {
754
803
  for (const part of textFragments(source, attribute.source.start, attribute.source.end, true, limits.recover ? () => { } : undefined, true)) {
@@ -756,13 +805,13 @@ export function* parseXmlSourceSteps(length, limits = {}) {
756
805
  yield part;
757
806
  continue;
758
807
  }
759
- limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: part }, element, continuation);
808
+ limits.onAttribute({ name: attribute.name, localName: attribute.localName, ...namespace, value: part }, element, continuation);
760
809
  continuation = true;
761
810
  yield 1;
762
811
  }
763
812
  }
764
813
  if (!continuation) {
765
- limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: attribute.value }, element);
814
+ limits.onAttribute({ name: attribute.name, localName: attribute.localName, ...namespace, value: attribute.value }, element);
766
815
  yield 1;
767
816
  }
768
817
  }
@@ -1,3 +1,7 @@
1
+ export interface XmlSourceSpan {
2
+ readonly start: number;
3
+ readonly end: number;
4
+ }
1
5
  /** Fill value before advancing. Streaming reads may be short; mark complete at EOF. */
2
6
  export interface XmlSourceRead {
3
7
  readonly offset: number;
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.799",
11
+ "version": "0.0.800",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.799",
3
+ "version": "0.0.800",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.799",
3
+ "version": "0.0.800",
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.799",
180
+ "toolcraft-schema": "0.0.800",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",