@poe-platform/safe-fs 0.1.818 → 0.1.819
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/README.md +1 -1
- package/dist/safe-fs/contracts/filesystem.d.ts +9 -1
- package/dist/safe-fs/core.d.ts +1 -1
- package/dist/safe-fs/fs/capabilities.js +7 -4
- package/dist/safe-fs/fs/devices/index.d.ts +3 -1
- package/dist/safe-fs/fs/devices/index.js +7 -0
- package/dist/safe-fs/fs/devices/path.js +6 -0
- package/dist/safe-fs/fs/devices/plain-path.d.ts +2 -0
- package/dist/safe-fs/fs/devices/plain-path.js +3 -0
- package/dist/safe-fs/fs/memory/index.d.ts +2 -1
- package/dist/safe-fs/fs/memory/index.js +3 -0
- package/dist/safe-fs/fs/mount/index.d.ts +4 -1
- package/dist/safe-fs/fs/mount/index.js +56 -25
- package/dist/safe-fs/fs/overlay/index.d.ts +2 -1
- package/dist/safe-fs/fs/overlay/index.js +2 -0
- package/dist/safe-fs/fs/real/index.d.ts +3 -0
- package/dist/safe-fs/fs/real/index.js +110 -5
- package/dist/safe-fs/fs/scoped.js +2 -2
- package/dist/xml-ast/attributes.d.ts +25 -3
- package/dist/xml-ast/attributes.js +6 -6
- package/dist/xml-ast/frames.d.ts +22 -4
- package/dist/xml-ast/frames.js +26 -4
- package/dist/xml-ast/index.d.ts +22 -3
- package/dist/xml-ast/index.js +179 -49
- package/dist/xml-ast/source.d.ts +4 -0
- package/package.json +1 -1
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
import type { XmlSourceSpan } from "./source.js";
|
|
2
|
+
import type { XmlNamespaceScope } from "./frames.js";
|
|
1
3
|
export interface XmlAttributeRecord {
|
|
2
4
|
name: string;
|
|
3
5
|
prefix: string;
|
|
4
6
|
localName: string;
|
|
5
7
|
value: string;
|
|
6
8
|
next: number;
|
|
9
|
+
nameSource?: XmlSourceSpan;
|
|
10
|
+
localNameSource?: XmlSourceSpan;
|
|
11
|
+
prefixSource?: XmlSourceSpan;
|
|
12
|
+
source?: {
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
};
|
|
7
16
|
}
|
|
8
17
|
export interface XmlAttributeState {
|
|
9
18
|
reference: number;
|
|
@@ -15,7 +24,13 @@ export type XmlAttributeRequest = {
|
|
|
15
24
|
attributeOperation: 'has' | 'expanded';
|
|
16
25
|
state: XmlAttributeState;
|
|
17
26
|
name: string;
|
|
27
|
+
nameSource?: XmlSourceSpan;
|
|
18
28
|
found?: boolean;
|
|
29
|
+
namespace?: {
|
|
30
|
+
scope: XmlNamespaceScope;
|
|
31
|
+
prefix: string;
|
|
32
|
+
prefixSource?: XmlSourceSpan;
|
|
33
|
+
};
|
|
19
34
|
} | {
|
|
20
35
|
attributeOperation: 'append';
|
|
21
36
|
state: XmlAttributeState;
|
|
@@ -40,8 +55,15 @@ export declare class XmlAttributes {
|
|
|
40
55
|
get length(): number;
|
|
41
56
|
get size(): number;
|
|
42
57
|
get first(): number;
|
|
43
|
-
has(name: string): Generator<XmlAttributeRequest, boolean, void>;
|
|
44
|
-
append(name: string, prefix: string, localName: string, value: string
|
|
58
|
+
has(name: string, nameSource?: XmlSourceSpan): Generator<XmlAttributeRequest, boolean, void>;
|
|
59
|
+
append(name: string, prefix: string, localName: string, value: string, source?: {
|
|
60
|
+
start: number;
|
|
61
|
+
end: number;
|
|
62
|
+
}, nameSource?: XmlSourceSpan, localNameSource?: XmlSourceSpan, prefixSource?: XmlSourceSpan): Generator<XmlAttributeRequest, void, void>;
|
|
45
63
|
read(reference: number, values: boolean): Generator<XmlAttributeRequest, XmlAttributeRecord, void>;
|
|
46
|
-
expanded(name: string
|
|
64
|
+
expanded(name: string, namespace?: {
|
|
65
|
+
scope: XmlNamespaceScope;
|
|
66
|
+
prefix: string;
|
|
67
|
+
prefixSource?: XmlSourceSpan;
|
|
68
|
+
}, nameSource?: XmlSourceSpan): Generator<XmlAttributeRequest, boolean, void>;
|
|
47
69
|
}
|
|
@@ -12,25 +12,25 @@ export class XmlAttributes {
|
|
|
12
12
|
get length() { return this.state.length; }
|
|
13
13
|
get size() { return this.state.size; }
|
|
14
14
|
get first() { return this.state.first; }
|
|
15
|
-
*has(name) {
|
|
15
|
+
*has(name, nameSource) {
|
|
16
16
|
if (!this.length)
|
|
17
17
|
return false;
|
|
18
18
|
if (!this.external)
|
|
19
19
|
return this.names.has(name);
|
|
20
|
-
const request = { attributeOperation: 'has', state: this.state, name };
|
|
20
|
+
const request = { attributeOperation: 'has', state: this.state, name, ...(nameSource ? { nameSource } : {}) };
|
|
21
21
|
yield request;
|
|
22
22
|
if (request.found === undefined)
|
|
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, nameSource, localNameSource, prefixSource) {
|
|
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 } : {}), ...(nameSource ? { nameSource } : {}), ...(localNameSource ? { localNameSource } : {}), ...(prefixSource ? { prefixSource } : {}) } };
|
|
34
34
|
yield request;
|
|
35
35
|
if (!request.result)
|
|
36
36
|
throw new TypeError('Incomplete XML attribute append');
|
|
@@ -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, nameSource) {
|
|
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 } : {}), ...(nameSource ? { nameSource } : {}) };
|
|
57
57
|
yield request;
|
|
58
58
|
if (request.found === undefined)
|
|
59
59
|
throw new TypeError('Incomplete XML expanded attribute lookup');
|
package/dist/xml-ast/frames.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { XmlSourceStep, XmlSourceSpan } 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,17 +34,34 @@ 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
|
+
readonly prefixSource?: XmlSourceSpan;
|
|
41
|
+
reference?: number;
|
|
42
|
+
complete?: true;
|
|
43
|
+
} | {
|
|
44
|
+
readonly namespaceOperation: 'has';
|
|
45
|
+
readonly scope: XmlNamespaceScope;
|
|
46
|
+
readonly prefix: string;
|
|
47
|
+
readonly prefixSource?: XmlSourceSpan;
|
|
48
|
+
found?: boolean;
|
|
49
|
+
} | {
|
|
36
50
|
readonly namespaceOperation: 'get';
|
|
37
51
|
readonly scope: XmlNamespaceScope;
|
|
38
52
|
readonly prefix: string;
|
|
53
|
+
readonly prefixSource?: XmlSourceSpan;
|
|
39
54
|
value?: string;
|
|
40
55
|
complete?: true;
|
|
41
56
|
} | {
|
|
42
57
|
readonly namespaceOperation: 'set';
|
|
43
58
|
readonly scope: XmlNamespaceScope;
|
|
44
59
|
readonly prefix: string;
|
|
45
|
-
readonly
|
|
60
|
+
readonly prefixSource?: XmlSourceSpan;
|
|
61
|
+
readonly value: string | Generator<XmlSourceStep | string, void, void>;
|
|
46
62
|
result?: XmlNamespaceScope;
|
|
47
63
|
};
|
|
48
|
-
export declare function namespaceValue(scope: Map<string, string> | XmlNamespaceScope, prefix: string): Generator<XmlNamespaceRequest, string | undefined, void>;
|
|
49
|
-
export declare function
|
|
64
|
+
export declare function namespaceValue(scope: Map<string, string> | XmlNamespaceScope, prefix: string, prefixSource?: XmlSourceSpan): Generator<XmlNamespaceRequest, string | undefined, void>;
|
|
65
|
+
export declare function namespaceMetadata(scope: Map<string, string> | XmlNamespaceScope, prefix: string, deferred?: boolean, prefixSource?: XmlSourceSpan): Generator<XmlNamespaceRequest, Pick<XmlName, 'namespace' | 'namespaceReference'>, void>;
|
|
66
|
+
export declare function hasNamespace(scope: Map<string, string> | XmlNamespaceScope, prefix: string, prefixSource?: XmlSourceSpan): Generator<XmlNamespaceRequest, boolean, void>;
|
|
67
|
+
export declare function bindNamespace(scope: Map<string, string> | XmlNamespaceScope, prefix: string, value: string | Generator<XmlSourceStep | string, void, void>, prefixSource?: XmlSourceSpan): Generator<XmlNamespaceRequest, Map<string, string> | XmlNamespaceScope, void>;
|
package/dist/xml-ast/frames.js
CHANGED
|
@@ -43,21 +43,43 @@ export class XmlFrames {
|
|
|
43
43
|
return frame;
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
export function* namespaceValue(scope, prefix) {
|
|
46
|
+
export function* namespaceValue(scope, prefix, prefixSource) {
|
|
47
47
|
if (scope instanceof Map)
|
|
48
48
|
return scope.get(prefix);
|
|
49
|
-
const request = { namespaceOperation: 'get', scope, prefix };
|
|
49
|
+
const request = { namespaceOperation: 'get', scope, prefix, ...(prefixSource ? { prefixSource } : {}) };
|
|
50
50
|
yield request;
|
|
51
51
|
if (!request.complete)
|
|
52
52
|
throw new TypeError('Incomplete XML namespace read');
|
|
53
53
|
return request.value;
|
|
54
54
|
}
|
|
55
|
-
export function*
|
|
55
|
+
export function* namespaceMetadata(scope, prefix, deferred = false, prefixSource) {
|
|
56
|
+
if (!deferred || scope instanceof Map)
|
|
57
|
+
return { namespace: (yield* namespaceValue(scope, prefix, prefixSource)) ?? '' };
|
|
58
|
+
if (prefix === 'xml')
|
|
59
|
+
return { namespace: 'http://www.w3.org/XML/1998/namespace' };
|
|
60
|
+
const request = { namespaceOperation: 'reference', scope, prefix, ...(prefixSource ? { prefixSource } : {}) };
|
|
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, prefixSource) {
|
|
67
|
+
if (scope instanceof Map)
|
|
68
|
+
return scope.has(prefix);
|
|
69
|
+
const request = { namespaceOperation: 'has', scope, prefix, ...(prefixSource ? { prefixSource } : {}) };
|
|
70
|
+
yield request;
|
|
71
|
+
if (request.found === undefined)
|
|
72
|
+
throw new TypeError('Incomplete XML namespace membership lookup');
|
|
73
|
+
return request.found;
|
|
74
|
+
}
|
|
75
|
+
export function* bindNamespace(scope, prefix, value, prefixSource) {
|
|
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
|
}
|
|
60
|
-
const request = { namespaceOperation: 'set', scope, prefix, value };
|
|
82
|
+
const request = { namespaceOperation: 'set', scope, prefix, value, ...(prefixSource ? { prefixSource } : {}) };
|
|
61
83
|
yield request;
|
|
62
84
|
if (!request.result)
|
|
63
85
|
throw new TypeError('Incomplete XML namespace update');
|
package/dist/xml-ast/index.d.ts
CHANGED
|
@@ -3,13 +3,21 @@ 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
|
+
readonly nameSource?: XmlSourceSpan;
|
|
13
|
+
readonly nameReference?: number;
|
|
14
|
+
readonly localNameSource?: XmlSourceSpan;
|
|
15
|
+
readonly localNameReference?: number;
|
|
16
|
+
readonly prefix?: string;
|
|
17
|
+
readonly prefixSource?: XmlSourceSpan;
|
|
18
|
+
readonly prefixReference?: number;
|
|
12
19
|
readonly namespace: string;
|
|
20
|
+
readonly namespaceReference?: number;
|
|
13
21
|
readonly localName: string;
|
|
14
22
|
}
|
|
15
23
|
export interface XmlAttribute extends XmlName {
|
|
@@ -21,6 +29,8 @@ export type XmlContent = XmlElement | {
|
|
|
21
29
|
} | {
|
|
22
30
|
readonly kind: "processing-instruction";
|
|
23
31
|
readonly target: string;
|
|
32
|
+
readonly targetSource?: XmlSourceSpan;
|
|
33
|
+
readonly targetReference?: number;
|
|
24
34
|
readonly text: string;
|
|
25
35
|
};
|
|
26
36
|
export interface XmlElement extends XmlName {
|
|
@@ -65,9 +75,18 @@ export interface XmlSourceLimits extends XmlStepLimits {
|
|
|
65
75
|
readonly storeFrames?: boolean;
|
|
66
76
|
/** Resolve namespace scopes through host-serviced requests. Requires retainTree: false. */
|
|
67
77
|
readonly storeNamespaces?: boolean;
|
|
78
|
+
/** Carry host namespace token references in event metadata. Requires stored namespaces. */
|
|
79
|
+
readonly deferNamespaces?: boolean;
|
|
80
|
+
/** Emit long processing-instruction targets as source spans. Requires fragmentContent. */
|
|
81
|
+
readonly deferContentNames?: boolean;
|
|
82
|
+
/** Defer long element names and, with external namespaces, their prefixes. */
|
|
83
|
+
readonly deferElementNames?: boolean;
|
|
84
|
+
/** Defer attribute names and namespace declaration prefixes to source spans. */
|
|
85
|
+
readonly deferAttributeNames?: boolean;
|
|
68
86
|
/** Store attribute collections through host requests and emit resolved attributes individually. */
|
|
69
87
|
readonly storeAttributes?: boolean;
|
|
70
|
-
readonly
|
|
88
|
+
readonly fragmentAttributes?: boolean;
|
|
89
|
+
readonly onAttribute?: (attribute: XmlAttribute, element: XmlElement, continuation?: boolean) => void;
|
|
71
90
|
}
|
|
72
91
|
export type XmlParseStep = XmlSourceStep | XmlFrameRequest | XmlNamespaceRequest | XmlAttributeRequest;
|
|
73
92
|
/** Parse a validated, BOM-free, line-normalized UTF-16 source via bounded read requests. */
|
package/dist/xml-ast/index.js
CHANGED
|
@@ -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,41 @@ 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 = -1, 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 >= 0 || first)
|
|
83
|
+
invalid("invalid qualified name");
|
|
84
|
+
colon = offset;
|
|
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
|
+
return colon;
|
|
105
|
+
}
|
|
71
106
|
function* entity(source, start, end, recover) {
|
|
72
107
|
// Inspect only a bounded prefix, even for arbitrarily long numeric references.
|
|
73
108
|
const prefix = typeof source === "string" ? source.slice(start, Math.min(end, start + 4))
|
|
@@ -102,7 +137,7 @@ function* entity(source, start, end, recover) {
|
|
|
102
137
|
return String.fromCodePoint(point);
|
|
103
138
|
}
|
|
104
139
|
/** Decoded text fragments; source read/work requests remain host-serviced. */
|
|
105
|
-
function* textParts(source, start, end, decode, recover) {
|
|
140
|
+
function* textParts(source, start, end, decode, recover, attribute = false) {
|
|
106
141
|
let cursor = start;
|
|
107
142
|
while (cursor < end) {
|
|
108
143
|
let finish = Math.min(end, cursor + 512);
|
|
@@ -111,7 +146,13 @@ function* textParts(source, start, end, decode, recover) {
|
|
|
111
146
|
if (last >= 0xd800 && last <= 0xdbff)
|
|
112
147
|
finish--;
|
|
113
148
|
}
|
|
114
|
-
|
|
149
|
+
let raw = yield* source.slice(cursor, finish);
|
|
150
|
+
if (attribute) {
|
|
151
|
+
let normalized = "";
|
|
152
|
+
for (const character of raw)
|
|
153
|
+
normalized += "\t\n\r".includes(character) ? " " : character;
|
|
154
|
+
raw = normalized;
|
|
155
|
+
}
|
|
115
156
|
const found = decode ? raw.indexOf("&") : -1;
|
|
116
157
|
if (found < 0) {
|
|
117
158
|
yield raw;
|
|
@@ -140,9 +181,9 @@ function* textParts(source, start, end, decode, recover) {
|
|
|
140
181
|
cursor = semicolon + 1;
|
|
141
182
|
}
|
|
142
183
|
}
|
|
143
|
-
function* textFragments(source, start, end, decode, recover) {
|
|
184
|
+
function* textFragments(source, start, end, decode, recover, attribute = false) {
|
|
144
185
|
let pending = "";
|
|
145
|
-
for (const part of textParts(source, start, end, decode, recover)) {
|
|
186
|
+
for (const part of textParts(source, start, end, decode, recover, attribute)) {
|
|
146
187
|
if (typeof part !== "string") {
|
|
147
188
|
yield part;
|
|
148
189
|
continue;
|
|
@@ -254,10 +295,20 @@ function validateLimits(limits) {
|
|
|
254
295
|
}
|
|
255
296
|
export function* parseXmlSourceSteps(length, limits = {}) {
|
|
256
297
|
validateLimits(limits);
|
|
298
|
+
if (limits.deferAttributeNames && (!limits.storeAttributes || !limits.storeNamespaces))
|
|
299
|
+
throw new TypeError("Deferred attribute names require stored attributes and namespaces");
|
|
300
|
+
if (limits.deferElementNames && limits.retainTree !== false)
|
|
301
|
+
throw new TypeError("Deferred element names require retainTree: false");
|
|
302
|
+
if (limits.deferContentNames && !limits.fragmentContent)
|
|
303
|
+
throw new TypeError("Deferred XML content names require fragmentContent: true");
|
|
304
|
+
if (limits.fragmentAttributes && !limits.storeAttributes)
|
|
305
|
+
throw new TypeError("XML attribute fragments require storeAttributes: true");
|
|
257
306
|
if (limits.storeFrames && limits.retainTree !== false)
|
|
258
307
|
throw new TypeError("Stored XML frames require retainTree: false");
|
|
259
308
|
if (limits.storeAttributes && limits.retainTree !== false)
|
|
260
309
|
throw new TypeError("Stored XML attributes require retainTree: false");
|
|
310
|
+
if (limits.deferNamespaces && !limits.storeNamespaces)
|
|
311
|
+
throw new TypeError("Deferred XML namespaces require stored namespaces");
|
|
261
312
|
if (limits.storeNamespaces && limits.retainTree !== false)
|
|
262
313
|
throw new TypeError("Stored XML namespaces require retainTree: false");
|
|
263
314
|
if (limits.fragmentContent && limits.retainTree !== false)
|
|
@@ -314,7 +365,24 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
314
365
|
}
|
|
315
366
|
return offset - start;
|
|
316
367
|
};
|
|
317
|
-
const
|
|
368
|
+
const sameName = function* (left, right, span) {
|
|
369
|
+
if (!left.nameSource && !span)
|
|
370
|
+
return left.name === right;
|
|
371
|
+
const leftLength = left.nameSource ? left.nameSource.end - left.nameSource.start : left.name.length;
|
|
372
|
+
const rightLength = span ? span.end - span.start : right.length;
|
|
373
|
+
if (leftLength !== rightLength)
|
|
374
|
+
return false;
|
|
375
|
+
for (let index = 0; index < leftLength; index += 512) {
|
|
376
|
+
const end = Math.min(leftLength, index + 512);
|
|
377
|
+
const a = left.nameSource ? yield* source.slice(left.nameSource.start + index, left.nameSource.start + end) : left.name.slice(index, end);
|
|
378
|
+
const b = span ? yield* source.slice(span.start + index, span.start + end) : right.slice(index, end);
|
|
379
|
+
yield end - index;
|
|
380
|
+
if (a !== b)
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
};
|
|
385
|
+
const scanName = function* (defer) {
|
|
318
386
|
const start = offset;
|
|
319
387
|
while (yield* source.has(offset)) {
|
|
320
388
|
const c = (yield* source.charCodeAt(offset));
|
|
@@ -327,6 +395,21 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
327
395
|
pendingWork -= 512;
|
|
328
396
|
}
|
|
329
397
|
}
|
|
398
|
+
if (defer && offset - start > 512) {
|
|
399
|
+
const colon = yield* validateNameSpan(source, start, offset);
|
|
400
|
+
if (defer === "content")
|
|
401
|
+
return ["", "", "", { start, end: offset }];
|
|
402
|
+
if (colon - start > 512 && !limits.storeNamespaces) {
|
|
403
|
+
const name = yield* source.slice(start, offset);
|
|
404
|
+
return [name, name.slice(0, colon - start), name.slice(colon - start + 1)];
|
|
405
|
+
}
|
|
406
|
+
const localStart = colon < 0 ? start : colon + 1;
|
|
407
|
+
const prefixSource = colon - start > 512 ? { start, end: colon } : undefined;
|
|
408
|
+
const prefix = colon < 0 || prefixSource ? "" : yield* source.slice(start, colon);
|
|
409
|
+
const localNameSource = offset - localStart > 512 ? { start: localStart, end: offset } : undefined;
|
|
410
|
+
const local = localNameSource ? "" : yield* source.slice(localStart, offset);
|
|
411
|
+
return ["", prefix, local, { start, end: offset }, localNameSource, prefixSource];
|
|
412
|
+
}
|
|
330
413
|
const name = (yield* source.slice(start, offset));
|
|
331
414
|
pendingWork += offset - start;
|
|
332
415
|
while (pendingWork >= 512) {
|
|
@@ -336,7 +419,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
336
419
|
const [prefix, localName] = qualifiedNameSync(name, qualifiedNames);
|
|
337
420
|
return [name, prefix, localName];
|
|
338
421
|
};
|
|
339
|
-
const rawContent = function* (kind, start, end, parent, target = "") {
|
|
422
|
+
const rawContent = function* (kind, start, end, parent, target = "", targetSource) {
|
|
340
423
|
if (end - start > maxTextLength - textLength)
|
|
341
424
|
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
342
425
|
textLength += end - start;
|
|
@@ -350,7 +433,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
350
433
|
finish--;
|
|
351
434
|
}
|
|
352
435
|
const text = yield* source.slice(cursor, finish);
|
|
353
|
-
const content = kind === "processing-instruction" ? { kind, target, text } : { kind, text };
|
|
436
|
+
const content = kind === "processing-instruction" ? { kind, target, text, ...(targetSource ? { targetSource } : {}) } : { kind, text };
|
|
354
437
|
if (kind === "cdata" && retainTree)
|
|
355
438
|
parent.element.text += text;
|
|
356
439
|
if (retain)
|
|
@@ -486,7 +569,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
486
569
|
else if ((yield* source.startsWith("<?", offset))) {
|
|
487
570
|
const start = offset;
|
|
488
571
|
offset += 2;
|
|
489
|
-
const [target] = yield* scanName();
|
|
572
|
+
const [target, , , targetSource] = yield* scanName(limits.deferContentNames ? "content" : undefined);
|
|
490
573
|
while (pendingWork >= 512) {
|
|
491
574
|
yield 512;
|
|
492
575
|
pendingWork -= 512;
|
|
@@ -519,7 +602,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
519
602
|
yield 512;
|
|
520
603
|
pendingWork -= 512;
|
|
521
604
|
}
|
|
522
|
-
yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target);
|
|
605
|
+
yield* rawContent("processing-instruction", body, end, yield* stack.peek(), target, targetSource);
|
|
523
606
|
}
|
|
524
607
|
offset = end + 2;
|
|
525
608
|
}
|
|
@@ -528,14 +611,14 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
528
611
|
}
|
|
529
612
|
else if ((yield* source.charCodeAt(offset + 1)) === 47) {
|
|
530
613
|
offset += 2;
|
|
531
|
-
const [name] = yield* scanName();
|
|
614
|
+
const [name, , , nameSource] = yield* scanName(limits.deferElementNames ? "element" : undefined);
|
|
532
615
|
yield* skipWhitespace();
|
|
533
616
|
while (pendingWork >= 512) {
|
|
534
617
|
yield 512;
|
|
535
618
|
pendingWork -= 512;
|
|
536
619
|
}
|
|
537
620
|
const closed = (yield* source.slice(offset++, offset)) === ">" ? (yield* stack.pop()) : undefined;
|
|
538
|
-
if (closed
|
|
621
|
+
if (!closed || !(yield* sameName(closed.element, name, nameSource))) {
|
|
539
622
|
if (!limits.recover)
|
|
540
623
|
invalid("mismatched closing tag");
|
|
541
624
|
limits.recover("mismatched closing tag");
|
|
@@ -546,9 +629,9 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
546
629
|
else {
|
|
547
630
|
offset++;
|
|
548
631
|
const repeated = previousEmpty && (yield* source.startsWith(previousEmpty.suffix, offset)) ? previousEmpty : undefined;
|
|
549
|
-
const [name, prefix, localName] = repeated
|
|
632
|
+
const [name, prefix, localName, nameSource, localNameSource, prefixSource] = repeated
|
|
550
633
|
? [repeated.name, repeated.prefix, repeated.localName]
|
|
551
|
-
: (yield* scanName());
|
|
634
|
+
: (yield* scanName(limits.deferElementNames ? "element" : undefined));
|
|
552
635
|
if (repeated)
|
|
553
636
|
offset += name.length;
|
|
554
637
|
// Preserve scan/validation work while reusing the admitted spelling.
|
|
@@ -573,12 +656,12 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
573
656
|
break;
|
|
574
657
|
if (ws === 0)
|
|
575
658
|
invalid("attributes require whitespace");
|
|
576
|
-
const [attribute, attrPrefix, attrLocal] = yield* scanName();
|
|
659
|
+
const [attribute, attrPrefix, attrLocal, nameSource, localNameSource, attrPrefixSource] = yield* scanName(limits.deferAttributeNames ? "attribute" : undefined);
|
|
577
660
|
while (pendingWork >= 512) {
|
|
578
661
|
yield 512;
|
|
579
662
|
pendingWork -= 512;
|
|
580
663
|
}
|
|
581
|
-
if ((yield* attributes.has(attribute)))
|
|
664
|
+
if ((yield* attributes.has(attribute, nameSource)))
|
|
582
665
|
invalid("duplicate attribute");
|
|
583
666
|
if (++attributeCount > maxAttributes)
|
|
584
667
|
throw new XmlLimitError("maxAttributes", "XML attribute limit exceeded");
|
|
@@ -600,28 +683,57 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
600
683
|
}
|
|
601
684
|
if (end < 0)
|
|
602
685
|
invalid("unterminated attribute");
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
686
|
+
const fragmented = limits.fragmentAttributes && (limits.storeNamespaces || attribute !== "xmlns" && attrPrefix !== "xmlns");
|
|
687
|
+
const valueStart = offset;
|
|
688
|
+
let value = "", size = 0, matchesXml = true, matchesXmlns = true;
|
|
689
|
+
if (fragmented) {
|
|
690
|
+
if ((yield* source.indexOf("<", offset, end)) >= 0)
|
|
691
|
+
invalid("less-than in attribute");
|
|
692
|
+
for (const part of textFragments(source, offset, end, true, limits.recover, true)) {
|
|
693
|
+
if (typeof part !== "string")
|
|
694
|
+
yield part;
|
|
695
|
+
else {
|
|
696
|
+
matchesXml = matchesXml && part === xmlNamespace.slice(size, size + part.length);
|
|
697
|
+
matchesXmlns = matchesXmlns && part === xmlnsNamespace.slice(size, size + part.length);
|
|
698
|
+
size += part.length;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
matchesXml = matchesXml && size === xmlNamespace.length;
|
|
702
|
+
matchesXmlns = matchesXmlns && size === xmlnsNamespace.length;
|
|
703
|
+
if (size > maxTextLength - textLength)
|
|
704
|
+
throw new XmlLimitError("maxTextLength", "XML text limit exceeded");
|
|
705
|
+
textLength += size;
|
|
608
706
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
707
|
+
else {
|
|
708
|
+
const raw = (yield* source.slice(offset, end));
|
|
709
|
+
pendingWork += raw.length * 2;
|
|
710
|
+
while (pendingWork >= 512) {
|
|
711
|
+
yield 512;
|
|
712
|
+
pendingWork -= 512;
|
|
713
|
+
}
|
|
714
|
+
if (raw.indexOf("<") >= 0)
|
|
715
|
+
invalid("less-than in attribute");
|
|
716
|
+
let normalized = raw;
|
|
717
|
+
if (raw.includes("\t") || raw.includes("\n") || raw.includes("\r")) {
|
|
718
|
+
normalized = "";
|
|
719
|
+
for (const character of raw)
|
|
720
|
+
normalized += "\t\n\r".includes(character) ? " " : character;
|
|
721
|
+
}
|
|
722
|
+
value = normalized.indexOf("&") < 0 ? (pendingWork += normalized.length, normalized) : yield* entities(normalized, limits.recover);
|
|
723
|
+
while (pendingWork >= 512) {
|
|
724
|
+
yield 512;
|
|
725
|
+
pendingWork -= 512;
|
|
726
|
+
}
|
|
727
|
+
admitText(value);
|
|
616
728
|
}
|
|
617
|
-
|
|
618
|
-
yield* attributes.append(attribute, attrPrefix, attrLocal, value);
|
|
729
|
+
yield* attributes.append(attribute, attrPrefix, attrLocal, value, limits.fragmentAttributes ? { start: offset, end } : undefined, nameSource, localNameSource, attrPrefixSource);
|
|
619
730
|
offset = end + 1;
|
|
620
|
-
if (attribute === "xmlns" ||
|
|
621
|
-
const nsPrefix = attribute === "xmlns" ? "" :
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|| (nsPrefix
|
|
731
|
+
if (attribute === "xmlns" || attrPrefix === "xmlns") {
|
|
732
|
+
const nsPrefix = attribute === "xmlns" ? "" : attrLocal;
|
|
733
|
+
const nsPrefixSource = attrPrefix === "xmlns" ? localNameSource : undefined;
|
|
734
|
+
if (nsPrefix === "xmlns" || (fragmented ? matchesXmlns : value === xmlnsNamespace)
|
|
735
|
+
|| (nsPrefix === "xml") !== (fragmented ? matchesXml : value === xmlNamespace)
|
|
736
|
+
|| ((nsPrefix !== "" || nsPrefixSource) && (fragmented ? size === 0 : value === "")))
|
|
625
737
|
invalid("invalid namespace binding");
|
|
626
738
|
if (!ownsNamespaces && namespaces instanceof Map) {
|
|
627
739
|
const copy = new Map();
|
|
@@ -632,7 +744,8 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
632
744
|
namespaces = copy;
|
|
633
745
|
ownsNamespaces = true;
|
|
634
746
|
}
|
|
635
|
-
namespaces = yield* bindNamespace(namespaces, nsPrefix,
|
|
747
|
+
namespaces = yield* bindNamespace(namespaces, nsPrefix, fragmented
|
|
748
|
+
? textFragments(source, valueStart, end, true, limits.recover ? () => { } : undefined, true) : value, nsPrefixSource);
|
|
636
749
|
if (namespaces.size > maxNamespaces)
|
|
637
750
|
throw new XmlLimitError("maxNamespaces", "XML namespace scope limit exceeded");
|
|
638
751
|
}
|
|
@@ -642,14 +755,15 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
642
755
|
const record = yield* attributes.read(cursor, false);
|
|
643
756
|
cursor = record.next;
|
|
644
757
|
const { name: attribute, prefix: attrPrefix, localName: attrLocal } = record;
|
|
645
|
-
if (attribute === "xmlns" ||
|
|
758
|
+
if (attribute === "xmlns" || attrPrefix === "xmlns")
|
|
646
759
|
continue;
|
|
647
760
|
pendingWork += attribute.length;
|
|
648
|
-
if (attrPrefix && (yield*
|
|
761
|
+
if ((attrPrefix || record.prefixSource) && !(yield* hasNamespace(namespaces, attrPrefix, record.prefixSource)))
|
|
649
762
|
invalid("unbound attribute prefix");
|
|
650
763
|
if (attributes.length > 1) {
|
|
651
|
-
const
|
|
652
|
-
|
|
764
|
+
const backed = limits.storeAttributes && !(namespaces instanceof Map) ? namespaces : undefined;
|
|
765
|
+
const key = backed ? attrLocal : `${attrPrefix ? (yield* namespaceValue(namespaces, attrPrefix)) : ""}\0${attrLocal}`;
|
|
766
|
+
if (yield* attributes.expanded(key, backed ? { scope: backed, prefix: attrPrefix, ...(record.prefixSource ? { prefixSource: record.prefixSource } : {}) } : undefined, record.localNameSource))
|
|
653
767
|
invalid("duplicate expanded attribute");
|
|
654
768
|
}
|
|
655
769
|
}
|
|
@@ -659,14 +773,15 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
659
773
|
yield 512;
|
|
660
774
|
pendingWork -= 512;
|
|
661
775
|
}
|
|
662
|
-
if (prefix === "xmlns" || (prefix && (yield*
|
|
776
|
+
if (prefix === "xmlns" || ((prefix || prefixSource) && !(yield* hasNamespace(namespaces, prefix, prefixSource))))
|
|
663
777
|
invalid("unbound element prefix");
|
|
664
778
|
if (++nodes > maxNodes)
|
|
665
779
|
throw new XmlLimitError("maxNodes", "XML resource limit exceeded");
|
|
666
780
|
if (stack.length + 1 > maxDepth)
|
|
667
781
|
throw new XmlLimitError("maxDepth", "XML resource limit exceeded");
|
|
668
|
-
const namespace =
|
|
669
|
-
|
|
782
|
+
const namespace = yield* namespaceMetadata(namespaces, prefix, limits.deferNamespaces, prefixSource);
|
|
783
|
+
const elementName = { name, localName, ...(nameSource ? { nameSource, prefix } : {}), ...(localNameSource ? { localNameSource } : {}), ...(prefixSource ? { prefixSource } : {}) };
|
|
784
|
+
limits.onElement?.({ ...elementName, ...namespace }, (yield* stack.peek())?.element, stack.length + 1);
|
|
670
785
|
admitContent();
|
|
671
786
|
const retainedAttributes = [];
|
|
672
787
|
if (retainContent) {
|
|
@@ -686,7 +801,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
686
801
|
pendingWork -= 512;
|
|
687
802
|
}
|
|
688
803
|
const content = retain ? [] : undefined;
|
|
689
|
-
const element = { kind: "element",
|
|
804
|
+
const element = { kind: "element", ...elementName, ...namespace, children: [], text: "", content: content ?? emptyContent, attributes: retainContent ? retainedAttributes : emptyAttributes, namespaces: retainContent && namespaces instanceof Map ? namespaces : emptyNamespaces, ...(root === undefined && retainContent ? { prolog, epilog, ...(declaration === undefined ? {} : { declaration }) } : {}) };
|
|
690
805
|
const parent = (yield* stack.peek());
|
|
691
806
|
if (parent) {
|
|
692
807
|
if (retainTree) {
|
|
@@ -708,7 +823,7 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
708
823
|
}
|
|
709
824
|
// Only cache compact, attribute-free spellings. Namespace resolution and
|
|
710
825
|
// every admission counter still run for each distinct physical element.
|
|
711
|
-
if (empty && !attributes.length && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
|
|
826
|
+
if (empty && !nameSource && !attributes.length && name.length <= 512 && (yield* source.slice(offset - name.length - 3, offset)) === `<${name}/>`)
|
|
712
827
|
previousEmpty = { suffix: name + "/>", name, prefix, localName };
|
|
713
828
|
limits.events?.({ type: "open", element, parent: parent?.element });
|
|
714
829
|
if (limits.storeAttributes && limits.onAttribute && retainContent) {
|
|
@@ -716,10 +831,25 @@ export function* parseXmlSourceSteps(length, limits = {}) {
|
|
|
716
831
|
for (let cursor = attributes.first; cursor;) {
|
|
717
832
|
const attribute = yield* attributes.read(cursor, true);
|
|
718
833
|
cursor = attribute.next;
|
|
719
|
-
const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? xmlnsNamespace
|
|
720
|
-
: attribute.prefix ?
|
|
721
|
-
|
|
722
|
-
|
|
834
|
+
const namespace = attribute.name === "xmlns" || attribute.prefix === "xmlns" ? { namespace: xmlnsNamespace }
|
|
835
|
+
: attribute.prefix || attribute.prefixSource ? yield* namespaceMetadata(namespaces, attribute.prefix, limits.deferNamespaces, attribute.prefixSource) : { namespace: "" };
|
|
836
|
+
const nameMetadata = { name: attribute.name, localName: attribute.localName, ...(attribute.nameSource ? { nameSource: attribute.nameSource, prefix: attribute.prefix } : {}), ...(attribute.localNameSource ? { localNameSource: attribute.localNameSource } : {}), ...(attribute.prefixSource ? { prefixSource: attribute.prefixSource } : {}) };
|
|
837
|
+
let continuation = false;
|
|
838
|
+
if (attribute.source) {
|
|
839
|
+
for (const part of textFragments(source, attribute.source.start, attribute.source.end, true, limits.recover ? () => { } : undefined, true)) {
|
|
840
|
+
if (typeof part !== "string") {
|
|
841
|
+
yield part;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
limits.onAttribute({ ...nameMetadata, ...namespace, value: part }, element, continuation);
|
|
845
|
+
continuation = true;
|
|
846
|
+
yield 1;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (!continuation) {
|
|
850
|
+
limits.onAttribute({ ...nameMetadata, ...namespace, value: attribute.value }, element);
|
|
851
|
+
yield 1;
|
|
852
|
+
}
|
|
723
853
|
}
|
|
724
854
|
}
|
|
725
855
|
if (!empty)
|