@aglyn/aglyn 1.0.0-beta.198 → 1.0.0-beta.200

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.
Files changed (113) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/activity-search.d.ts +39 -0
  3. package/src/lib/app-utils/activity-search.js +85 -0
  4. package/src/lib/app-utils/activity-search.js.map +1 -0
  5. package/src/lib/app-utils/admin-audit-index.d.ts +104 -0
  6. package/src/lib/app-utils/admin-audit-index.js +177 -0
  7. package/src/lib/app-utils/admin-audit-index.js.map +1 -0
  8. package/src/lib/app-utils/artifact-list-keys.d.ts +51 -0
  9. package/src/lib/app-utils/artifact-list-keys.js +144 -0
  10. package/src/lib/app-utils/artifact-list-keys.js.map +1 -0
  11. package/src/lib/app-utils/content-query-fields.d.ts +69 -0
  12. package/src/lib/app-utils/content-query-fields.js +79 -0
  13. package/src/lib/app-utils/content-query-fields.js.map +1 -0
  14. package/src/lib/app-utils/crm.d.ts +151 -1
  15. package/src/lib/app-utils/crm.js +388 -1
  16. package/src/lib/app-utils/crm.js.map +1 -1
  17. package/src/lib/app-utils/dataset-models.d.ts +75 -24
  18. package/src/lib/app-utils/dataset-models.js +107 -40
  19. package/src/lib/app-utils/dataset-models.js.map +1 -1
  20. package/src/lib/app-utils/docs-index.generated.js +40 -40
  21. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  22. package/src/lib/app-utils/email-search.d.ts +36 -0
  23. package/src/lib/app-utils/email-search.js +53 -0
  24. package/src/lib/app-utils/email-search.js.map +1 -0
  25. package/src/lib/app-utils/forms.d.ts +83 -3
  26. package/src/lib/app-utils/forms.js +67 -0
  27. package/src/lib/app-utils/forms.js.map +1 -1
  28. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +7 -6
  29. package/src/lib/app-utils/marketplace-listing-visibility.js +12 -10
  30. package/src/lib/app-utils/marketplace-listing-visibility.js.map +1 -1
  31. package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
  32. package/src/lib/app-utils/media-embedded-fields.js +619 -0
  33. package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
  34. package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
  35. package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
  36. package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
  37. package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
  38. package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
  39. package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
  40. package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
  41. package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
  42. package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
  43. package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
  44. package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
  45. package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
  46. package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
  47. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
  48. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
  49. package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
  50. package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
  51. package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
  52. package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
  53. package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
  54. package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
  55. package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
  56. package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
  57. package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
  58. package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
  59. package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
  60. package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
  61. package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
  62. package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
  63. package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
  64. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
  65. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
  66. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
  67. package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
  68. package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
  69. package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
  70. package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
  71. package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
  72. package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
  73. package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
  74. package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
  75. package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
  76. package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
  77. package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
  78. package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
  79. package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
  80. package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
  81. package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
  82. package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
  83. package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
  84. package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
  85. package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
  86. package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
  87. package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
  88. package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
  89. package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
  90. package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
  91. package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
  92. package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
  93. package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
  94. package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
  95. package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
  96. package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
  97. package/src/lib/app-utils/media-metadata.d.ts +79 -0
  98. package/src/lib/app-utils/media-metadata.js +74 -0
  99. package/src/lib/app-utils/media-metadata.js.map +1 -1
  100. package/src/lib/app-utils/message-search.d.ts +40 -0
  101. package/src/lib/app-utils/message-search.js +168 -0
  102. package/src/lib/app-utils/message-search.js.map +1 -0
  103. package/src/lib/app-utils/name-search.d.ts +95 -0
  104. package/src/lib/app-utils/name-search.js +108 -0
  105. package/src/lib/app-utils/name-search.js.map +1 -1
  106. package/src/lib/app-utils/server.d.ts +2 -0
  107. package/src/lib/app-utils/server.js +2 -0
  108. package/src/lib/app-utils/server.js.map +1 -1
  109. package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
  110. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  111. package/src/server.d.ts +1 -0
  112. package/src/server.js +7 -0
  113. package/src/server.js.map +1 -1
@@ -0,0 +1,598 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * A minimal, FAITHFUL XML DOM for editing XMP packets in place (AGL-3331).
18
+ *
19
+ * ## Why not a general XML library
20
+ *
21
+ * An XMP packet written by Lightroom or Photoshop carries structures this
22
+ * module does not understand — `xmpMM:History`, `crs:*` tone curves,
23
+ * `mwg-rs` face regions, contact-info structs — and an edit of one caption
24
+ * must leave every one of them exactly as it was. A conventional DOM
25
+ * normalizes on the way in (quote style, attribute whitespace, entity
26
+ * spelling, `<a/>` versus `<a></a>`), so a round trip through it rewrites
27
+ * the whole packet. This one keeps the raw text of everything it parses
28
+ * next to the decoded value, and {@link serializeXml} writes the raw text
29
+ * back, so `serializeXml(parseXml(s)) === s` for any input it accepts and
30
+ * only the nodes an edit touched come out different.
31
+ *
32
+ * ## What it refuses
33
+ *
34
+ * The input is hostile: it arrives inside an uploaded file. There is NO DTD
35
+ * support at all — `<!DOCTYPE`, and with it `<!ENTITY`, external entities
36
+ * and entity expansion ("billion laughs"), are unparseable, as is any named
37
+ * entity other than the five XML predefines (XML 1.0 §4.6). Depth, node
38
+ * count and input length are bounded. Everything that is not well-formed
39
+ * returns `null`; nothing here throws on input.
40
+ *
41
+ * It is deliberately lenient in one direction only: content outside the
42
+ * root element (the `<?xpacket?>` wrappers, trailing padding, a BOM) is
43
+ * kept as nodes of the document rather than rejected, because XMP packets
44
+ * live in exactly that space and a fragment handed over from an SVG's
45
+ * `<metadata>` may have several top-level elements.
46
+ */ /** The `xml:` prefix is bound by definition (Namespaces in XML 1.0 §3). */ export const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
47
+ /** The `xmlns:` prefix is bound by definition (Namespaces in XML 1.0 §3). */ export const XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/';
48
+ /** Default parse bounds. XMP packets are kilobytes; these are generous. */ export const XML_MAX_DEPTH = 64;
49
+ export const XML_MAX_NODES = 50000;
50
+ export const XML_MAX_LENGTH = 16 * 1024 * 1024;
51
+ /*
52
+ * XML 1.0 (Fifth Edition) §2.3, NameStartChar and NameChar, exactly. The
53
+ * `u` flag makes the astral range one range of code points rather than a
54
+ * pair of surrogate halves. The combining-mark range leads NameChar so no
55
+ * base character sits right before it in the class.
56
+ */ const NAME_START = 'A-Za-z_:\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D' + '\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF' + '\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\u{10000}-\\u{EFFFF}';
57
+ const NAME_CHAR = '\\u0300-\\u036F' + NAME_START + '\\-.0-9\\u00B7\\u203F-\\u2040';
58
+ const NAME = new RegExp(`[${NAME_START}][${NAME_CHAR}]*`, 'uy');
59
+ const WHITESPACE = /[ \t\r\n]*/y;
60
+ const EQUALS = /[ \t\r\n]*=[ \t\r\n]*/y;
61
+ const ALL_WHITESPACE = /^[ \t\r\n]*$/;
62
+ /** Read a Name at `pos` with a sticky regex, or null. */ function matchAt(pattern, input, pos) {
63
+ pattern.lastIndex = pos;
64
+ const match = pattern.exec(input);
65
+ return match ? match[0] : null;
66
+ }
67
+ /**
68
+ * Whether a code point is a legal XML 1.0 `Char` (§2.2). Character
69
+ * references to anything else — `&#0;`, a lone surrogate — are not
70
+ * well-formed.
71
+ */ function isXmlChar(code) {
72
+ return code === 0x9 || code === 0xa || code === 0xd || code >= 0x20 && code <= 0xd7ff || code >= 0xe000 && code <= 0xfffd || code >= 0x10000 && code <= 0x10ffff;
73
+ }
74
+ const PREDEFINED_ENTITIES = {
75
+ lt: '<',
76
+ gt: '>',
77
+ amp: '&',
78
+ quot: '"',
79
+ apos: "'"
80
+ };
81
+ /**
82
+ * Decode the raw text of a text node or an attribute value.
83
+ *
84
+ * Applies the normalizations a conforming parser applies, in the order the
85
+ * spec gives them: line ends first (§2.11, CRLF and lone CR become LF),
86
+ * then — for attributes only — each literal whitespace character becomes a
87
+ * space (§3.3.3), then references are replaced. A character produced by a
88
+ * reference is NOT normalized, which is how `&#xA;` survives in an
89
+ * attribute. Returns null for an undeclared entity, a bare `&` or a
90
+ * reference to a character XML cannot hold.
91
+ */ export function decodeXmlText(raw, attribute = false) {
92
+ let text = raw;
93
+ if (text.includes('\r')) text = text.replace(/\r\n?/g, '\n');
94
+ if (attribute && /[\t\n]/.test(text)) text = text.replace(/[\t\n]/g, ' ');
95
+ if (!text.includes('&')) return text;
96
+ let out = '';
97
+ let pos = 0;
98
+ for(;;){
99
+ const amp = text.indexOf('&', pos);
100
+ if (amp === -1) break;
101
+ const semi = text.indexOf(';', amp + 1);
102
+ // The longest legal reference is a padded numeric one; 32 is generous.
103
+ if (semi === -1 || semi - amp > 32) return null;
104
+ const ref = text.slice(amp + 1, semi);
105
+ let decoded;
106
+ if (ref.startsWith('#x')) {
107
+ if (!/^#x[0-9A-Fa-f]+$/.test(ref)) return null;
108
+ const code = parseInt(ref.slice(2), 16);
109
+ if (!isXmlChar(code)) return null;
110
+ decoded = String.fromCodePoint(code);
111
+ } else if (ref.startsWith('#')) {
112
+ if (!/^#[0-9]+$/.test(ref)) return null;
113
+ const code = parseInt(ref.slice(1), 10);
114
+ if (!isXmlChar(code)) return null;
115
+ decoded = String.fromCodePoint(code);
116
+ } else {
117
+ decoded = Object.prototype.hasOwnProperty.call(PREDEFINED_ENTITIES, ref) ? PREDEFINED_ENTITIES[ref] : undefined;
118
+ if (decoded === undefined) return null;
119
+ }
120
+ out += text.slice(pos, amp) + decoded;
121
+ pos = semi + 1;
122
+ }
123
+ return out + text.slice(pos);
124
+ }
125
+ /**
126
+ * Escape a value for a text node. `>` is escaped too so a value can never
127
+ * spell `]]>`, and CR is written as a reference because a parser would
128
+ * otherwise normalize it away (§2.11).
129
+ */ export function escapeXmlText(value) {
130
+ return value.replace(/[&<>\r]/g, (ch)=>ch === '&' ? '&amp;' : ch === '<' ? '&lt;' : ch === '>' ? '&gt;' : '&#xD;');
131
+ }
132
+ /**
133
+ * Escape a value for an attribute delimited by `quote`. Tab, LF and CR are
134
+ * written as references so attribute-value normalization (§3.3.3) does not
135
+ * turn them into spaces on the next read.
136
+ */ export function escapeXmlAttribute(value, quote = '"') {
137
+ return value.replace(/[&<>"'\t\n\r]/g, (ch)=>{
138
+ switch(ch){
139
+ case '&':
140
+ return '&amp;';
141
+ case '<':
142
+ return '&lt;';
143
+ case '>':
144
+ return '&gt;';
145
+ case '"':
146
+ return quote === '"' ? '&quot;' : ch;
147
+ case "'":
148
+ return quote === "'" ? '&apos;' : ch;
149
+ case '\t':
150
+ return '&#x9;';
151
+ case '\n':
152
+ return '&#xA;';
153
+ default:
154
+ return '&#xD;';
155
+ }
156
+ });
157
+ }
158
+ /**
159
+ * Whether a string holds a character that XML 1.0 cannot represent at all,
160
+ * not even as a reference (§2.2): C0 controls other than tab/LF/CR, U+FFFE,
161
+ * U+FFFF and unpaired surrogates. A writer must refuse such a value rather
162
+ * than emit a packet no reader will parse.
163
+ */ export function hasNonXmlCharacters(value) {
164
+ for(let i = 0; i < value.length; i++){
165
+ const code = value.charCodeAt(i);
166
+ if (code >= 0xd800 && code <= 0xdbff) {
167
+ const next = value.charCodeAt(i + 1);
168
+ if (next >= 0xdc00 && next <= 0xdfff) {
169
+ i++;
170
+ continue;
171
+ }
172
+ return true;
173
+ }
174
+ if (!isXmlChar(code)) return true;
175
+ }
176
+ return false;
177
+ }
178
+ /**
179
+ * Parse `input` into a faithful DOM, or return null when it is not
180
+ * well-formed, declares a DTD, or exceeds a bound. Never throws.
181
+ *
182
+ * The parser is iterative — an explicit stack, not recursion — so a
183
+ * hostile nesting depth costs a counter check, not the call stack.
184
+ */ export function parseXml(input, limits = {}) {
185
+ try {
186
+ return parse(input, limits);
187
+ } catch (unused) {
188
+ // Defensive only: every path below returns null rather than throwing,
189
+ // and a parser of hostile bytes must not become a 500 if one is missed.
190
+ return null;
191
+ }
192
+ }
193
+ function parse(input, limits) {
194
+ var _limits_maxDepth, _limits_maxNodes, _limits_maxLength;
195
+ if (typeof input !== 'string') return null;
196
+ const maxDepth = (_limits_maxDepth = limits.maxDepth) != null ? _limits_maxDepth : XML_MAX_DEPTH;
197
+ const maxNodes = (_limits_maxNodes = limits.maxNodes) != null ? _limits_maxNodes : XML_MAX_NODES;
198
+ const maxLength = (_limits_maxLength = limits.maxLength) != null ? _limits_maxLength : XML_MAX_LENGTH;
199
+ if (input.length > maxLength) return null;
200
+ const doc = {
201
+ type: 'document',
202
+ children: []
203
+ };
204
+ const stack = [
205
+ doc
206
+ ];
207
+ let nodes = 0;
208
+ let pos = 0;
209
+ const length = input.length;
210
+ const current = ()=>{
211
+ var _stack_;
212
+ return (_stack_ = stack[stack.length - 1]) != null ? _stack_ : doc;
213
+ };
214
+ const add = (node)=>{
215
+ if (++nodes > maxNodes) return false;
216
+ const parent = current();
217
+ if (node.type === 'element') node.parent = parent;
218
+ parent.children.push(node);
219
+ return true;
220
+ };
221
+ while(pos < length){
222
+ const lt = input.indexOf('<', pos);
223
+ const textEnd = lt === -1 ? length : lt;
224
+ if (textEnd > pos) {
225
+ const raw = input.slice(pos, textEnd);
226
+ const value = decodeXmlText(raw);
227
+ if (value === null) return null;
228
+ if (!add({
229
+ type: 'text',
230
+ value,
231
+ raw
232
+ })) return null;
233
+ pos = textEnd;
234
+ continue;
235
+ }
236
+ // `input[pos]` is '<'.
237
+ if (input.startsWith('<!--', pos)) {
238
+ const end = input.indexOf('-->', pos + 4);
239
+ if (end === -1) return null;
240
+ if (!add({
241
+ type: 'comment',
242
+ value: input.slice(pos + 4, end)
243
+ })) return null;
244
+ pos = end + 3;
245
+ continue;
246
+ }
247
+ if (input.startsWith('<![CDATA[', pos)) {
248
+ // CDATA is content (§2.7): it cannot stand outside an element.
249
+ if (stack.length === 1) return null;
250
+ const end = input.indexOf(']]>', pos + 9);
251
+ if (end === -1) return null;
252
+ if (!add({
253
+ type: 'cdata',
254
+ value: input.slice(pos + 9, end)
255
+ })) return null;
256
+ pos = end + 3;
257
+ continue;
258
+ }
259
+ if (input.startsWith('<!', pos)) {
260
+ // <!DOCTYPE, <!ENTITY, <!ELEMENT, <!ATTLIST … — no DTD, ever.
261
+ return null;
262
+ }
263
+ if (input.startsWith('<?', pos)) {
264
+ const target = matchAt(NAME, input, pos + 2);
265
+ if (!target) return null;
266
+ const end = input.indexOf('?>', pos + 2 + target.length);
267
+ if (end === -1) return null;
268
+ const data = input.slice(pos + 2 + target.length, end);
269
+ // PI data is separated from its target by whitespace (§2.6).
270
+ if (data && !/^[ \t\r\n]/.test(data)) return null;
271
+ if (!add({
272
+ type: 'pi',
273
+ target,
274
+ data
275
+ })) return null;
276
+ pos = end + 2;
277
+ continue;
278
+ }
279
+ if (input.startsWith('</', pos)) {
280
+ var _matchAt;
281
+ const name = matchAt(NAME, input, pos + 2);
282
+ if (!name) return null;
283
+ let cursor = pos + 2 + name.length;
284
+ const tail = (_matchAt = matchAt(WHITESPACE, input, cursor)) != null ? _matchAt : '';
285
+ cursor += tail.length;
286
+ if (input[cursor] !== '>') return null;
287
+ const open = current();
288
+ if (open.type !== 'element' || open.name !== name) return null;
289
+ open.endTagTail = tail;
290
+ stack.pop();
291
+ pos = cursor + 1;
292
+ continue;
293
+ }
294
+ // A start tag (§3.1).
295
+ const name = matchAt(NAME, input, pos + 1);
296
+ if (!name) return null;
297
+ if (stack.length > maxDepth) return null;
298
+ const element = {
299
+ type: 'element',
300
+ name,
301
+ attributes: [],
302
+ children: [],
303
+ parent: null,
304
+ selfClosing: false,
305
+ tagTail: '',
306
+ endTagTail: ''
307
+ };
308
+ if (!add(element)) return null;
309
+ let cursor = pos + 1 + name.length;
310
+ const seen = new Set();
311
+ for(;;){
312
+ var _matchAt1;
313
+ const space = (_matchAt1 = matchAt(WHITESPACE, input, cursor)) != null ? _matchAt1 : '';
314
+ cursor += space.length;
315
+ if (input[cursor] === '>') {
316
+ element.tagTail = space;
317
+ cursor += 1;
318
+ stack.push(element);
319
+ break;
320
+ }
321
+ if (input.startsWith('/>', cursor)) {
322
+ element.tagTail = space;
323
+ element.selfClosing = true;
324
+ cursor += 2;
325
+ break;
326
+ }
327
+ // Attributes are separated from the name and each other by space.
328
+ if (!space) return null;
329
+ const attrName = matchAt(NAME, input, cursor);
330
+ if (!attrName) return null;
331
+ cursor += attrName.length;
332
+ const equals = matchAt(EQUALS, input, cursor);
333
+ if (!equals) return null;
334
+ cursor += equals.length;
335
+ const quote = input[cursor];
336
+ if (quote !== '"' && quote !== "'") return null;
337
+ const close = input.indexOf(quote, cursor + 1);
338
+ if (close === -1) return null;
339
+ const raw = input.slice(cursor + 1, close);
340
+ // `<` may not appear in an attribute value, even escaped-looking.
341
+ if (raw.includes('<')) return null;
342
+ const value = decodeXmlText(raw, true);
343
+ if (value === null) return null;
344
+ // Unique attribute names (§3.1, WFC: Unique Att Spec).
345
+ if (seen.has(attrName)) return null;
346
+ seen.add(attrName);
347
+ if (++nodes > maxNodes) return null;
348
+ element.attributes.push({
349
+ name: attrName,
350
+ value,
351
+ raw,
352
+ quote,
353
+ leading: space,
354
+ equals
355
+ });
356
+ cursor = close + 1;
357
+ }
358
+ pos = cursor;
359
+ }
360
+ // An element still open at the end of input is an unterminated tag.
361
+ if (stack.length !== 1) return null;
362
+ return doc;
363
+ }
364
+ /** Serialize a document or node back to text, raw spellings preserved. */ export function serializeXml(node) {
365
+ const out = [];
366
+ write(node, out);
367
+ return out.join('');
368
+ }
369
+ function write(node, out) {
370
+ switch(node.type){
371
+ case 'document':
372
+ for (const child of node.children)write(child, out);
373
+ return;
374
+ case 'text':
375
+ out.push(node.raw);
376
+ return;
377
+ case 'cdata':
378
+ out.push('<![CDATA[', node.value, ']]>');
379
+ return;
380
+ case 'comment':
381
+ out.push('<!--', node.value, '-->');
382
+ return;
383
+ case 'pi':
384
+ out.push('<?', node.target, node.data, '?>');
385
+ return;
386
+ case 'element':
387
+ {
388
+ out.push('<', node.name);
389
+ for (const attr of node.attributes){
390
+ out.push(attr.leading, attr.name, attr.equals, attr.quote, attr.raw, attr.quote);
391
+ }
392
+ out.push(node.tagTail);
393
+ if (node.selfClosing && !node.children.length) {
394
+ out.push('/>');
395
+ return;
396
+ }
397
+ out.push('>');
398
+ for (const child of node.children)write(child, out);
399
+ out.push('</', node.name, node.endTagTail, '>');
400
+ return;
401
+ }
402
+ }
403
+ }
404
+ // ---------------------------------------------------------------------------
405
+ // Construction and mutation. Every helper keeps `raw` in step with `value`
406
+ // and `parent` in step with `children`, so a serializer pass never has to
407
+ // guess which of the two is current.
408
+ // ---------------------------------------------------------------------------
409
+ /** A text node whose raw spelling is the escaped value. */ export function createText(value) {
410
+ return {
411
+ type: 'text',
412
+ value,
413
+ raw: escapeXmlText(value)
414
+ };
415
+ }
416
+ /**
417
+ * A whitespace-only text node written verbatim — indentation copied from
418
+ * the document, where escaping a CR would turn `\r\n` into `&#xD;\n`.
419
+ * Anything that is not XML whitespace is dropped.
420
+ */ export function createWhitespace(raw) {
421
+ const clean = raw.replace(/[^ \t\r\n]/g, '');
422
+ return {
423
+ type: 'text',
424
+ value: clean.replace(/\r\n?/g, '\n'),
425
+ raw: clean
426
+ };
427
+ }
428
+ /** A new element; attributes are `[name, value]` pairs, space-separated. */ export function createElement(name, attributes = [], children = [], quote = '"') {
429
+ const element = {
430
+ type: 'element',
431
+ name,
432
+ attributes: attributes.map(([attrName, value])=>({
433
+ name: attrName,
434
+ value,
435
+ raw: escapeXmlAttribute(value, quote),
436
+ quote,
437
+ leading: ' ',
438
+ equals: '='
439
+ })),
440
+ children: [],
441
+ parent: null,
442
+ selfClosing: false,
443
+ tagTail: '',
444
+ endTagTail: ''
445
+ };
446
+ insertChildren(element, 0, children);
447
+ return element;
448
+ }
449
+ /** Insert nodes at `index`, adopting any elements among them. */ export function insertChildren(parent, index, nodes) {
450
+ for (const node of nodes){
451
+ if (node.type === 'element') node.parent = parent;
452
+ }
453
+ parent.children.splice(index, 0, ...nodes);
454
+ }
455
+ /** Replace every child of `parent`. */ export function replaceChildren(parent, nodes) {
456
+ parent.children = [];
457
+ insertChildren(parent, 0, nodes);
458
+ }
459
+ /** Remove one child. Returns false when it was not a child of `parent`. */ export function removeChild(parent, node) {
460
+ const index = parent.children.indexOf(node);
461
+ if (index === -1) return false;
462
+ parent.children.splice(index, 1);
463
+ if (node.type === 'element') node.parent = null;
464
+ return true;
465
+ }
466
+ export function getAttribute(element, name) {
467
+ return element.attributes.find((attr)=>attr.name === name);
468
+ }
469
+ /**
470
+ * Set an attribute's value. An existing attribute keeps its position,
471
+ * quote style and surrounding whitespace; a new one is appended with
472
+ * `leading` before it.
473
+ */ export function setAttribute(element, name, value, options = {}) {
474
+ var _options_quote, _options_leading, _options_index;
475
+ const existing = getAttribute(element, name);
476
+ if (existing) {
477
+ existing.value = value;
478
+ existing.raw = escapeXmlAttribute(value, existing.quote);
479
+ return existing;
480
+ }
481
+ const quote = (_options_quote = options.quote) != null ? _options_quote : '"';
482
+ const attr = {
483
+ name,
484
+ value,
485
+ raw: escapeXmlAttribute(value, quote),
486
+ quote,
487
+ leading: (_options_leading = options.leading) != null ? _options_leading : ' ',
488
+ equals: '='
489
+ };
490
+ const index = (_options_index = options.index) != null ? _options_index : element.attributes.length;
491
+ element.attributes.splice(Math.max(0, Math.min(index, element.attributes.length)), 0, attr);
492
+ return attr;
493
+ }
494
+ export function removeAttribute(element, name) {
495
+ const index = element.attributes.findIndex((attr)=>attr.name === name);
496
+ if (index === -1) return false;
497
+ element.attributes.splice(index, 1);
498
+ return true;
499
+ }
500
+ /** Replace an element's content with one text node (none for ''). */ export function setTextContent(element, value) {
501
+ replaceChildren(element, value ? [
502
+ createText(value)
503
+ ] : []);
504
+ // `<a/>` would read back as '' either way; an explicit pair is clearer
505
+ // for a property that was just given a value.
506
+ if (value) element.selfClosing = false;
507
+ }
508
+ /** The decoded text of an element's direct text and CDATA children. */ export function textContent(element) {
509
+ let out = '';
510
+ for (const child of element.children){
511
+ if (child.type === 'text' || child.type === 'cdata') out += child.value;
512
+ }
513
+ return out;
514
+ }
515
+ export function elementChildren(parent) {
516
+ return parent.children.filter((child)=>child.type === 'element');
517
+ }
518
+ /** Whether a text node is nothing but XML whitespace. */ export function isWhitespaceText(node) {
519
+ return (node == null ? void 0 : node.type) === 'text' && ALL_WHITESPACE.test(node.raw);
520
+ }
521
+ /**
522
+ * Visit every element in document order, iteratively. Return `false` from
523
+ * `visit` to skip an element's descendants.
524
+ */ export function walkElements(root, visit) {
525
+ const pending = [
526
+ ...root.children
527
+ ].reverse();
528
+ while(pending.length){
529
+ const node = pending.pop();
530
+ if (!node || node.type !== 'element') continue;
531
+ if (visit(node) === false) continue;
532
+ for(let i = node.children.length - 1; i >= 0; i--){
533
+ const child = node.children[i];
534
+ if (child) pending.push(child);
535
+ }
536
+ }
537
+ }
538
+ // ---------------------------------------------------------------------------
539
+ // Namespaces (Namespaces in XML 1.0, Third Edition).
540
+ // ---------------------------------------------------------------------------
541
+ /** `dc:title` → `{ prefix: 'dc', local: 'title' }`; no colon → prefix ''. */ export function splitQName(name) {
542
+ const colon = name.indexOf(':');
543
+ return colon === -1 ? {
544
+ prefix: '',
545
+ local: name
546
+ } : {
547
+ prefix: name.slice(0, colon),
548
+ local: name.slice(colon + 1)
549
+ };
550
+ }
551
+ /**
552
+ * The namespace URI `prefix` is bound to at `element`, through the nearest
553
+ * `xmlns:prefix` (or, for `''`, `xmlns`) on it or an ancestor. Null when
554
+ * unbound — or, for the default namespace, explicitly undeclared (§6.2).
555
+ */ export function resolvePrefix(element, prefix) {
556
+ if (prefix === 'xml') return XML_NAMESPACE;
557
+ if (prefix === 'xmlns') return XMLNS_NAMESPACE;
558
+ const declaration = prefix ? `xmlns:${prefix}` : 'xmlns';
559
+ let cursor = element;
560
+ while(cursor && cursor.type === 'element'){
561
+ const attr = getAttribute(cursor, declaration);
562
+ if (attr) return attr.value || null;
563
+ cursor = cursor.parent;
564
+ }
565
+ return null;
566
+ }
567
+ /** An element's namespace URI: its prefix's, or the default namespace. */ export function elementNamespace(element) {
568
+ return resolvePrefix(element, splitQName(element.name).prefix);
569
+ }
570
+ /**
571
+ * An attribute's namespace URI. An unprefixed attribute is in NO namespace
572
+ * — the default namespace does not apply to attributes (§6.2).
573
+ */ export function attributeNamespace(element, attr) {
574
+ if (attr.name === 'xmlns') return XMLNS_NAMESPACE;
575
+ const { prefix } = splitQName(attr.name);
576
+ return prefix ? resolvePrefix(element, prefix) : null;
577
+ }
578
+ /** Whether an attribute is a namespace declaration. */ export function isNamespaceDeclaration(attr) {
579
+ return attr.name === 'xmlns' || attr.name.startsWith('xmlns:');
580
+ }
581
+ /**
582
+ * A prefix bound to `uri` in scope at `element` and not shadowed by a
583
+ * nearer declaration of the same prefix, or null. The default namespace is
584
+ * never returned: an attribute cannot use it.
585
+ */ export function prefixForNamespace(element, uri) {
586
+ let cursor = element;
587
+ while(cursor && cursor.type === 'element'){
588
+ for (const attr of cursor.attributes){
589
+ if (!attr.name.startsWith('xmlns:') || attr.value !== uri) continue;
590
+ const prefix = attr.name.slice(6);
591
+ if (resolvePrefix(element, prefix) === uri) return prefix;
592
+ }
593
+ cursor = cursor.parent;
594
+ }
595
+ return null;
596
+ }
597
+
598
+ //# sourceMappingURL=xml.js.map