toolcraft 0.0.798 → 0.0.799

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.798",
141
+ "version": "0.0.799",
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.798",
151
+ "version": "0.0.799",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "name": "toolcraft",
141
- "version": "0.0.798",
141
+ "version": "0.0.799",
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.798",
151
+ "version": "0.0.799",
152
152
  "license": "MIT"
153
153
  },
154
154
  {
@@ -81,7 +81,14 @@ receive resolved attributes in source order, after the open event and before any
81
81
  content or close event. The parser yields between attributes so a host can await
82
82
  storage or a consumer. Namespace declarations may follow prefixed attributes;
83
83
  resolution and duplicate-expanded-name validation still happen after the complete
84
- start tag. Individual attribute names and values retain their token cost.
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
86
+ units. Hosts must preserve the optional `source` field in append/read requests.
87
+ The third `onAttribute` argument is `true` for continuations of the same logical
88
+ attribute; await each fragment before requesting the next step. Empty attributes
89
+ 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.
85
92
 
86
93
  Pass `undefined` as the source length to parse incrementally. Such read requests
87
94
  have `streaming: true` and request at most 512 UTF-16 units. Return any nonempty
@@ -100,5 +107,4 @@ decoding replay bounded source windows before publishing a logical node, retaini
100
107
  its diagnostic and limit ordering. Numeric entity references are decoded without
101
108
  materializing the token. Set `compactDeclaration: true` to retain normalized declaration fields without
102
109
  copying long whitespace spans; validation always reads bounded windows. The
103
- buffered API keeps the original declaration spelling. Names, attribute values
104
- and namespace scopes still require their own memory budget.
110
+ buffered API keeps the original declaration spelling. Names and namespace declaration values still require their own memory budget.
@@ -4,6 +4,10 @@ export interface XmlAttributeRecord {
4
4
  localName: string;
5
5
  value: string;
6
6
  next: number;
7
+ source?: {
8
+ start: number;
9
+ end: number;
10
+ };
7
11
  }
8
12
  export interface XmlAttributeState {
9
13
  reference: number;
@@ -41,7 +45,10 @@ export declare class XmlAttributes {
41
45
  get size(): number;
42
46
  get first(): number;
43
47
  has(name: string): Generator<XmlAttributeRequest, boolean, void>;
44
- append(name: string, prefix: string, localName: string, value: string): Generator<XmlAttributeRequest, void, void>;
48
+ append(name: string, prefix: string, localName: string, value: string, source?: {
49
+ start: number;
50
+ end: number;
51
+ }): Generator<XmlAttributeRequest, void, void>;
45
52
  read(reference: number, values: boolean): Generator<XmlAttributeRequest, XmlAttributeRecord, void>;
46
53
  expanded(name: string): Generator<XmlAttributeRequest, boolean, void>;
47
54
  }
@@ -23,14 +23,14 @@ export class XmlAttributes {
23
23
  throw new TypeError('Incomplete XML attribute lookup');
24
24
  return request.found;
25
25
  }
26
- *append(name, prefix, localName, value) {
26
+ *append(name, prefix, localName, value, source) {
27
27
  if (!this.external) {
28
28
  this.names.set(name, value);
29
29
  this.records.push({ name, prefix, localName });
30
30
  this.state = { reference: 0, first: 1, length: this.records.length, size: this.names.size };
31
31
  return;
32
32
  }
33
- const request = { attributeOperation: 'append', state: this.state, attribute: { name, prefix, localName, value } };
33
+ const request = { attributeOperation: 'append', state: this.state, attribute: { name, prefix, localName, value, ...(source ? { source } : {}) } };
34
34
  yield request;
35
35
  if (!request.result)
36
36
  throw new TypeError('Incomplete XML attribute append');
@@ -67,7 +67,8 @@ export interface XmlSourceLimits extends XmlStepLimits {
67
67
  readonly storeNamespaces?: boolean;
68
68
  /** Store attribute collections through host requests and emit resolved attributes individually. */
69
69
  readonly storeAttributes?: boolean;
70
- readonly onAttribute?: (attribute: XmlAttribute, element: XmlElement) => void;
70
+ readonly fragmentAttributes?: boolean;
71
+ readonly onAttribute?: (attribute: XmlAttribute, element: XmlElement, continuation?: boolean) => void;
71
72
  }
72
73
  export type XmlParseStep = XmlSourceStep | XmlFrameRequest | XmlNamespaceRequest | XmlAttributeRequest;
73
74
  /** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
@@ -102,7 +102,7 @@ function* entity(source, start, end, recover) {
102
102
  return String.fromCodePoint(point);
103
103
  }
104
104
  /** Decoded text fragments; source read/work requests remain host-serviced. */
105
- function* textParts(source, start, end, decode, recover) {
105
+ function* textParts(source, start, end, decode, recover, attribute = false) {
106
106
  let cursor = start;
107
107
  while (cursor < end) {
108
108
  let finish = Math.min(end, cursor + 512);
@@ -111,7 +111,13 @@ function* textParts(source, start, end, decode, recover) {
111
111
  if (last >= 0xd800 && last <= 0xdbff)
112
112
  finish--;
113
113
  }
114
- const raw = yield* source.slice(cursor, finish);
114
+ let raw = yield* source.slice(cursor, finish);
115
+ if (attribute) {
116
+ let normalized = "";
117
+ for (const character of raw)
118
+ normalized += "\t\n\r".includes(character) ? " " : character;
119
+ raw = normalized;
120
+ }
115
121
  const found = decode ? raw.indexOf("&") : -1;
116
122
  if (found < 0) {
117
123
  yield raw;
@@ -140,9 +146,9 @@ function* textParts(source, start, end, decode, recover) {
140
146
  cursor = semicolon + 1;
141
147
  }
142
148
  }
143
- function* textFragments(source, start, end, decode, recover) {
149
+ function* textFragments(source, start, end, decode, recover, attribute = false) {
144
150
  let pending = "";
145
- for (const part of textParts(source, start, end, decode, recover)) {
151
+ for (const part of textParts(source, start, end, decode, recover, attribute)) {
146
152
  if (typeof part !== "string") {
147
153
  yield part;
148
154
  continue;
@@ -254,6 +260,8 @@ function validateLimits(limits) {
254
260
  }
255
261
  export function* parseXmlSourceSteps(length, limits = {}) {
256
262
  validateLimits(limits);
263
+ if (limits.fragmentAttributes && !limits.storeAttributes)
264
+ throw new TypeError("XML attribute fragments require storeAttributes: true");
257
265
  if (limits.storeFrames && limits.retainTree !== false)
258
266
  throw new TypeError("Stored XML frames require retainTree: false");
259
267
  if (limits.storeAttributes && limits.retainTree !== false)
@@ -600,22 +608,45 @@ export function* parseXmlSourceSteps(length, limits = {}) {
600
608
  }
601
609
  if (end < 0)
602
610
  invalid("unterminated attribute");
603
- const raw = (yield* source.slice(offset, end));
604
- pendingWork += raw.length * 2;
605
- while (pendingWork >= 512) {
606
- yield 512;
607
- pendingWork -= 512;
611
+ const fragmented = limits.fragmentAttributes && attribute !== "xmlns" && attrPrefix !== "xmlns";
612
+ let value = "";
613
+ if (fragmented) {
614
+ if ((yield* source.indexOf("<", offset, end)) >= 0)
615
+ invalid("less-than in attribute");
616
+ let size = 0;
617
+ for (const part of textFragments(source, offset, end, true, limits.recover, true)) {
618
+ if (typeof part !== "string")
619
+ yield part;
620
+ else
621
+ size += part.length;
622
+ }
623
+ if (size > maxTextLength - textLength)
624
+ throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
625
+ textLength += size;
608
626
  }
609
- if (raw.indexOf("<") >= 0)
610
- invalid("less-than in attribute");
611
- const normalized = /[\t\n\r]/.test(raw) ? raw.replace(/[\t\n\r]/g, " ") : raw;
612
- const value = normalized.indexOf("&") < 0 ? (pendingWork += normalized.length, normalized) : yield* entities(normalized, limits.recover);
613
- while (pendingWork >= 512) {
614
- yield 512;
615
- pendingWork -= 512;
627
+ else {
628
+ const raw = (yield* source.slice(offset, end));
629
+ pendingWork += raw.length * 2;
630
+ while (pendingWork >= 512) {
631
+ yield 512;
632
+ pendingWork -= 512;
633
+ }
634
+ if (raw.indexOf("<") >= 0)
635
+ invalid("less-than in attribute");
636
+ let normalized = raw;
637
+ if (raw.includes("\t") || raw.includes("\n") || raw.includes("\r")) {
638
+ normalized = "";
639
+ for (const character of raw)
640
+ normalized += "\t\n\r".includes(character) ? " " : character;
641
+ }
642
+ value = normalized.indexOf("&") < 0 ? (pendingWork += normalized.length, normalized) : yield* entities(normalized, limits.recover);
643
+ while (pendingWork >= 512) {
644
+ yield 512;
645
+ pendingWork -= 512;
646
+ }
647
+ admitText(value);
616
648
  }
617
- admitText(value);
618
- yield* attributes.append(attribute, attrPrefix, attrLocal, value);
649
+ yield* attributes.append(attribute, attrPrefix, attrLocal, value, fragmented ? { start: offset, end } : undefined);
619
650
  offset = end + 1;
620
651
  if (attribute === "xmlns" || attribute.startsWith("xmlns:")) {
621
652
  const nsPrefix = attribute === "xmlns" ? "" : attribute.slice(6);
@@ -718,8 +749,22 @@ export function* parseXmlSourceSteps(length, limits = {}) {
718
749
  cursor = attribute.next;
719
750
  const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? xmlnsNamespace
720
751
  : attribute.prefix ? (yield* namespaceValue(namespaces, attribute.prefix)) : "";
721
- limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: attribute.value }, element);
722
- yield 1;
752
+ let continuation = false;
753
+ if (attribute.source) {
754
+ for (const part of textFragments(source, attribute.source.start, attribute.source.end, true, limits.recover ? () => { } : undefined, true)) {
755
+ if (typeof part !== "string") {
756
+ yield part;
757
+ continue;
758
+ }
759
+ limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: part }, element, continuation);
760
+ continuation = true;
761
+ yield 1;
762
+ }
763
+ }
764
+ if (!continuation) {
765
+ limits.onAttribute({ name: attribute.name, localName: attribute.localName, namespace, value: attribute.value }, element);
766
+ yield 1;
767
+ }
723
768
  }
724
769
  }
725
770
  if (!empty)
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.798",
11
+ "version": "0.0.799",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.798",
3
+ "version": "0.0.799",
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.798",
3
+ "version": "0.0.799",
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.798",
180
+ "toolcraft-schema": "0.0.799",
181
181
  "toolcraft-design": "*",
182
182
  "@poe-code/frontmatter": "*",
183
183
  "@poe-code/agent-mcp-config": "*",