@hyperframes/parsers 0.8.156 → 0.8.158

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/dist/hfIds.d.ts CHANGED
@@ -23,5 +23,8 @@ declare function isCompositionTemplate(el: Element): boolean;
23
23
  declare function walkCompositionDescendants(root: Document | Element, visit: (el: Element) => void): void;
24
24
 
25
25
  declare function ensureHfIds(html: string): string;
26
+ /** ensureHfIds for a file someone edits: the same ids, inserted into the source text so nothing else in it changes.
27
+ * Markup whose tags the parser did not keep one-to-one falls back to ensureHfIds. */
28
+ declare function stampHfIdsInPlace(html: string): string;
26
29
 
27
- export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, walkCompositionDescendants };
30
+ export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, stampHfIdsInPlace, walkCompositionDescendants };
package/dist/hfIds.js CHANGED
@@ -96,18 +96,150 @@ function assignHfIds(body) {
96
96
 
97
97
  // src/hfIds.ts
98
98
  function ensureHfIds(html) {
99
+ const { document, wrapped } = parseFragmentOrDocument(html);
100
+ if (!document.body) return html;
101
+ assignHfIds(document.body);
102
+ return wrapped ? document.body.innerHTML || "" : document.toString();
103
+ }
104
+ function parseFragmentOrDocument(html) {
99
105
  const wrapped = !/^(?:<!doctype|<html[\s>/])/i.test(
100
106
  html.replace(/^(?:\s|<!--(?:>|->|[\s\S]*?-->))*/, "")
101
107
  );
102
108
  const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
109
+ return { document, wrapped };
110
+ }
111
+ function stampHfIdsInPlace(html) {
112
+ const { document, wrapped } = parseFragmentOrDocument(html);
103
113
  if (!document.body) return html;
114
+ const elements = wrapped ? getChildElements(document.body).flatMap(inDocumentOrder) : inDocumentOrder(document.documentElement);
115
+ const existing = elements.map((el) => el.getAttribute("data-hf-id"));
116
+ const pinned = existing.map((id) => id !== null && id.length > 0);
104
117
  assignHfIds(document.body);
105
- return wrapped ? document.body.innerHTML || "" : document.toString();
118
+ const minted = elements.map((el, n) => pinned[n] ? null : el.getAttribute("data-hf-id"));
119
+ const tags = startTags(html);
120
+ const inserts = tags && alignInserts(html, tags, elements, minted, existing);
121
+ return inserts ? spliceIds(html, inserts) : ensureHfIds(html);
122
+ }
123
+ function inDocumentOrder(el) {
124
+ return [el, ...getChildElements(el).flatMap(inDocumentOrder)];
125
+ }
126
+ var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title", "xmp", "noembed", "noframes"]);
127
+ var IMPLIED = /* @__PURE__ */ new Set(["html", "head", "body", "tbody"]);
128
+ function startTags(html) {
129
+ const lower = html.toLowerCase();
130
+ const tags = [];
131
+ let i = html.indexOf("<");
132
+ while (i >= 0) {
133
+ const name = html.startsWith("<!--", i) ? null : /^<([a-zA-Z][^\s/>]*)/.exec(html.slice(i, i + 128));
134
+ if (!name) {
135
+ i = nextTagAfter(html, i);
136
+ continue;
137
+ }
138
+ const end = closingBracket(html, i + name[0].length);
139
+ if (end < 0) return null;
140
+ const tag = name[1].toLowerCase();
141
+ tags.push({ name: tag, nameEnd: i + name[0].length, bracket: end });
142
+ const after = RAW_TEXT.has(tag) ? rawTextEnd(lower, tag, end) : end;
143
+ i = after < 0 ? -1 : html.indexOf("<", after + 1);
144
+ }
145
+ return tags;
146
+ }
147
+ function rawTextEnd(lower, tag, from) {
148
+ const marker = `</${tag}`;
149
+ for (let at = lower.indexOf(marker, from); at >= 0; at = lower.indexOf(marker, at + marker.length)) {
150
+ const next = lower[at + marker.length];
151
+ if (next !== void 0 && /[\s/>]/.test(next)) return at;
152
+ }
153
+ return -1;
154
+ }
155
+ function nextTagAfter(html, lt) {
156
+ const comment = html.startsWith("<!--", lt);
157
+ const end = comment ? html.indexOf("-->", lt + 4) : html.indexOf(">", lt);
158
+ return end < 0 ? -1 : html.indexOf("<", end + 1);
159
+ }
160
+ function closingBracket(html, from) {
161
+ let quote = "";
162
+ for (let i = from; i < html.length; i++) {
163
+ const c = html[i];
164
+ if (quote) quote = c === quote ? "" : quote;
165
+ else if (c === '"' || c === "'") quote = c;
166
+ else if (c === ">") return i;
167
+ }
168
+ return -1;
169
+ }
170
+ function alignInserts(html, tags, elements, minted, existing) {
171
+ const walked = walkAlign(html, tags, elements, minted, existing);
172
+ if (!walked) return null;
173
+ return walked.next === tags.length ? walked.inserts : null;
174
+ }
175
+ function walkAlign(html, tags, elements, minted, existing) {
176
+ const state = { inserts: [], next: 0 };
177
+ for (const [n, el] of elements.entries()) {
178
+ if (!take(state, html, tags, el, minted[n] ?? null, existing[n] === "")) return null;
179
+ }
180
+ return state;
181
+ }
182
+ function take(state, html, tags, el, id, fillEmpty) {
183
+ const step = alignOne(html, tags[state.next], el, id, fillEmpty);
184
+ if (!step) return false;
185
+ if (step.insert) state.inserts.push(step.insert);
186
+ if (step.next) state.next += 1;
187
+ return true;
188
+ }
189
+ function alignOne(html, tag, el, id, fillEmpty) {
190
+ if (tag?.name === el.tagName.toLowerCase()) return matched(html, tag, id, fillEmpty);
191
+ if (IMPLIED.has(el.tagName.toLowerCase()) && !id) return { insert: null, next: false };
192
+ return null;
193
+ }
194
+ function matched(html, tag, id, fillEmpty) {
195
+ if (!id) return { insert: null, next: true };
196
+ const placed = placeId(html, tag, id, fillEmpty);
197
+ if (!placed) return null;
198
+ return { insert: placed, next: true };
199
+ }
200
+ function placeId(html, tag, id, fillEmpty) {
201
+ if (!fillEmpty) return { at: tag.nameEnd, text: ` data-hf-id="${id}"` };
202
+ const emptyAt = emptyHfIdValue(html, tag);
203
+ if (emptyAt === null) return null;
204
+ return { at: emptyAt, text: id };
205
+ }
206
+ function emptyHfIdValue(html, tag) {
207
+ const open = outsideQuotes(html.slice(tag.nameEnd, tag.bracket));
208
+ const match = /\sdata-hf-id\s*=\s*(["'])\1/i.exec(open);
209
+ if (!match) return null;
210
+ return tag.nameEnd + match.index + match[0].length - 1;
211
+ }
212
+ function outsideQuotes(open) {
213
+ let quote = "";
214
+ let out = "";
215
+ for (const c of open) {
216
+ out += quotedChar(quote, c);
217
+ quote = quoteOf(quote, c);
218
+ }
219
+ return out;
220
+ }
221
+ function quoteOf(quote, c) {
222
+ if (quote) return c === quote ? "" : quote;
223
+ return c === '"' || c === "'" ? c : "";
224
+ }
225
+ function quotedChar(quote, c) {
226
+ if (!quote || c === quote) return c;
227
+ return " ".repeat(c.length);
228
+ }
229
+ function spliceIds(html, inserts) {
230
+ let out = "";
231
+ let from = 0;
232
+ for (const { at, text } of inserts) {
233
+ out += html.slice(from, at) + text;
234
+ from = at;
235
+ }
236
+ return out + html.slice(from);
106
237
  }
107
238
  export {
108
239
  EXCLUDED_TAGS,
109
240
  ensureHfIds,
110
241
  isCompositionTemplate,
111
242
  mintHfId,
243
+ stampHfIdsInPlace,
112
244
  walkCompositionDescendants
113
245
  };
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { isStudioHoldSet } from './gsapParser.js';
5
5
  export { getObjectArrayKeyframeTiming } from './gsapParserExports.js';
6
6
  export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEase.js';
7
7
  export { parseGsapScriptAcorn as parseGsapScript } from './gsapParserAcorn.js';
8
- export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, walkCompositionDescendants } from './hfIds.js';
8
+ export { EXCLUDED_TAGS, ensureHfIds, isCompositionTemplate, mintHfId, stampHfIdsInPlace, walkCompositionDescendants } from './hfIds.js';
9
9
  export { ParsableDocumentLike, SubCompositionValidity, SubCompositionValidityReason, checkSubCompositionUsability, resolveSubCompositionContent } from './subCompositionValidity.js';
10
10
  export { CANONICAL_AUTHORED_TIMING_ATTRIBUTES, COMPOSITION_ATTRIBUTES, COMPOSITION_CONTRACT_VERSION, ClipAttributeReader, ClipAttributeWriter, ClipTiming, ClipTimingDiagnostic, ClipTimingDiagnosticCode, ClipTimingUpdate, ClipTimingWriteError, DERIVED_TIMING_ATTRIBUTES, LEGACY_TIMING_ATTRIBUTES, ReadClipTimingOptions, ReferenceExpression, parseNumeric, parseStartExpression, readClipTiming, writeClipTiming } from './compositionContract.js';
11
11
  export { CANONICAL_FONT_DISPLAY_NAMES, FONT_ALIAS_KEYS, FONT_ALIAS_MAP, HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE, MediaSrcMutation, VIEWPORT_META_SIZE_RE, VariableUsageScan, extractMediaSrcMutations, parseCompositionVariables, resolveAliasDisplayName, scanVariableUsage } from './composition.js';
package/dist/index.js CHANGED
@@ -2471,13 +2471,144 @@ function assignHfIds(body) {
2471
2471
 
2472
2472
  // src/hfIds.ts
2473
2473
  function ensureHfIds(html) {
2474
+ const { document, wrapped } = parseFragmentOrDocument(html);
2475
+ if (!document.body) return html;
2476
+ assignHfIds(document.body);
2477
+ return wrapped ? document.body.innerHTML || "" : document.toString();
2478
+ }
2479
+ function parseFragmentOrDocument(html) {
2474
2480
  const wrapped = !/^(?:<!doctype|<html[\s>/])/i.test(
2475
2481
  html.replace(/^(?:\s|<!--(?:>|->|[\s\S]*?-->))*/, "")
2476
2482
  );
2477
2483
  const { document } = wrapped ? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`) : parseHTML(html);
2484
+ return { document, wrapped };
2485
+ }
2486
+ function stampHfIdsInPlace(html) {
2487
+ const { document, wrapped } = parseFragmentOrDocument(html);
2478
2488
  if (!document.body) return html;
2489
+ const elements = wrapped ? getChildElements(document.body).flatMap(inDocumentOrder) : inDocumentOrder(document.documentElement);
2490
+ const existing = elements.map((el) => el.getAttribute("data-hf-id"));
2491
+ const pinned = existing.map((id) => id !== null && id.length > 0);
2479
2492
  assignHfIds(document.body);
2480
- return wrapped ? document.body.innerHTML || "" : document.toString();
2493
+ const minted = elements.map((el, n) => pinned[n] ? null : el.getAttribute("data-hf-id"));
2494
+ const tags = startTags(html);
2495
+ const inserts = tags && alignInserts(html, tags, elements, minted, existing);
2496
+ return inserts ? spliceIds(html, inserts) : ensureHfIds(html);
2497
+ }
2498
+ function inDocumentOrder(el) {
2499
+ return [el, ...getChildElements(el).flatMap(inDocumentOrder)];
2500
+ }
2501
+ var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title", "xmp", "noembed", "noframes"]);
2502
+ var IMPLIED = /* @__PURE__ */ new Set(["html", "head", "body", "tbody"]);
2503
+ function startTags(html) {
2504
+ const lower = html.toLowerCase();
2505
+ const tags = [];
2506
+ let i = html.indexOf("<");
2507
+ while (i >= 0) {
2508
+ const name = html.startsWith("<!--", i) ? null : /^<([a-zA-Z][^\s/>]*)/.exec(html.slice(i, i + 128));
2509
+ if (!name) {
2510
+ i = nextTagAfter(html, i);
2511
+ continue;
2512
+ }
2513
+ const end = closingBracket(html, i + name[0].length);
2514
+ if (end < 0) return null;
2515
+ const tag = name[1].toLowerCase();
2516
+ tags.push({ name: tag, nameEnd: i + name[0].length, bracket: end });
2517
+ const after = RAW_TEXT.has(tag) ? rawTextEnd(lower, tag, end) : end;
2518
+ i = after < 0 ? -1 : html.indexOf("<", after + 1);
2519
+ }
2520
+ return tags;
2521
+ }
2522
+ function rawTextEnd(lower, tag, from) {
2523
+ const marker = `</${tag}`;
2524
+ for (let at = lower.indexOf(marker, from); at >= 0; at = lower.indexOf(marker, at + marker.length)) {
2525
+ const next = lower[at + marker.length];
2526
+ if (next !== void 0 && /[\s/>]/.test(next)) return at;
2527
+ }
2528
+ return -1;
2529
+ }
2530
+ function nextTagAfter(html, lt) {
2531
+ const comment = html.startsWith("<!--", lt);
2532
+ const end = comment ? html.indexOf("-->", lt + 4) : html.indexOf(">", lt);
2533
+ return end < 0 ? -1 : html.indexOf("<", end + 1);
2534
+ }
2535
+ function closingBracket(html, from) {
2536
+ let quote = "";
2537
+ for (let i = from; i < html.length; i++) {
2538
+ const c = html[i];
2539
+ if (quote) quote = c === quote ? "" : quote;
2540
+ else if (c === '"' || c === "'") quote = c;
2541
+ else if (c === ">") return i;
2542
+ }
2543
+ return -1;
2544
+ }
2545
+ function alignInserts(html, tags, elements, minted, existing) {
2546
+ const walked = walkAlign(html, tags, elements, minted, existing);
2547
+ if (!walked) return null;
2548
+ return walked.next === tags.length ? walked.inserts : null;
2549
+ }
2550
+ function walkAlign(html, tags, elements, minted, existing) {
2551
+ const state = { inserts: [], next: 0 };
2552
+ for (const [n, el] of elements.entries()) {
2553
+ if (!take(state, html, tags, el, minted[n] ?? null, existing[n] === "")) return null;
2554
+ }
2555
+ return state;
2556
+ }
2557
+ function take(state, html, tags, el, id, fillEmpty) {
2558
+ const step = alignOne(html, tags[state.next], el, id, fillEmpty);
2559
+ if (!step) return false;
2560
+ if (step.insert) state.inserts.push(step.insert);
2561
+ if (step.next) state.next += 1;
2562
+ return true;
2563
+ }
2564
+ function alignOne(html, tag, el, id, fillEmpty) {
2565
+ if (tag?.name === el.tagName.toLowerCase()) return matched(html, tag, id, fillEmpty);
2566
+ if (IMPLIED.has(el.tagName.toLowerCase()) && !id) return { insert: null, next: false };
2567
+ return null;
2568
+ }
2569
+ function matched(html, tag, id, fillEmpty) {
2570
+ if (!id) return { insert: null, next: true };
2571
+ const placed = placeId(html, tag, id, fillEmpty);
2572
+ if (!placed) return null;
2573
+ return { insert: placed, next: true };
2574
+ }
2575
+ function placeId(html, tag, id, fillEmpty) {
2576
+ if (!fillEmpty) return { at: tag.nameEnd, text: ` data-hf-id="${id}"` };
2577
+ const emptyAt = emptyHfIdValue(html, tag);
2578
+ if (emptyAt === null) return null;
2579
+ return { at: emptyAt, text: id };
2580
+ }
2581
+ function emptyHfIdValue(html, tag) {
2582
+ const open = outsideQuotes(html.slice(tag.nameEnd, tag.bracket));
2583
+ const match = /\sdata-hf-id\s*=\s*(["'])\1/i.exec(open);
2584
+ if (!match) return null;
2585
+ return tag.nameEnd + match.index + match[0].length - 1;
2586
+ }
2587
+ function outsideQuotes(open) {
2588
+ let quote = "";
2589
+ let out = "";
2590
+ for (const c of open) {
2591
+ out += quotedChar(quote, c);
2592
+ quote = quoteOf(quote, c);
2593
+ }
2594
+ return out;
2595
+ }
2596
+ function quoteOf(quote, c) {
2597
+ if (quote) return c === quote ? "" : quote;
2598
+ return c === '"' || c === "'" ? c : "";
2599
+ }
2600
+ function quotedChar(quote, c) {
2601
+ if (!quote || c === quote) return c;
2602
+ return " ".repeat(c.length);
2603
+ }
2604
+ function spliceIds(html, inserts) {
2605
+ let out = "";
2606
+ let from = 0;
2607
+ for (const { at, text } of inserts) {
2608
+ out += html.slice(from, at) + text;
2609
+ from = at;
2610
+ }
2611
+ return out + html.slice(from);
2481
2612
  }
2482
2613
 
2483
2614
  // src/utils/cssSelector.ts
@@ -4501,6 +4632,7 @@ export {
4501
4632
  scanHtmlOpeningTags,
4502
4633
  scanVariableUsage,
4503
4634
  serializeGsapAnimations,
4635
+ stampHfIdsInPlace,
4504
4636
  suggestMatchingPreset,
4505
4637
  timingMismatches,
4506
4638
  topLevelElements,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/parsers",
3
- "version": "0.8.156",
3
+ "version": "0.8.158",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -119,7 +119,7 @@
119
119
  "tsx": "^4.21.0",
120
120
  "typescript": "^5.0.0",
121
121
  "vitest": "^4.1.11",
122
- "@hyperframes/core": "0.8.156"
122
+ "@hyperframes/core": "0.8.158"
123
123
  },
124
124
  "scripts": {
125
125
  "build": "tsup",