@wdprlib/decompiler 0.1.0

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/index.js ADDED
@@ -0,0 +1,2103 @@
1
+ // packages/decompiler/src/html2ast/index.ts
2
+ import { parseDocument } from "htmlparser2";
3
+
4
+ // packages/decompiler/src/html2ast/context.ts
5
+ class DecompileContext {
6
+ options;
7
+ footnoteContents = new Map;
8
+ tocIndex = 0;
9
+ constructor(options = {}) {
10
+ this.options = {
11
+ inlineFootnotes: options.inlineFootnotes ?? true
12
+ };
13
+ }
14
+ nextTocIndex() {
15
+ return this.tocIndex++;
16
+ }
17
+ }
18
+
19
+ // packages/decompiler/src/html2ast/recognizer.ts
20
+ import { isTag as isTag11, isText } from "domhandler";
21
+
22
+ // packages/decompiler/src/html2ast/text.ts
23
+ import { text, lineBreak, horizontalRule } from "@wdprlib/ast";
24
+ function recognizeText(data) {
25
+ return text(data);
26
+ }
27
+ function recognizeLineBreak() {
28
+ return lineBreak();
29
+ }
30
+ function recognizeHorizontalRule() {
31
+ return horizontalRule();
32
+ }
33
+
34
+ // packages/decompiler/src/html2ast/container.ts
35
+ import { container } from "@wdprlib/ast";
36
+
37
+ // packages/decompiler/src/html2ast/utils.ts
38
+ function parseStyleProperties(style) {
39
+ return style.split(";").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
40
+ const colonIndex = s.indexOf(":");
41
+ if (colonIndex === -1)
42
+ return null;
43
+ return {
44
+ property: s.slice(0, colonIndex).trim(),
45
+ value: s.slice(colonIndex + 1).trim()
46
+ };
47
+ }).filter((p) => p !== null);
48
+ }
49
+ function hasOnlyStyleProperty(style, property) {
50
+ const props = parseStyleProperties(style);
51
+ return props.length === 1 && props[0]?.property === property;
52
+ }
53
+ function getStyleValue(style, property) {
54
+ const props = parseStyleProperties(style);
55
+ const found = props.find((p) => p.property === property);
56
+ return found?.value ?? null;
57
+ }
58
+ function getTextContent(node) {
59
+ let result = "";
60
+ for (const child of node.childNodes) {
61
+ if (child.type === "text") {
62
+ result += child.data;
63
+ } else if ("childNodes" in child) {
64
+ result += getTextContent(child);
65
+ }
66
+ }
67
+ return result.replace(/\u00a0/g, " ");
68
+ }
69
+
70
+ // packages/decompiler/src/html2ast/container.ts
71
+ function containerUnchecked(type, elements, attributes = {}) {
72
+ return container(type, elements, attributes);
73
+ }
74
+ function recognizeParagraph(node, rec) {
75
+ const attrs = extractAttributes(node, []);
76
+ const elements = rec(node);
77
+ return container("paragraph", elements, attrs);
78
+ }
79
+ function recognizeInlineFormat(node, type, rec) {
80
+ const attrs = extractAttributes(node, ["style"]);
81
+ const elements = rec(node);
82
+ return containerUnchecked(type, elements, attrs);
83
+ }
84
+ function recognizeSpanContainer(node, rec) {
85
+ const style = node.attribs.style ?? "";
86
+ if (hasOnlyStyleProperty(style, "text-decoration")) {
87
+ const value = getStyleValue(style, "text-decoration");
88
+ if (value === "underline") {
89
+ return recognizeInlineFormat(node, "underline", rec);
90
+ }
91
+ if (value === "line-through") {
92
+ return recognizeInlineFormat(node, "strikethrough", rec);
93
+ }
94
+ }
95
+ if (hasOnlyStyleProperty(style, "font-size")) {
96
+ const attrs2 = extractAttributes(node, []);
97
+ const elements2 = rec(node);
98
+ return container("size", elements2, attrs2);
99
+ }
100
+ if (hasOnlyStyleProperty(style, "white-space")) {
101
+ const value = getStyleValue(style, "white-space");
102
+ if (value === "pre-wrap") {
103
+ const textContent = getTextContent(node);
104
+ return { element: "raw", data: textContent };
105
+ }
106
+ }
107
+ if (style.includes("display: none") || style.includes("display:none")) {
108
+ const attrs2 = extractAttributes(node, []);
109
+ const elements2 = rec(node);
110
+ return container("span", elements2, attrs2);
111
+ }
112
+ if (style.includes("visibility: hidden") || style.includes("visibility:hidden")) {
113
+ const attrs2 = extractAttributes(node, []);
114
+ const elements2 = rec(node);
115
+ return container("span", elements2, attrs2);
116
+ }
117
+ const attrs = extractAttributes(node, []);
118
+ const elements = rec(node);
119
+ return container("span", elements, attrs);
120
+ }
121
+ function recognizeBlockquote(node, rec) {
122
+ const attrs = extractAttributes(node, []);
123
+ const elements = rec(node);
124
+ return container("blockquote", elements, attrs);
125
+ }
126
+ function recognizeDiv(node, rec) {
127
+ const style = node.attribs.style ?? "";
128
+ const alignMatch = style.match(/text-align:\s*(left|right|center|justify)/);
129
+ if (alignMatch) {
130
+ const align = alignMatch[1];
131
+ const attrs2 = extractAttributes(node, ["style"]);
132
+ const elements2 = rec(node);
133
+ return container({ align }, elements2, attrs2);
134
+ }
135
+ const attrs = extractAttributes(node, []);
136
+ const elements = rec(node);
137
+ return container("div", elements, attrs);
138
+ }
139
+ function extractAttributes(node, exclude) {
140
+ const attrs = {};
141
+ const excludeSet = new Set(exclude);
142
+ for (const [key, value] of Object.entries(node.attribs)) {
143
+ if (excludeSet.has(key))
144
+ continue;
145
+ if (key === "id" && /^toc\d+/.test(value))
146
+ continue;
147
+ attrs[key] = value;
148
+ }
149
+ return Object.keys(attrs).length > 0 ? attrs : {};
150
+ }
151
+
152
+ // packages/decompiler/src/html2ast/heading.ts
153
+ import { heading } from "@wdprlib/ast";
154
+ import { isTag } from "domhandler";
155
+ function recognizeHeading(node, ctx, rec) {
156
+ const levelStr = node.name.charAt(1);
157
+ const level = parseInt(levelStr, 10);
158
+ const id = node.attribs.id ?? "";
159
+ const hasToc = /^toc\d+/.test(id);
160
+ if (hasToc) {
161
+ ctx.nextTocIndex();
162
+ }
163
+ const innerChildren = unwrapSpan(node);
164
+ const elements = innerChildren.flatMap((child) => {
165
+ if (isTag(child)) {
166
+ return rec(child);
167
+ }
168
+ if (child.type === "text") {
169
+ const data = child.data;
170
+ if (data.trim() === "")
171
+ return [];
172
+ return [{ element: "text", data }];
173
+ }
174
+ return [];
175
+ });
176
+ return heading(level, elements, hasToc);
177
+ }
178
+ function unwrapSpan(node) {
179
+ for (const child of node.childNodes) {
180
+ if (isTag(child) && child.name === "span") {
181
+ return child.childNodes;
182
+ }
183
+ }
184
+ return node.childNodes;
185
+ }
186
+
187
+ // packages/decompiler/src/html2ast/link.ts
188
+ import { link } from "@wdprlib/ast";
189
+ function recognizeLink(node, _ctx, rec) {
190
+ const href = node.attribs.href ?? "";
191
+ const nameAttr = node.attribs.name;
192
+ if (nameAttr && !href) {
193
+ return [{ element: "anchor-name", data: nameAttr }];
194
+ }
195
+ if (href.startsWith("mailto:")) {
196
+ const email = href.slice(7);
197
+ return [{ element: "email", data: email }];
198
+ }
199
+ const target = parseTarget(node.attribs.target);
200
+ const labelElements = rec(node);
201
+ const labelText = extractTextContent(labelElements);
202
+ if (href === "javascript:;") {
203
+ const label2 = labelText ? { text: labelText } : { text: "" };
204
+ return [link(href, label2, { type: "anchor", target })];
205
+ }
206
+ if (href.startsWith("/") && !href.startsWith("//")) {
207
+ const pageName = href.slice(1).replace(/#.*$/, "");
208
+ const extra = href.includes("#") ? href.slice(href.indexOf("#")) : null;
209
+ const location = { site: null, page: pageName };
210
+ const label2 = labelText && labelText !== pageName ? { text: labelText } : "page";
211
+ return [link(location, label2, { type: "page", extra, target })];
212
+ }
213
+ const linkType = "direct";
214
+ const label = labelText && labelText !== href ? { text: labelText } : { url: href };
215
+ return [link(href, label, { type: linkType, target })];
216
+ }
217
+ function parseTarget(target) {
218
+ switch (target) {
219
+ case "_blank":
220
+ return "new-tab";
221
+ case "_parent":
222
+ return "parent";
223
+ case "_top":
224
+ return "top";
225
+ case "_self":
226
+ return "same";
227
+ default:
228
+ return null;
229
+ }
230
+ }
231
+ function extractTextContent(elements) {
232
+ let result = "";
233
+ for (const el of elements) {
234
+ if (el.element === "text") {
235
+ result += el.data;
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+
241
+ // packages/decompiler/src/html2ast/image.ts
242
+ import { isTag as isTag2 } from "domhandler";
243
+ function recognizeImage(node) {
244
+ const src = node.attribs.src ?? "";
245
+ const source = parseImageSource(src);
246
+ const attrs = extractImageAttributes(node);
247
+ const linkLocation = findImageLink(node);
248
+ const alignment = findImageAlignment(node);
249
+ return {
250
+ element: "image",
251
+ data: {
252
+ source,
253
+ link: linkLocation,
254
+ alignment,
255
+ attributes: attrs
256
+ }
257
+ };
258
+ }
259
+ function recognizeImageContainer(node) {
260
+ const className = node.attribs.class ?? "";
261
+ if (!className.includes("image-container"))
262
+ return null;
263
+ const img = findDescendant(node, "img");
264
+ if (!img)
265
+ return null;
266
+ const src = img.attribs.src ?? "";
267
+ const source = parseImageSource(src);
268
+ const attrs = extractImageAttributes(img);
269
+ const linkLocation = findImageLink(img);
270
+ const alignment = parseAlignmentFromClass(className);
271
+ return {
272
+ element: "image",
273
+ data: {
274
+ source,
275
+ link: linkLocation,
276
+ alignment,
277
+ attributes: attrs
278
+ }
279
+ };
280
+ }
281
+ function parseImageSource(src) {
282
+ if (src.startsWith("http://") || src.startsWith("https://") || src.startsWith("/")) {
283
+ return { type: "url", data: src };
284
+ }
285
+ return { type: "url", data: src };
286
+ }
287
+ function extractImageAttributes(node) {
288
+ const attrs = {};
289
+ if (node.attribs.alt)
290
+ attrs.alt = node.attribs.alt;
291
+ if (node.attribs.width)
292
+ attrs.width = node.attribs.width;
293
+ if (node.attribs.height)
294
+ attrs.height = node.attribs.height;
295
+ if (node.attribs.class && node.attribs.class !== "image") {
296
+ attrs.class = node.attribs.class;
297
+ }
298
+ if (node.attribs.style)
299
+ attrs.style = node.attribs.style;
300
+ return attrs;
301
+ }
302
+ function findImageLink(imgNode) {
303
+ const parent = imgNode.parent;
304
+ if (parent && isTag2(parent) && parent.name === "a") {
305
+ const href = parent.attribs.href;
306
+ if (href)
307
+ return href;
308
+ }
309
+ return null;
310
+ }
311
+ function findImageAlignment(imgNode) {
312
+ const parent = imgNode.parent;
313
+ if (parent && isTag2(parent)) {
314
+ const containerNode = parent.name === "a" ? parent.parent : parent;
315
+ if (containerNode && isTag2(containerNode)) {
316
+ const className = containerNode.attribs.class ?? "";
317
+ if (className.includes("image-container")) {
318
+ return parseAlignmentFromClass(className);
319
+ }
320
+ }
321
+ }
322
+ return null;
323
+ }
324
+ function parseAlignmentFromClass(className) {
325
+ const floatMatch = className.match(/float(left|right|center)/);
326
+ if (floatMatch) {
327
+ return { align: floatMatch[1], float: true };
328
+ }
329
+ const alignMatch = className.match(/align(left|right|center)/);
330
+ if (alignMatch) {
331
+ return { align: alignMatch[1], float: false };
332
+ }
333
+ return null;
334
+ }
335
+ function findDescendant(node, tagName) {
336
+ for (const child of node.childNodes) {
337
+ if (isTag2(child)) {
338
+ if (child.name === tagName)
339
+ return child;
340
+ const found = findDescendant(child, tagName);
341
+ if (found)
342
+ return found;
343
+ }
344
+ }
345
+ return null;
346
+ }
347
+
348
+ // packages/decompiler/src/html2ast/list.ts
349
+ import { list, listItemElements, listItemSubList } from "@wdprlib/ast";
350
+ import { isTag as isTag3 } from "domhandler";
351
+ function recognizeList(node, ctx, rec) {
352
+ const type = node.name === "ol" ? "numbered" : "bullet";
353
+ const items = recognizeListItems(node, ctx, rec);
354
+ return list(type, items);
355
+ }
356
+ function recognizeListItems(node, ctx, rec) {
357
+ const items = [];
358
+ for (const child of node.childNodes) {
359
+ if (!isTag3(child))
360
+ continue;
361
+ if (child.name === "li") {
362
+ items.push(recognizeListItem(child, ctx, rec));
363
+ }
364
+ }
365
+ return items;
366
+ }
367
+ function recognizeListItem(node, ctx, rec) {
368
+ for (const child of node.childNodes) {
369
+ if (isTag3(child) && (child.name === "ul" || child.name === "ol")) {
370
+ const subType = child.name === "ol" ? "numbered" : "bullet";
371
+ const subItems = recognizeListItems(child, ctx, rec);
372
+ const subData = { type: subType, attributes: {}, items: subItems };
373
+ const precedingElements = collectPrecedingElements(node, child, rec);
374
+ if (precedingElements.length > 0) {
375
+ return listItemElements([
376
+ ...precedingElements,
377
+ { element: "list", data: subData }
378
+ ]);
379
+ }
380
+ return listItemSubList(subData);
381
+ }
382
+ }
383
+ const elements = rec(node);
384
+ return listItemElements(elements);
385
+ }
386
+ function collectPrecedingElements(parent, stopAt, rec) {
387
+ const elements = [];
388
+ for (const child of parent.childNodes) {
389
+ if (child === stopAt)
390
+ break;
391
+ if (isTag3(child)) {
392
+ if (child.name !== "ul" && child.name !== "ol") {
393
+ elements.push(...rec(child));
394
+ }
395
+ } else if (child.type === "text") {
396
+ const data = child.data.trim();
397
+ if (data) {
398
+ elements.push({ element: "text", data: child.data });
399
+ }
400
+ }
401
+ }
402
+ return elements;
403
+ }
404
+ function recognizeDefinitionList(node, _ctx, rec) {
405
+ const items = [];
406
+ let currentKey = [];
407
+ let currentKeyString = "";
408
+ for (const child of node.childNodes) {
409
+ if (!isTag3(child))
410
+ continue;
411
+ if (child.name === "dt") {
412
+ currentKey = rec(child);
413
+ currentKeyString = extractText(child);
414
+ } else if (child.name === "dd") {
415
+ const value = rec(child);
416
+ items.push({
417
+ key_string: currentKeyString,
418
+ key: currentKey,
419
+ value
420
+ });
421
+ currentKey = [];
422
+ currentKeyString = "";
423
+ }
424
+ }
425
+ return { element: "definition-list", data: items };
426
+ }
427
+ function extractText(node) {
428
+ let result = "";
429
+ for (const child of node.childNodes) {
430
+ if (child.type === "text") {
431
+ result += child.data;
432
+ } else if (isTag3(child)) {
433
+ result += extractText(child);
434
+ }
435
+ }
436
+ return result;
437
+ }
438
+
439
+ // packages/decompiler/src/html2ast/table.ts
440
+ import { isTag as isTag4 } from "domhandler";
441
+ function recognizeTable(node, _ctx, rec) {
442
+ const rows = [];
443
+ const rowContainers = findRowContainers(node);
444
+ for (const container2 of rowContainers) {
445
+ for (const child of container2.childNodes) {
446
+ if (!isTag4(child) || child.name !== "tr")
447
+ continue;
448
+ rows.push(recognizeRow(child, rec));
449
+ }
450
+ }
451
+ const data = {
452
+ attributes: {},
453
+ rows
454
+ };
455
+ return { element: "table", data };
456
+ }
457
+ function findRowContainers(table) {
458
+ const containers = [];
459
+ for (const child of table.childNodes) {
460
+ if (!isTag4(child))
461
+ continue;
462
+ if (child.name === "tbody" || child.name === "thead" || child.name === "tfoot") {
463
+ containers.push(child);
464
+ } else if (child.name === "tr") {
465
+ containers.push(table);
466
+ return containers;
467
+ }
468
+ }
469
+ if (containers.length === 0)
470
+ containers.push(table);
471
+ return containers;
472
+ }
473
+ function recognizeRow(node, rec) {
474
+ const cells = [];
475
+ for (const child of node.childNodes) {
476
+ if (!isTag4(child))
477
+ continue;
478
+ if (child.name === "td" || child.name === "th") {
479
+ cells.push(recognizeCell(child, rec));
480
+ }
481
+ }
482
+ return { attributes: {}, cells };
483
+ }
484
+ function recognizeCell(node, rec) {
485
+ const header = node.name === "th";
486
+ const colspanStr = node.attribs.colspan;
487
+ const columnSpan = colspanStr ? parseInt(colspanStr, 10) : 1;
488
+ const style = node.attribs.style ?? "";
489
+ const alignMatch = style.match(/text-align:\s*(left|right|center|justify)/);
490
+ const align = alignMatch ? alignMatch[1] : null;
491
+ const elements = rec(node);
492
+ return {
493
+ header,
494
+ "column-span": columnSpan,
495
+ align,
496
+ attributes: {},
497
+ elements
498
+ };
499
+ }
500
+
501
+ // packages/decompiler/src/html2ast/code.ts
502
+ import { isTag as isTag5 } from "domhandler";
503
+ function recognizeCodeBlock(node) {
504
+ const data = extractCodeBlockData(node);
505
+ return { element: "code", data };
506
+ }
507
+ function extractCodeBlockData(node) {
508
+ const hlMain = findByClass(node, "hl-main");
509
+ if (hlMain) {
510
+ const pre2 = findTag(hlMain, "pre");
511
+ const contents = pre2 ? extractPlainText(pre2) : "";
512
+ return { contents, language: null, name: null };
513
+ }
514
+ const pre = findTag(node, "pre");
515
+ if (pre) {
516
+ const code = findTag(pre, "code");
517
+ const contents = code ? extractPlainText(code) : extractPlainText(pre);
518
+ return { contents, language: null, name: null };
519
+ }
520
+ return { contents: "", language: null, name: null };
521
+ }
522
+ function findByClass(node, className) {
523
+ for (const child of node.childNodes) {
524
+ if (!isTag5(child))
525
+ continue;
526
+ if ((child.attribs.class ?? "").includes(className))
527
+ return child;
528
+ const found = findByClass(child, className);
529
+ if (found)
530
+ return found;
531
+ }
532
+ return null;
533
+ }
534
+ function findTag(node, tagName) {
535
+ for (const child of node.childNodes) {
536
+ if (isTag5(child) && child.name === tagName)
537
+ return child;
538
+ }
539
+ return null;
540
+ }
541
+ function extractPlainText(node) {
542
+ let result = "";
543
+ for (const child of node.childNodes) {
544
+ if (child.type === "text") {
545
+ result += child.data;
546
+ } else if (isTag5(child)) {
547
+ result += extractPlainText(child);
548
+ }
549
+ }
550
+ return result;
551
+ }
552
+
553
+ // packages/decompiler/src/html2ast/collapsible.ts
554
+ import { isTag as isTag6 } from "domhandler";
555
+ function recognizeCollapsible(node, _ctx, rec) {
556
+ let showText = null;
557
+ let hideText = null;
558
+ let startOpen = false;
559
+ let showTop = true;
560
+ let showBottom = false;
561
+ let contentElements = [];
562
+ const folded = findByClass2(node, "collapsible-block-folded");
563
+ const unfolded = findByClass2(node, "collapsible-block-unfolded");
564
+ if (folded) {
565
+ const style = folded.attribs.style ?? "";
566
+ startOpen = style.includes("display:none") || style.includes("display: none");
567
+ const linkText = extractLinkText(folded);
568
+ if (linkText) {
569
+ showText = cleanCollapsibleLabel(linkText);
570
+ }
571
+ }
572
+ if (unfolded) {
573
+ const content = findByClass2(unfolded, "collapsible-block-content");
574
+ if (content) {
575
+ contentElements = rec(content);
576
+ }
577
+ const linkText = extractLinkText(unfolded);
578
+ if (linkText) {
579
+ hideText = cleanCollapsibleLabel(linkText);
580
+ }
581
+ const unfoldedLinks = findAllByClass(unfolded, "collapsible-block-unfolded-link");
582
+ if (unfoldedLinks.length === 2) {
583
+ showTop = true;
584
+ showBottom = true;
585
+ } else if (unfoldedLinks.length === 1) {
586
+ const link2 = unfoldedLinks[0];
587
+ const contentDiv = findByClass2(unfolded, "collapsible-block-content");
588
+ if (contentDiv) {
589
+ const linkIndex = getChildIndex(unfolded, link2);
590
+ const contentIndex = getChildIndex(unfolded, contentDiv);
591
+ showTop = linkIndex < contentIndex;
592
+ showBottom = linkIndex > contentIndex;
593
+ }
594
+ }
595
+ }
596
+ const isDefaultShow = showText === null || showText === "+ show block";
597
+ const isDefaultHide = hideText === null || hideText === "- hide block";
598
+ const data = {
599
+ elements: contentElements,
600
+ attributes: {},
601
+ "start-open": startOpen,
602
+ "show-text": isDefaultShow ? null : showText,
603
+ "hide-text": isDefaultHide ? null : hideText,
604
+ "show-top": showTop,
605
+ "show-bottom": showBottom
606
+ };
607
+ return { element: "collapsible", data };
608
+ }
609
+ function cleanCollapsibleLabel(text2) {
610
+ return text2.replace(/\u00a0/g, " ").replace(/^[+–-]\s*/, "").replace(/\s+/g, " ").trim();
611
+ }
612
+ function extractLinkText(node) {
613
+ const link2 = findByClass2(node, "collapsible-block-link");
614
+ if (!link2)
615
+ return null;
616
+ return getTextContent2(link2);
617
+ }
618
+ function getTextContent2(node) {
619
+ let result = "";
620
+ for (const child of node.childNodes) {
621
+ if (child.type === "text") {
622
+ result += child.data;
623
+ } else if (isTag6(child)) {
624
+ result += getTextContent2(child);
625
+ }
626
+ }
627
+ return result;
628
+ }
629
+ function findByClass2(node, className) {
630
+ for (const child of node.childNodes) {
631
+ if (!isTag6(child))
632
+ continue;
633
+ const classes = (child.attribs.class ?? "").split(/\s+/);
634
+ if (classes.includes(className))
635
+ return child;
636
+ const found = findByClass2(child, className);
637
+ if (found)
638
+ return found;
639
+ }
640
+ return null;
641
+ }
642
+ function findAllByClass(node, className) {
643
+ const results = [];
644
+ for (const child of node.childNodes) {
645
+ if (!isTag6(child))
646
+ continue;
647
+ const classes = (child.attribs.class ?? "").split(/\s+/);
648
+ if (classes.includes(className))
649
+ results.push(child);
650
+ }
651
+ return results;
652
+ }
653
+ function getChildIndex(parent, child) {
654
+ let index = 0;
655
+ for (const c of parent.childNodes) {
656
+ if (c === child)
657
+ return index;
658
+ if (isTag6(c))
659
+ index++;
660
+ }
661
+ return -1;
662
+ }
663
+
664
+ // packages/decompiler/src/html2ast/tab-view.ts
665
+ import { isTag as isTag7 } from "domhandler";
666
+ function recognizeTabView(node, _ctx, rec) {
667
+ const labels = [];
668
+ const tabs = [];
669
+ const nav = findByClass3(node, "yui-nav");
670
+ if (nav) {
671
+ for (const li of nav.childNodes) {
672
+ if (!isTag7(li) || li.name !== "li")
673
+ continue;
674
+ const em = findDescendantTag(li, "em");
675
+ if (em) {
676
+ labels.push(getTextContent3(em));
677
+ }
678
+ }
679
+ }
680
+ const content = findByClass3(node, "yui-content");
681
+ if (content) {
682
+ let tabIndex = 0;
683
+ for (const child of content.childNodes) {
684
+ if (!isTag7(child) || child.name !== "div")
685
+ continue;
686
+ const label = labels[tabIndex] ?? `Tab ${tabIndex + 1}`;
687
+ const elements = rec(child);
688
+ tabs.push({ label, elements });
689
+ tabIndex++;
690
+ }
691
+ }
692
+ return { element: "tab-view", data: tabs };
693
+ }
694
+ function findByClass3(node, className) {
695
+ for (const child of node.childNodes) {
696
+ if (!isTag7(child))
697
+ continue;
698
+ const classes = (child.attribs.class ?? "").split(/\s+/);
699
+ if (classes.includes(className))
700
+ return child;
701
+ const found = findByClass3(child, className);
702
+ if (found)
703
+ return found;
704
+ }
705
+ return null;
706
+ }
707
+ function findDescendantTag(node, tagName) {
708
+ for (const child of node.childNodes) {
709
+ if (isTag7(child)) {
710
+ if (child.name === tagName)
711
+ return child;
712
+ const found = findDescendantTag(child, tagName);
713
+ if (found)
714
+ return found;
715
+ }
716
+ }
717
+ return null;
718
+ }
719
+ function getTextContent3(node) {
720
+ let result = "";
721
+ for (const child of node.childNodes) {
722
+ if (child.type === "text") {
723
+ result += child.data;
724
+ } else if (isTag7(child)) {
725
+ result += getTextContent3(child);
726
+ }
727
+ }
728
+ return result;
729
+ }
730
+
731
+ // packages/decompiler/src/html2ast/footnote.ts
732
+ import { isTag as isTag8 } from "domhandler";
733
+ function recognizeFootnoteRef(node, _ctx) {
734
+ const link2 = findDescendantTag2(node, "a");
735
+ const refText = link2 ? getTextContent4(link2) : "1";
736
+ const refNum = parseInt(refText, 10) || 1;
737
+ return { element: "footnote-ref", data: refNum };
738
+ }
739
+ function recognizeFootnotesFooter(node, ctx, rec) {
740
+ for (const child of node.childNodes) {
741
+ if (!isTag8(child))
742
+ continue;
743
+ const classes = (child.attribs.class ?? "").split(/\s+/);
744
+ if (!classes.includes("footnote-footer"))
745
+ continue;
746
+ const id = child.attribs.id ?? "";
747
+ const match = id.match(/footnote-(\d+)/);
748
+ const num = match ? parseInt(match[1], 10) : 0;
749
+ if (num === 0)
750
+ continue;
751
+ const content = extractFootnoteContent(child, rec);
752
+ ctx.footnoteContents.set(num, content);
753
+ }
754
+ return [{ element: "footnote-block", data: { title: null } }];
755
+ }
756
+ function extractFootnoteContent(node, rec) {
757
+ const elements = [];
758
+ let pastLink = false;
759
+ let pastDotSpace = false;
760
+ for (const child of node.childNodes) {
761
+ if (!pastLink) {
762
+ if (isTag8(child) && child.name === "a") {
763
+ pastLink = true;
764
+ continue;
765
+ }
766
+ continue;
767
+ }
768
+ if (!pastDotSpace) {
769
+ if (child.type === "text") {
770
+ const data = child.data.replace(/^\.\s*/, "");
771
+ if (data) {
772
+ elements.push({ element: "text", data });
773
+ }
774
+ pastDotSpace = true;
775
+ continue;
776
+ }
777
+ }
778
+ if (isTag8(child)) {
779
+ elements.push(...rec(child));
780
+ } else if (child.type === "text") {
781
+ const data = child.data;
782
+ if (data.trim()) {
783
+ elements.push({ element: "text", data });
784
+ }
785
+ }
786
+ }
787
+ return elements;
788
+ }
789
+ function findDescendantTag2(node, tagName) {
790
+ for (const child of node.childNodes) {
791
+ if (isTag8(child)) {
792
+ if (child.name === tagName)
793
+ return child;
794
+ const found = findDescendantTag2(child, tagName);
795
+ if (found)
796
+ return found;
797
+ }
798
+ }
799
+ return null;
800
+ }
801
+ function getTextContent4(node) {
802
+ let result = "";
803
+ for (const child of node.childNodes) {
804
+ if (child.type === "text") {
805
+ result += child.data;
806
+ } else if (isTag8(child)) {
807
+ result += getTextContent4(child);
808
+ }
809
+ }
810
+ return result;
811
+ }
812
+
813
+ // packages/decompiler/src/html2ast/math.ts
814
+ import { isTag as isTag9 } from "domhandler";
815
+ function recognizeMathBlock(node) {
816
+ const source = findMathSource(node);
817
+ const name = node.attribs["data-name"] ?? null;
818
+ const data = {
819
+ name,
820
+ "latex-source": source
821
+ };
822
+ return { element: "math", data };
823
+ }
824
+ function recognizeMathInline(node) {
825
+ const source = findMathSource(node);
826
+ const data = {
827
+ "latex-source": source
828
+ };
829
+ return { element: "math-inline", data };
830
+ }
831
+ function findMathSource(node) {
832
+ for (const child of node.childNodes) {
833
+ if (!isTag9(child))
834
+ continue;
835
+ if (child.name === "code" && (child.attribs.class ?? "").includes("math-source")) {
836
+ return getTextContent5(child);
837
+ }
838
+ }
839
+ return "";
840
+ }
841
+ function getTextContent5(node) {
842
+ let result = "";
843
+ for (const child of node.childNodes) {
844
+ if (child.type === "text") {
845
+ result += child.data;
846
+ } else if (isTag9(child)) {
847
+ result += getTextContent5(child);
848
+ }
849
+ }
850
+ return result;
851
+ }
852
+
853
+ // packages/decompiler/src/html2ast/misc.ts
854
+ import { isTag as isTag10 } from "domhandler";
855
+ function recognizeColor(node, _ctx, rec) {
856
+ const style = node.attribs.style ?? "";
857
+ const match = style.match(/color:\s*([^;]+)/);
858
+ const color = match ? match[1].trim() : "inherit";
859
+ const elements = rec(node);
860
+ const data = { color, elements };
861
+ return { element: "color", data };
862
+ }
863
+ function recognizeClearFloat(node) {
864
+ const style = node.attribs.style ?? "";
865
+ if (!style.includes("clear:") && !style.includes("clear :"))
866
+ return null;
867
+ let direction = "both";
868
+ if (style.includes("clear:left") || style.includes("clear: left")) {
869
+ direction = "left";
870
+ } else if (style.includes("clear:right") || style.includes("clear: right")) {
871
+ direction = "right";
872
+ }
873
+ return { element: "clear-float", data: direction };
874
+ }
875
+ function recognizeEmbed(node) {
876
+ const className = node.attribs.class ?? "";
877
+ if (className.includes("embed-youtube")) {
878
+ const iframe = findDescendantTag3(node, "iframe");
879
+ if (!iframe)
880
+ return null;
881
+ const src = iframe.attribs.src ?? "";
882
+ const match = src.match(/youtube\.com\/embed\/([a-zA-Z0-9_-]+)/);
883
+ if (!match)
884
+ return null;
885
+ const data = { embed: "youtube", data: { "video-id": match[1] } };
886
+ return { element: "embed", data };
887
+ }
888
+ if (className.includes("embed-vimeo")) {
889
+ const iframe = findDescendantTag3(node, "iframe");
890
+ if (!iframe)
891
+ return null;
892
+ const src = iframe.attribs.src ?? "";
893
+ const match = src.match(/vimeo\.com\/video\/([a-zA-Z0-9_-]+)/);
894
+ if (!match)
895
+ return null;
896
+ const data = { embed: "vimeo", data: { "video-id": match[1] } };
897
+ return { element: "embed", data };
898
+ }
899
+ return null;
900
+ }
901
+ function recognizeIframe(node) {
902
+ const url = node.attribs.src ?? "";
903
+ const attrs = {};
904
+ const allowedAttrs = ["width", "height", "scrolling", "frameborder", "class", "style"];
905
+ for (const attr of allowedAttrs) {
906
+ if (node.attribs[attr]) {
907
+ attrs[attr] = node.attribs[attr];
908
+ }
909
+ }
910
+ const data = { url, attributes: attrs };
911
+ return { element: "iframe", data };
912
+ }
913
+ function recognizeStyle(node) {
914
+ const content = getTextContent6(node);
915
+ return { element: "style", data: content };
916
+ }
917
+ function findDescendantTag3(node, tagName) {
918
+ for (const child of node.childNodes) {
919
+ if (isTag10(child)) {
920
+ if (child.name === tagName)
921
+ return child;
922
+ const found = findDescendantTag3(child, tagName);
923
+ if (found)
924
+ return found;
925
+ }
926
+ }
927
+ return null;
928
+ }
929
+ function getTextContent6(node) {
930
+ let result = "";
931
+ for (const child of node.childNodes) {
932
+ if (child.type === "text") {
933
+ result += child.data;
934
+ } else if (isTag10(child)) {
935
+ result += getTextContent6(child);
936
+ }
937
+ }
938
+ return result;
939
+ }
940
+
941
+ // packages/decompiler/src/html2ast/recognizer.ts
942
+ function recognizeNode(node, ctx) {
943
+ if (isText(node)) {
944
+ const data = node.data;
945
+ if (/^\s*$/.test(data)) {
946
+ if (/[ \u00a0]/.test(data)) {
947
+ return [recognizeText(" ")];
948
+ }
949
+ return [];
950
+ }
951
+ return [recognizeText(data)];
952
+ }
953
+ if (!isTag11(node))
954
+ return [];
955
+ return recognizeElement(node, ctx);
956
+ }
957
+ function recognizeElement(node, ctx) {
958
+ const rec = (n) => recognizeChildren(n, ctx);
959
+ const className = node.attribs.class ?? "";
960
+ switch (node.name) {
961
+ case "p": {
962
+ const iframe = findDirectChild(node, "iframe");
963
+ if (iframe)
964
+ return [recognizeIframe(iframe)];
965
+ return [recognizeParagraph(node, rec)];
966
+ }
967
+ case "br":
968
+ return [recognizeLineBreak()];
969
+ case "hr":
970
+ return [recognizeHorizontalRule()];
971
+ case "h1":
972
+ case "h2":
973
+ case "h3":
974
+ case "h4":
975
+ case "h5":
976
+ case "h6":
977
+ return [recognizeHeading(node, ctx, rec)];
978
+ case "strong":
979
+ case "b":
980
+ return [recognizeInlineFormat(node, "bold", rec)];
981
+ case "em":
982
+ case "i":
983
+ return [recognizeInlineFormat(node, "italics", rec)];
984
+ case "sup":
985
+ if (className.includes("footnoteref")) {
986
+ return [recognizeFootnoteRef(node, ctx)];
987
+ }
988
+ return [recognizeInlineFormat(node, "superscript", rec)];
989
+ case "sub":
990
+ return [recognizeInlineFormat(node, "subscript", rec)];
991
+ case "tt":
992
+ return [recognizeInlineFormat(node, "monospace", rec)];
993
+ case "mark":
994
+ return [recognizeInlineFormat(node, "mark", rec)];
995
+ case "ins":
996
+ return [recognizeInlineFormat(node, "insertion", rec)];
997
+ case "del":
998
+ return [recognizeInlineFormat(node, "deletion", rec)];
999
+ case "ruby":
1000
+ return [recognizeInlineFormat(node, "ruby", rec)];
1001
+ case "rt":
1002
+ return [recognizeInlineFormat(node, "ruby-text", rec)];
1003
+ case "span":
1004
+ return recognizeSpanDispatch(node, ctx, rec);
1005
+ case "a":
1006
+ return recognizeLink(node, ctx, rec);
1007
+ case "img":
1008
+ return [recognizeImage(node)];
1009
+ case "blockquote":
1010
+ return [recognizeBlockquote(node, rec)];
1011
+ case "div":
1012
+ return recognizeDivDispatch(node, ctx, rec);
1013
+ case "ul":
1014
+ case "ol":
1015
+ return [recognizeList(node, ctx, rec)];
1016
+ case "dl":
1017
+ return [recognizeDefinitionList(node, ctx, rec)];
1018
+ case "table":
1019
+ return [recognizeTable(node, ctx, rec)];
1020
+ case "iframe":
1021
+ return [recognizeIframe(node)];
1022
+ case "style":
1023
+ return [recognizeStyle(node)];
1024
+ default:
1025
+ return recognizeChildren(node, ctx);
1026
+ }
1027
+ }
1028
+ function recognizeSpanDispatch(node, ctx, rec) {
1029
+ const className = node.attribs.class ?? "";
1030
+ const style = node.attribs.style ?? "";
1031
+ if (className.includes("math-inline")) {
1032
+ return [recognizeMathInline(node)];
1033
+ }
1034
+ if (hasOnlyStyleProperty(style, "color")) {
1035
+ return [recognizeColor(node, ctx, rec)];
1036
+ }
1037
+ const result = recognizeSpanContainer(node, rec);
1038
+ if (result)
1039
+ return [result];
1040
+ return rec(node);
1041
+ }
1042
+ function recognizeDivDispatch(node, ctx, rec) {
1043
+ const className = node.attribs.class ?? "";
1044
+ const style = node.attribs.style ?? "";
1045
+ if (className === "code" || className.startsWith("code ")) {
1046
+ return [recognizeCodeBlock(node)];
1047
+ }
1048
+ if (className.includes("collapsible-block") && !className.includes("collapsible-block-")) {
1049
+ return [recognizeCollapsible(node, ctx, rec)];
1050
+ }
1051
+ if (className.includes("yui-navset")) {
1052
+ return [recognizeTabView(node, ctx, rec)];
1053
+ }
1054
+ if (className.includes("image-container")) {
1055
+ const img = recognizeImageContainer(node);
1056
+ if (img)
1057
+ return [img];
1058
+ }
1059
+ if (className.includes("footnotes-footer")) {
1060
+ return recognizeFootnotesFooter(node, ctx, rec);
1061
+ }
1062
+ if (className.includes("math-block")) {
1063
+ return [recognizeMathBlock(node)];
1064
+ }
1065
+ if (className.includes("embed-")) {
1066
+ const embed = recognizeEmbed(node);
1067
+ if (embed)
1068
+ return [embed];
1069
+ }
1070
+ if (style.includes("clear:") || style.includes("clear :")) {
1071
+ const cf = recognizeClearFloat(node);
1072
+ if (cf)
1073
+ return [cf];
1074
+ }
1075
+ const result = recognizeDiv(node, rec);
1076
+ if (result)
1077
+ return [result];
1078
+ return recognizeChildren(node, ctx);
1079
+ }
1080
+ function findDirectChild(node, tagName) {
1081
+ for (const child of node.childNodes) {
1082
+ if (isTag11(child) && child.name === tagName)
1083
+ return child;
1084
+ }
1085
+ return null;
1086
+ }
1087
+ function recognizeChildren(node, ctx) {
1088
+ const elements = [];
1089
+ for (const child of node.childNodes) {
1090
+ elements.push(...recognizeNode(child, ctx));
1091
+ }
1092
+ return elements;
1093
+ }
1094
+
1095
+ // packages/decompiler/src/html2ast/index.ts
1096
+ function htmlToAst(html, options) {
1097
+ const document = parseDocument(html, { decodeEntities: true });
1098
+ const ctx = new DecompileContext(options);
1099
+ const elements = document.childNodes.flatMap((node) => recognizeNode(node, ctx));
1100
+ const tree = { elements };
1101
+ if (ctx.footnoteContents.size > 0) {
1102
+ const footnotes = [];
1103
+ for (const [index, content] of ctx.footnoteContents) {
1104
+ footnotes[index - 1] = content;
1105
+ }
1106
+ tree.footnotes = footnotes;
1107
+ }
1108
+ return tree;
1109
+ }
1110
+
1111
+ // packages/decompiler/src/serializer/context.ts
1112
+ class SerializeContext {
1113
+ newline;
1114
+ forceLineBreakSyntax = false;
1115
+ insideBlockquote = false;
1116
+ inParagraph = false;
1117
+ buffer = [];
1118
+ atLineStart = true;
1119
+ _pendingBlankLine = "none";
1120
+ constructor(options = {}) {
1121
+ this.newline = options.newline ?? `
1122
+ `;
1123
+ }
1124
+ requestBlankLine() {
1125
+ this._pendingBlankLine = "block";
1126
+ }
1127
+ requestParagraphBlankLine() {
1128
+ if (this._pendingBlankLine === "none") {
1129
+ this._pendingBlankLine = "paragraph";
1130
+ }
1131
+ }
1132
+ flushPendingBlankLine() {
1133
+ if (this._pendingBlankLine !== "none") {
1134
+ this.buffer.push(this.newline);
1135
+ this._pendingBlankLine = "none";
1136
+ }
1137
+ }
1138
+ clearPendingBlankLine() {
1139
+ this._pendingBlankLine = "none";
1140
+ }
1141
+ get hasPendingBlankLine() {
1142
+ return this._pendingBlankLine !== "none";
1143
+ }
1144
+ push(text2) {
1145
+ this.flushPendingBlankLine();
1146
+ this.buffer.push(text2);
1147
+ this.atLineStart = text2.endsWith(this.newline);
1148
+ }
1149
+ pushBlockLine(text2) {
1150
+ if (this._pendingBlankLine === "block") {
1151
+ this.flushPendingBlankLine();
1152
+ } else {
1153
+ this.clearPendingBlankLine();
1154
+ }
1155
+ this.buffer.push(text2 + this.newline);
1156
+ this.atLineStart = true;
1157
+ }
1158
+ pushLine(text2) {
1159
+ this.push(text2 + this.newline);
1160
+ }
1161
+ pushBlankLine() {
1162
+ this.push(this.newline);
1163
+ }
1164
+ isAtLineStart() {
1165
+ return this.atLineStart;
1166
+ }
1167
+ getOutput() {
1168
+ return this.buffer.join("");
1169
+ }
1170
+ getBlockInnerOutput() {
1171
+ let output = this.getOutput();
1172
+ const nl = this.newline;
1173
+ while (output.endsWith(nl + nl)) {
1174
+ output = output.slice(0, -nl.length);
1175
+ }
1176
+ return output;
1177
+ }
1178
+ }
1179
+
1180
+ // packages/decompiler/src/serializer/text.ts
1181
+ var UNICODE_TO_SOURCE = [
1182
+ ["«", "<<"],
1183
+ ["»", ">>"],
1184
+ ["—", "--"]
1185
+ ];
1186
+ function serializeText(ctx, data) {
1187
+ let text2 = data;
1188
+ for (const [unicode, source] of UNICODE_TO_SOURCE) {
1189
+ text2 = text2.replaceAll(unicode, source);
1190
+ }
1191
+ ctx.push(text2);
1192
+ }
1193
+ function serializeRaw(ctx, data) {
1194
+ ctx.push(`@@${data}@@`);
1195
+ }
1196
+ function serializeLineBreak(ctx) {
1197
+ if (ctx.isAtLineStart() || ctx.forceLineBreakSyntax) {
1198
+ ctx.push(" _" + ctx.newline);
1199
+ } else {
1200
+ ctx.push(ctx.newline);
1201
+ }
1202
+ }
1203
+ function serializeHorizontalRule(ctx) {
1204
+ ctx.pushBlockLine("----");
1205
+ ctx.requestBlankLine();
1206
+ }
1207
+ function serializeContentSeparator(ctx) {
1208
+ ctx.pushBlockLine("====");
1209
+ }
1210
+ function serializeEmail(ctx, data) {
1211
+ ctx.push(data);
1212
+ }
1213
+
1214
+ // packages/decompiler/src/serializer/container.ts
1215
+ import { isHeaderType, isAlignType, isStringContainerType } from "@wdprlib/ast";
1216
+ function serializeContainer(ctx, data) {
1217
+ const { type, elements, attributes } = data;
1218
+ if (isHeaderType(type)) {
1219
+ serializeHeading(ctx, type.header.level, type.header["has-toc"], elements);
1220
+ return;
1221
+ }
1222
+ if (isAlignType(type)) {
1223
+ serializeAlignment(ctx, type.align, elements);
1224
+ return;
1225
+ }
1226
+ if (isStringContainerType(type)) {
1227
+ serializeStringContainer(ctx, type, elements, attributes);
1228
+ return;
1229
+ }
1230
+ const typeStr = type;
1231
+ switch (typeStr) {
1232
+ case "mark":
1233
+ case "insertion":
1234
+ case "deletion":
1235
+ serializeSpanLike(ctx, typeStr, elements, attributes);
1236
+ return;
1237
+ case "ruby":
1238
+ serializeElements(ctx, elements);
1239
+ return;
1240
+ case "ruby-text":
1241
+ serializeElements(ctx, elements);
1242
+ return;
1243
+ default:
1244
+ serializeElements(ctx, elements);
1245
+ }
1246
+ }
1247
+ function serializeHeading(ctx, level, hasToc, elements) {
1248
+ const prefix = "+".repeat(level);
1249
+ const tocMarker = hasToc ? "" : "*";
1250
+ ctx.pushBlockLine(prefix + tocMarker + " " + serializeInline(ctx, elements));
1251
+ ctx.requestBlankLine();
1252
+ }
1253
+ function serializeAlignment(ctx, align, elements) {
1254
+ const tag = alignTag(align);
1255
+ ctx.pushBlockLine(`[[${tag}]]`);
1256
+ const inner = serializeBlockInner(ctx, elements);
1257
+ ctx.push(inner);
1258
+ ctx.pushBlockLine(`[[/${tag}]]`);
1259
+ ctx.requestBlankLine();
1260
+ }
1261
+ function alignTag(align) {
1262
+ switch (align) {
1263
+ case "center":
1264
+ return "=";
1265
+ case "right":
1266
+ return ">";
1267
+ case "left":
1268
+ return "<";
1269
+ case "justify":
1270
+ return "==";
1271
+ }
1272
+ }
1273
+ function serializeStringContainer(ctx, type, elements, attributes) {
1274
+ switch (type) {
1275
+ case "paragraph": {
1276
+ const style = attributes.style ?? "";
1277
+ const prevInParagraph = ctx.inParagraph;
1278
+ ctx.inParagraph = true;
1279
+ if (style === "text-align: center;") {
1280
+ ctx.pushBlockLine("= " + serializeInline(ctx, elements));
1281
+ ctx.requestParagraphBlankLine();
1282
+ } else {
1283
+ serializeElements(ctx, elements);
1284
+ ctx.push(ctx.newline);
1285
+ ctx.requestParagraphBlankLine();
1286
+ }
1287
+ ctx.inParagraph = prevInParagraph;
1288
+ break;
1289
+ }
1290
+ case "bold":
1291
+ ctx.push("**");
1292
+ serializeElements(ctx, elements);
1293
+ ctx.push("**");
1294
+ break;
1295
+ case "italics":
1296
+ ctx.push("//");
1297
+ serializeElements(ctx, elements);
1298
+ ctx.push("//");
1299
+ break;
1300
+ case "underline":
1301
+ ctx.push("__");
1302
+ serializeElements(ctx, elements);
1303
+ ctx.push("__");
1304
+ break;
1305
+ case "strikethrough":
1306
+ ctx.push("--");
1307
+ serializeElements(ctx, elements);
1308
+ ctx.push("--");
1309
+ break;
1310
+ case "superscript":
1311
+ ctx.push("^^");
1312
+ serializeElements(ctx, elements);
1313
+ ctx.push("^^");
1314
+ break;
1315
+ case "subscript":
1316
+ ctx.push(",,");
1317
+ serializeElements(ctx, elements);
1318
+ ctx.push(",,");
1319
+ break;
1320
+ case "monospace":
1321
+ ctx.push("{{");
1322
+ serializeElements(ctx, elements);
1323
+ ctx.push("}}");
1324
+ break;
1325
+ case "span":
1326
+ if (!ctx.inParagraph) {
1327
+ serializeBlockSpan(ctx, elements, attributes);
1328
+ } else {
1329
+ serializeSpanLike(ctx, "span", elements, attributes);
1330
+ }
1331
+ break;
1332
+ case "div":
1333
+ serializeDivContainer(ctx, elements, attributes);
1334
+ break;
1335
+ case "blockquote":
1336
+ serializeBlockquote(ctx, elements);
1337
+ break;
1338
+ case "size":
1339
+ serializeSizeContainer(ctx, elements, attributes);
1340
+ break;
1341
+ default:
1342
+ serializeElements(ctx, elements);
1343
+ }
1344
+ }
1345
+ function serializeSpanLike(ctx, type, elements, attributes) {
1346
+ const attrs = { ...attributes };
1347
+ if (type !== "span") {
1348
+ attrs.class = type;
1349
+ }
1350
+ const attrStr = formatAttributes(attrs);
1351
+ ctx.push(`[[span${attrStr}]]`);
1352
+ serializeElements(ctx, elements);
1353
+ ctx.push("[[/span]]");
1354
+ }
1355
+ function serializeBlockSpan(ctx, elements, attributes) {
1356
+ const attrStr = formatAttributes(attributes);
1357
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1358
+ innerCtx.inParagraph = true;
1359
+ serializeElements(innerCtx, elements);
1360
+ const content = innerCtx.getOutput().replace(/\n$/, "");
1361
+ ctx.pushBlockLine(`[[span_${attrStr}]]${content}[[/span]]`);
1362
+ ctx.requestParagraphBlankLine();
1363
+ }
1364
+ function serializeDivContainer(ctx, elements, attributes) {
1365
+ const attrStr = formatAttributes(attributes);
1366
+ const isParagraphStrip = elements.length > 0 && (isInlineTextElement(elements[0]) || isInlineTextElement(elements[elements.length - 1]));
1367
+ const openTag = isParagraphStrip ? "div_" : "div";
1368
+ ctx.pushBlockLine(`[[${openTag}${attrStr}]]`);
1369
+ if (isParagraphStrip) {
1370
+ const inner = serializeDivStripInner(ctx, elements);
1371
+ ctx.push(inner);
1372
+ } else {
1373
+ const inner = serializeBlockInner(ctx, elements);
1374
+ ctx.push(inner);
1375
+ }
1376
+ ctx.pushBlockLine("[[/div]]");
1377
+ ctx.requestBlankLine();
1378
+ }
1379
+ function serializeDivStripInner(parentCtx, elements) {
1380
+ const innerCtx = new SerializeContext({ newline: parentCtx.newline });
1381
+ let i = 0;
1382
+ while (i < elements.length) {
1383
+ const el = elements[i];
1384
+ if (isBlockLevelElement(el)) {
1385
+ serializeElement(innerCtx, el);
1386
+ i++;
1387
+ } else {
1388
+ while (i < elements.length && !isBlockLevelElement(elements[i])) {
1389
+ serializeElement(innerCtx, elements[i]);
1390
+ i++;
1391
+ }
1392
+ innerCtx.push(innerCtx.newline);
1393
+ innerCtx.requestParagraphBlankLine();
1394
+ }
1395
+ }
1396
+ return innerCtx.getBlockInnerOutput();
1397
+ }
1398
+ function isBlockLevelElement(el) {
1399
+ switch (el.element) {
1400
+ case "container": {
1401
+ const type = el.data?.type;
1402
+ if (typeof type === "object")
1403
+ return true;
1404
+ if (typeof type === "string") {
1405
+ return type === "paragraph" || type === "div" || type === "blockquote";
1406
+ }
1407
+ return false;
1408
+ }
1409
+ case "list":
1410
+ case "definition-list":
1411
+ case "table":
1412
+ case "horizontal-rule":
1413
+ case "clear-float":
1414
+ case "code":
1415
+ case "collapsible":
1416
+ case "tab-view":
1417
+ case "footnote-block":
1418
+ case "style":
1419
+ case "embed":
1420
+ case "embed-block":
1421
+ case "iframe":
1422
+ case "content-separator":
1423
+ case "math":
1424
+ case "bibliography-block":
1425
+ return true;
1426
+ default:
1427
+ return false;
1428
+ }
1429
+ }
1430
+ function isInlineTextElement(el) {
1431
+ switch (el.element) {
1432
+ case "text":
1433
+ case "line-break":
1434
+ case "line-breaks":
1435
+ case "raw":
1436
+ case "variable":
1437
+ case "email":
1438
+ case "link":
1439
+ case "anchor":
1440
+ case "anchor-name":
1441
+ case "footnote":
1442
+ case "footnote-ref":
1443
+ case "bibliography-cite":
1444
+ case "user":
1445
+ case "date":
1446
+ case "color":
1447
+ case "math-inline":
1448
+ case "equation-reference":
1449
+ case "expr":
1450
+ return true;
1451
+ case "container": {
1452
+ const type = el.data?.type;
1453
+ if (typeof type === "string") {
1454
+ switch (type) {
1455
+ case "bold":
1456
+ case "italics":
1457
+ case "underline":
1458
+ case "strikethrough":
1459
+ case "superscript":
1460
+ case "subscript":
1461
+ case "monospace":
1462
+ case "span":
1463
+ case "size":
1464
+ return true;
1465
+ }
1466
+ }
1467
+ return false;
1468
+ }
1469
+ default:
1470
+ return false;
1471
+ }
1472
+ }
1473
+ function serializeBlockquote(ctx, elements) {
1474
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1475
+ innerCtx.insideBlockquote = true;
1476
+ serializeElements(innerCtx, elements);
1477
+ const inner = innerCtx.getBlockInnerOutput();
1478
+ const lines = inner.split(ctx.newline);
1479
+ for (let i = 0;i < lines.length; i++) {
1480
+ const line = lines[i];
1481
+ if (i === lines.length - 1 && line === "")
1482
+ break;
1483
+ if (line.startsWith(">")) {
1484
+ ctx.pushBlockLine(`>${line}`);
1485
+ } else if (line === "") {
1486
+ ctx.pushBlockLine("> ");
1487
+ } else {
1488
+ ctx.pushBlockLine(`> ${line}`);
1489
+ }
1490
+ }
1491
+ if (!ctx.insideBlockquote) {
1492
+ ctx.requestBlankLine();
1493
+ }
1494
+ }
1495
+ function serializeSizeContainer(ctx, elements, attributes) {
1496
+ const style = attributes.style ?? "";
1497
+ const match = style.match(/font-size:\s*([^;]+)/);
1498
+ const size = match ? match[1].trim() : "1em";
1499
+ ctx.push(`[[size ${size}]]`);
1500
+ serializeElements(ctx, elements);
1501
+ ctx.push("[[/size]]");
1502
+ }
1503
+ function serializeBlockInner(parentCtx, elements) {
1504
+ const innerCtx = new SerializeContext({ newline: parentCtx.newline });
1505
+ serializeElements(innerCtx, elements);
1506
+ return innerCtx.getBlockInnerOutput();
1507
+ }
1508
+ function serializeInline(parentCtx, elements) {
1509
+ const innerCtx = new SerializeContext({ newline: parentCtx.newline });
1510
+ serializeElements(innerCtx, elements);
1511
+ return innerCtx.getOutput().replace(/\n$/, "");
1512
+ }
1513
+ function formatAttributes(attributes) {
1514
+ const entries = Object.entries(attributes).filter(([k]) => !k.startsWith("_"));
1515
+ if (entries.length === 0)
1516
+ return "";
1517
+ return " " + entries.map(([k, v]) => {
1518
+ if (k === "id" && v.startsWith("u-")) {
1519
+ return `${k}="${v.slice(2)}"`;
1520
+ }
1521
+ return `${k}="${v}"`;
1522
+ }).join(" ");
1523
+ }
1524
+
1525
+ // packages/decompiler/src/serializer/link.ts
1526
+ function serializeLink(ctx, data) {
1527
+ const { type, link: location, label, target, extra } = data;
1528
+ switch (type) {
1529
+ case "anchor": {
1530
+ const labelText = extractLabelText(label);
1531
+ ctx.push(`[# ${labelText}]`);
1532
+ break;
1533
+ }
1534
+ case "page": {
1535
+ const pageName = typeof location === "string" ? location : location.site ? `${location.site}:${location.page}` : location.page;
1536
+ const targetSuffix = target === "new-tab" ? "*" : "";
1537
+ const extraSuffix = extra ?? "";
1538
+ const labelText = extractLabelText(label);
1539
+ if (label === "page" || labelText === pageName) {
1540
+ ctx.push(`[[[${pageName}${extraSuffix}${targetSuffix}]]]`);
1541
+ } else {
1542
+ ctx.push(`[[[${pageName}${extraSuffix}${targetSuffix} | ${labelText}]]]`);
1543
+ }
1544
+ break;
1545
+ }
1546
+ case "direct": {
1547
+ const url = typeof location === "string" ? location : "";
1548
+ const labelText = extractLabelText(label);
1549
+ const targetPrefix = target === "new-tab" ? "*" : "";
1550
+ if (labelIsUrl(label, url)) {
1551
+ ctx.push(`[${targetPrefix}${url}]`);
1552
+ } else {
1553
+ ctx.push(`[${targetPrefix}${url} ${labelText}]`);
1554
+ }
1555
+ break;
1556
+ }
1557
+ default: {
1558
+ const labelText = extractLabelText(label);
1559
+ ctx.push(labelText || (typeof location === "string" ? location : ""));
1560
+ }
1561
+ }
1562
+ }
1563
+ function serializeAnchorName(ctx, name) {
1564
+ ctx.push(`[[# ${name}]]`);
1565
+ }
1566
+ function extractLabelText(label) {
1567
+ if (label === "page")
1568
+ return "";
1569
+ if ("text" in label)
1570
+ return label.text;
1571
+ if ("url" in label)
1572
+ return label.url ?? "";
1573
+ return "";
1574
+ }
1575
+ function labelIsUrl(label, url) {
1576
+ if (label === "page")
1577
+ return false;
1578
+ if ("url" in label)
1579
+ return true;
1580
+ if ("text" in label)
1581
+ return label.text === url;
1582
+ return false;
1583
+ }
1584
+
1585
+ // packages/decompiler/src/serializer/image.ts
1586
+ function serializeImage(ctx, data) {
1587
+ const source = formatImageSource(data.source);
1588
+ const attrs = [];
1589
+ let alignPrefix = "";
1590
+ if (data.alignment) {
1591
+ const { align, float: isFloat } = data.alignment;
1592
+ if (isFloat) {
1593
+ switch (align) {
1594
+ case "left":
1595
+ alignPrefix = "f<";
1596
+ break;
1597
+ case "right":
1598
+ alignPrefix = "f>";
1599
+ break;
1600
+ case "center":
1601
+ alignPrefix = "f=";
1602
+ break;
1603
+ }
1604
+ } else {
1605
+ switch (align) {
1606
+ case "left":
1607
+ alignPrefix = "<";
1608
+ break;
1609
+ case "right":
1610
+ alignPrefix = ">";
1611
+ break;
1612
+ case "center":
1613
+ alignPrefix = "=";
1614
+ break;
1615
+ }
1616
+ }
1617
+ }
1618
+ if (data.link) {
1619
+ const link2 = typeof data.link === "string" ? data.link : data.link.page;
1620
+ attrs.push(`link="${link2}"`);
1621
+ }
1622
+ for (const [key, value] of Object.entries(data.attributes)) {
1623
+ if (key === "class" && value === "image")
1624
+ continue;
1625
+ attrs.push(`${key}="${value}"`);
1626
+ }
1627
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1628
+ ctx.push(`[[${alignPrefix}image ${source}${attrStr}]]`);
1629
+ }
1630
+ function formatImageSource(source) {
1631
+ switch (source.type) {
1632
+ case "url":
1633
+ return source.data;
1634
+ case "file1":
1635
+ return source.data.file;
1636
+ case "file2":
1637
+ return `${source.data.page}/${source.data.file}`;
1638
+ case "file3":
1639
+ return `${source.data.site}:${source.data.page}/${source.data.file}`;
1640
+ }
1641
+ }
1642
+
1643
+ // packages/decompiler/src/serializer/list.ts
1644
+ function serializeList(ctx, data, depth = 0) {
1645
+ const marker = data.type === "numbered" ? "#" : "*";
1646
+ const prefix = " ".repeat(depth) + marker;
1647
+ const prevForce = ctx.forceLineBreakSyntax;
1648
+ ctx.forceLineBreakSyntax = true;
1649
+ for (const item of data.items) {
1650
+ if (item["item-type"] === "sub-list") {
1651
+ serializeList(ctx, item.data, depth + 1);
1652
+ } else {
1653
+ ctx.push(`${prefix} `);
1654
+ serializeElements(ctx, item.elements);
1655
+ ctx.push(ctx.newline);
1656
+ }
1657
+ }
1658
+ ctx.forceLineBreakSyntax = prevForce;
1659
+ if (depth === 0) {
1660
+ ctx.requestBlankLine();
1661
+ }
1662
+ }
1663
+ function serializeDefinitionList(ctx, items) {
1664
+ const prevForce = ctx.forceLineBreakSyntax;
1665
+ ctx.forceLineBreakSyntax = true;
1666
+ for (const item of items) {
1667
+ ctx.push(": ");
1668
+ serializeElements(ctx, item.key);
1669
+ ctx.push(" : ");
1670
+ serializeElements(ctx, item.value);
1671
+ ctx.push(ctx.newline);
1672
+ }
1673
+ ctx.forceLineBreakSyntax = prevForce;
1674
+ ctx.requestBlankLine();
1675
+ }
1676
+
1677
+ // packages/decompiler/src/serializer/table.ts
1678
+ function serializeTable(ctx, data) {
1679
+ for (const row of data.rows) {
1680
+ for (const cell of row.cells) {
1681
+ let prefix = "||";
1682
+ for (let i = 1;i < cell["column-span"]; i++) {
1683
+ prefix += "||";
1684
+ }
1685
+ if (cell.header) {
1686
+ prefix += "~";
1687
+ } else if (cell.align === "center") {
1688
+ prefix += "=";
1689
+ } else if (cell.align === "right") {
1690
+ prefix += ">";
1691
+ } else if (cell.align === "left") {
1692
+ prefix += "<";
1693
+ }
1694
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1695
+ serializeElements(innerCtx, cell.elements);
1696
+ const content = innerCtx.getOutput().replace(/\n$/, "");
1697
+ ctx.push(`${prefix} ${content} `);
1698
+ }
1699
+ ctx.pushLine("||");
1700
+ }
1701
+ ctx.requestBlankLine();
1702
+ }
1703
+
1704
+ // packages/decompiler/src/serializer/code.ts
1705
+ function serializeCode(ctx, data) {
1706
+ const attrs = [];
1707
+ if (data.language) {
1708
+ attrs.push(`type="${data.language}"`);
1709
+ }
1710
+ if (data.name) {
1711
+ attrs.push(`name="${data.name}"`);
1712
+ }
1713
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1714
+ ctx.pushBlockLine(`[[code${attrStr}]]`);
1715
+ if (data.contents.length > 0) {
1716
+ ctx.push(data.contents);
1717
+ if (!data.contents.endsWith(`
1718
+ `)) {
1719
+ ctx.push(ctx.newline);
1720
+ }
1721
+ }
1722
+ ctx.pushBlockLine("[[/code]]");
1723
+ ctx.requestBlankLine();
1724
+ }
1725
+
1726
+ // packages/decompiler/src/serializer/collapsible.ts
1727
+ function serializeCollapsible(ctx, data) {
1728
+ const attrs = [];
1729
+ if (data["show-text"]) {
1730
+ attrs.push(`show="${data["show-text"]}"`);
1731
+ }
1732
+ if (data["hide-text"]) {
1733
+ attrs.push(`hide="${data["hide-text"]}"`);
1734
+ }
1735
+ if (data["start-open"]) {
1736
+ attrs.push(`folded="no"`);
1737
+ }
1738
+ if (!data["show-top"] && !data["show-bottom"]) {
1739
+ attrs.push(`hideLocation="neither"`);
1740
+ } else if (!data["show-top"] && data["show-bottom"]) {
1741
+ attrs.push(`hideLocation="bottom"`);
1742
+ } else if (data["show-top"] && data["show-bottom"]) {
1743
+ attrs.push(`hideLocation="both"`);
1744
+ }
1745
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1746
+ ctx.pushBlockLine(`[[collapsible${attrStr}]]`);
1747
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1748
+ serializeElements(innerCtx, data.elements);
1749
+ ctx.push(innerCtx.getBlockInnerOutput());
1750
+ ctx.pushBlockLine("[[/collapsible]]");
1751
+ ctx.requestBlankLine();
1752
+ }
1753
+
1754
+ // packages/decompiler/src/serializer/tab-view.ts
1755
+ function serializeTabView(ctx, tabs) {
1756
+ ctx.pushBlockLine("[[tabview]]");
1757
+ for (const tab of tabs) {
1758
+ ctx.pushBlockLine(`[[tab ${tab.label}]]`);
1759
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1760
+ serializeElements(innerCtx, tab.elements);
1761
+ ctx.push(innerCtx.getBlockInnerOutput());
1762
+ ctx.pushBlockLine("[[/tab]]");
1763
+ }
1764
+ ctx.pushBlockLine("[[/tabview]]");
1765
+ ctx.requestBlankLine();
1766
+ }
1767
+
1768
+ // packages/decompiler/src/serializer/footnote.ts
1769
+ function serializeFootnoteInline(ctx, index, footnotes) {
1770
+ const content = footnotes?.[index];
1771
+ if (content && content.length > 0) {
1772
+ serializeFootnoteContent(ctx, content);
1773
+ } else {
1774
+ ctx.push("[[footnote]][[/footnote]]");
1775
+ }
1776
+ }
1777
+ function serializeFootnoteRef(ctx, refNum, footnotes) {
1778
+ const content = footnotes?.[refNum - 1];
1779
+ if (content && content.length > 0) {
1780
+ serializeFootnoteContent(ctx, content);
1781
+ } else {
1782
+ ctx.push("[[footnote]][[/footnote]]");
1783
+ }
1784
+ }
1785
+ function serializeFootnoteContent(ctx, content) {
1786
+ const hasParagraph = content.some((el) => el.element === "container" && el.data?.type === "paragraph");
1787
+ if (hasParagraph) {
1788
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1789
+ let seenParagraph = false;
1790
+ for (const el of content) {
1791
+ if (el.element === "container" && el.data?.type === "paragraph" && !seenParagraph) {
1792
+ seenParagraph = true;
1793
+ innerCtx.push(innerCtx.newline);
1794
+ innerCtx.pushBlankLine();
1795
+ }
1796
+ serializeElement(innerCtx, el);
1797
+ }
1798
+ const inner = innerCtx.getBlockInnerOutput();
1799
+ ctx.push("[[footnote]]" + ctx.newline);
1800
+ ctx.push(inner);
1801
+ if (!inner.endsWith(ctx.newline)) {
1802
+ ctx.push(ctx.newline);
1803
+ }
1804
+ ctx.push("[[/footnote]]");
1805
+ } else {
1806
+ const innerCtx = new SerializeContext({ newline: ctx.newline });
1807
+ serializeElements(innerCtx, content);
1808
+ const inner = innerCtx.getBlockInnerOutput();
1809
+ if (inner.includes(ctx.newline)) {
1810
+ ctx.push("[[footnote]]" + ctx.newline);
1811
+ ctx.push(inner);
1812
+ if (!inner.endsWith(ctx.newline)) {
1813
+ ctx.push(ctx.newline);
1814
+ }
1815
+ ctx.push("[[/footnote]]");
1816
+ } else {
1817
+ ctx.push(`[[footnote]]${inner}[[/footnote]]`);
1818
+ }
1819
+ }
1820
+ }
1821
+ function serializeFootnoteBlock(ctx, data, isLastElement) {
1822
+ if (!data.title && !data.hide && isLastElement)
1823
+ return;
1824
+ const attrs = [];
1825
+ if (data.hide)
1826
+ attrs.push('hide="true"');
1827
+ if (data.title)
1828
+ attrs.push(`title="${data.title}"`);
1829
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1830
+ if (data.hide || data.title) {
1831
+ ctx.flushPendingBlankLine();
1832
+ }
1833
+ ctx.pushBlockLine(`[[footnoteblock${attrStr}]]`);
1834
+ ctx.requestBlankLine();
1835
+ }
1836
+
1837
+ // packages/decompiler/src/serializer/math.ts
1838
+ function serializeMath(ctx, data) {
1839
+ const attrs = [];
1840
+ if (data.name) {
1841
+ attrs.push(data.name);
1842
+ }
1843
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1844
+ if (!data["latex-source"].includes(`
1845
+ `)) {
1846
+ ctx.pushBlockLine(`[[math${attrStr}]] ${data["latex-source"]} [[/math]]`);
1847
+ } else {
1848
+ ctx.pushBlockLine(`[[math${attrStr}]]`);
1849
+ ctx.push(data["latex-source"]);
1850
+ if (!data["latex-source"].endsWith(`
1851
+ `)) {
1852
+ ctx.push(ctx.newline);
1853
+ }
1854
+ ctx.pushBlockLine("[[/math]]");
1855
+ }
1856
+ ctx.requestBlankLine();
1857
+ }
1858
+ function serializeMathInline(ctx, data) {
1859
+ ctx.push(`[[$ ${data["latex-source"]} $]]`);
1860
+ }
1861
+
1862
+ // packages/decompiler/src/serializer/misc.ts
1863
+ function serializeColor(ctx, data) {
1864
+ ctx.push(`##${data.color}|`);
1865
+ serializeElements(ctx, data.elements);
1866
+ ctx.push("##");
1867
+ }
1868
+ function serializeClearFloat(ctx, data) {
1869
+ switch (data) {
1870
+ case "left":
1871
+ ctx.pushBlockLine("~~~~<");
1872
+ break;
1873
+ case "right":
1874
+ ctx.pushBlockLine("~~~~>");
1875
+ break;
1876
+ case "both":
1877
+ ctx.pushBlockLine("~~~~");
1878
+ break;
1879
+ }
1880
+ ctx.requestBlankLine();
1881
+ }
1882
+ function serializeEmbed(ctx, data) {
1883
+ switch (data.embed) {
1884
+ case "youtube":
1885
+ ctx.pushBlockLine(`[[embedvideo youtube ${data.data["video-id"]}]]`);
1886
+ break;
1887
+ case "vimeo":
1888
+ ctx.pushBlockLine(`[[embedvideo vimeo ${data.data["video-id"]}]]`);
1889
+ break;
1890
+ case "github-gist":
1891
+ ctx.pushBlockLine(`[[embedvideo github-gist ${data.data.username} ${data.data.hash}]]`);
1892
+ break;
1893
+ case "gitlab-snippet":
1894
+ ctx.pushBlockLine(`[[embedvideo gitlab-snippet ${data.data["snippet-id"]}]]`);
1895
+ break;
1896
+ }
1897
+ ctx.requestBlankLine();
1898
+ }
1899
+ function serializeEmbedBlock(ctx, data) {
1900
+ ctx.pushBlockLine("[[embed]]");
1901
+ ctx.push(data.contents);
1902
+ if (!data.contents.endsWith(`
1903
+ `)) {
1904
+ ctx.push(ctx.newline);
1905
+ }
1906
+ ctx.pushBlockLine("[[/embed]]");
1907
+ ctx.requestBlankLine();
1908
+ }
1909
+ function serializeIframe(ctx, data) {
1910
+ const attrs = [];
1911
+ for (const [key, value] of Object.entries(data.attributes)) {
1912
+ attrs.push(`${key}="${value}"`);
1913
+ }
1914
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1915
+ ctx.pushBlockLine(`[[iframe ${data.url}${attrStr}]]`);
1916
+ ctx.requestBlankLine();
1917
+ }
1918
+ function serializeStyle(ctx, data) {
1919
+ ctx.pushBlockLine("[[module CSS]]");
1920
+ ctx.push(data);
1921
+ if (!data.endsWith(`
1922
+ `)) {
1923
+ ctx.push(ctx.newline);
1924
+ }
1925
+ ctx.pushBlockLine("[[/module]]");
1926
+ ctx.requestBlankLine();
1927
+ }
1928
+ function serializeAnchor(ctx, data) {
1929
+ const attrs = [];
1930
+ for (const [key, value] of Object.entries(data.attributes)) {
1931
+ if (key === "id" && value.startsWith("u-")) {
1932
+ attrs.push(`${key}="${value.slice(2)}"`);
1933
+ } else {
1934
+ attrs.push(`${key}="${value}"`);
1935
+ }
1936
+ }
1937
+ const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
1938
+ ctx.push(`[[a${attrStr}]]`);
1939
+ serializeElements(ctx, data.elements);
1940
+ ctx.push("[[/a]]");
1941
+ }
1942
+ function serializeBibliographyCite(ctx, data) {
1943
+ ctx.push(`((bibcite ${data.label}))`);
1944
+ }
1945
+ function serializeBibliographyBlock(ctx, data) {
1946
+ ctx.flushPendingBlankLine();
1947
+ ctx.pushBlockLine("[[bibliography]]");
1948
+ for (const entry of data.entries) {
1949
+ ctx.push(": ");
1950
+ serializeElements(ctx, entry.key);
1951
+ ctx.push(" : ");
1952
+ serializeElements(ctx, entry.value);
1953
+ ctx.push(ctx.newline);
1954
+ }
1955
+ ctx.pushBlockLine("[[/bibliography]]");
1956
+ ctx.requestBlankLine();
1957
+ }
1958
+ function serializeEquationRef(ctx, name) {
1959
+ ctx.push(`[[eref ${name}]]`);
1960
+ }
1961
+
1962
+ // packages/decompiler/src/serializer/serialize-element.ts
1963
+ var currentTree;
1964
+ var footnoteCounter = 0;
1965
+ function setCurrentTree(tree) {
1966
+ currentTree = tree;
1967
+ footnoteCounter = 0;
1968
+ }
1969
+ function serializeElement(ctx, element) {
1970
+ switch (element.element) {
1971
+ case "text":
1972
+ serializeText(ctx, element.data);
1973
+ break;
1974
+ case "raw":
1975
+ serializeRaw(ctx, element.data);
1976
+ break;
1977
+ case "email":
1978
+ serializeEmail(ctx, element.data);
1979
+ break;
1980
+ case "container":
1981
+ serializeContainer(ctx, element.data);
1982
+ break;
1983
+ case "line-break":
1984
+ serializeLineBreak(ctx);
1985
+ break;
1986
+ case "horizontal-rule":
1987
+ serializeHorizontalRule(ctx);
1988
+ break;
1989
+ case "content-separator":
1990
+ serializeContentSeparator(ctx);
1991
+ break;
1992
+ case "link":
1993
+ serializeLink(ctx, element.data);
1994
+ break;
1995
+ case "anchor":
1996
+ serializeAnchor(ctx, element.data);
1997
+ break;
1998
+ case "anchor-name":
1999
+ serializeAnchorName(ctx, element.data);
2000
+ break;
2001
+ case "image":
2002
+ serializeImage(ctx, element.data);
2003
+ break;
2004
+ case "list":
2005
+ serializeList(ctx, element.data);
2006
+ break;
2007
+ case "definition-list":
2008
+ serializeDefinitionList(ctx, element.data);
2009
+ break;
2010
+ case "table":
2011
+ serializeTable(ctx, element.data);
2012
+ break;
2013
+ case "code":
2014
+ serializeCode(ctx, element.data);
2015
+ break;
2016
+ case "collapsible":
2017
+ serializeCollapsible(ctx, element.data);
2018
+ break;
2019
+ case "tab-view":
2020
+ serializeTabView(ctx, element.data);
2021
+ break;
2022
+ case "footnote":
2023
+ serializeFootnoteInline(ctx, footnoteCounter, currentTree?.footnotes);
2024
+ footnoteCounter++;
2025
+ break;
2026
+ case "footnote-ref":
2027
+ serializeFootnoteRef(ctx, element.data, currentTree?.footnotes);
2028
+ break;
2029
+ case "footnote-block": {
2030
+ const isLast = element._isLastElement;
2031
+ serializeFootnoteBlock(ctx, element.data, isLast);
2032
+ break;
2033
+ }
2034
+ case "math":
2035
+ serializeMath(ctx, element.data);
2036
+ break;
2037
+ case "math-inline":
2038
+ serializeMathInline(ctx, element.data);
2039
+ break;
2040
+ case "equation-reference":
2041
+ serializeEquationRef(ctx, element.data);
2042
+ break;
2043
+ case "color":
2044
+ serializeColor(ctx, element.data);
2045
+ break;
2046
+ case "clear-float":
2047
+ serializeClearFloat(ctx, element.data);
2048
+ break;
2049
+ case "embed":
2050
+ serializeEmbed(ctx, element.data);
2051
+ break;
2052
+ case "embed-block":
2053
+ serializeEmbedBlock(ctx, element.data);
2054
+ break;
2055
+ case "iframe":
2056
+ serializeIframe(ctx, element.data);
2057
+ break;
2058
+ case "style":
2059
+ serializeStyle(ctx, element.data);
2060
+ break;
2061
+ case "bibliography-cite":
2062
+ serializeBibliographyCite(ctx, element.data);
2063
+ break;
2064
+ case "bibliography-block":
2065
+ serializeBibliographyBlock(ctx, element.data);
2066
+ break;
2067
+ default:
2068
+ break;
2069
+ }
2070
+ }
2071
+ function serializeElements(ctx, elements) {
2072
+ for (let i = 0;i < elements.length; i++) {
2073
+ const el = elements[i];
2074
+ if (el.element === "footnote-block" && i === elements.length - 1) {
2075
+ el._isLastElement = true;
2076
+ }
2077
+ serializeElement(ctx, el);
2078
+ }
2079
+ }
2080
+
2081
+ // packages/decompiler/src/serializer/index.ts
2082
+ function serialize(tree, options) {
2083
+ const ctx = new SerializeContext(options);
2084
+ setCurrentTree(tree);
2085
+ serializeElements(ctx, tree.elements);
2086
+ let output = ctx.getOutput();
2087
+ const nl = ctx.newline;
2088
+ const nlEsc = nl.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
2089
+ output = output.replace(new RegExp(`(${nlEsc}){3,}`, "g"), nl + nl);
2090
+ output = output.replace(new RegExp(`(${nlEsc})+$`), nl);
2091
+ return output;
2092
+ }
2093
+
2094
+ // packages/decompiler/src/index.ts
2095
+ function decompile(html, options) {
2096
+ const tree = htmlToAst(html, options);
2097
+ return serialize(tree);
2098
+ }
2099
+ export {
2100
+ serialize,
2101
+ htmlToAst,
2102
+ decompile
2103
+ };