@lexbuild/core 1.0.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,1600 @@
1
+ // src/xml/parser.ts
2
+ import { SaxesParser } from "saxes";
3
+
4
+ // src/xml/namespace.ts
5
+ var USLM_NS = "http://xml.house.gov/schemas/uslm/1.0";
6
+ var XHTML_NS = "http://www.w3.org/1999/xhtml";
7
+ var DC_NS = "http://purl.org/dc/elements/1.1/";
8
+ var DCTERMS_NS = "http://purl.org/dc/terms/";
9
+ var XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
10
+ var NAMESPACE_PREFIXES = {
11
+ [XHTML_NS]: "xhtml",
12
+ [DC_NS]: "dc",
13
+ [DCTERMS_NS]: "dcterms",
14
+ [XSI_NS]: "xsi"
15
+ };
16
+ var LEVEL_ELEMENTS = /* @__PURE__ */ new Set([
17
+ "title",
18
+ "subtitle",
19
+ "chapter",
20
+ "subchapter",
21
+ "article",
22
+ "subarticle",
23
+ "part",
24
+ "subpart",
25
+ "division",
26
+ "subdivision",
27
+ "preliminary",
28
+ "section",
29
+ "subsection",
30
+ "paragraph",
31
+ "subparagraph",
32
+ "clause",
33
+ "subclause",
34
+ "item",
35
+ "subitem",
36
+ "subsubitem",
37
+ // Appendix-level elements
38
+ "appendix",
39
+ "compiledAct",
40
+ "reorganizationPlans",
41
+ "reorganizationPlan",
42
+ "courtRules",
43
+ "courtRule"
44
+ ]);
45
+ var CONTENT_ELEMENTS = /* @__PURE__ */ new Set([
46
+ "content",
47
+ "chapeau",
48
+ "continuation",
49
+ "proviso"
50
+ ]);
51
+ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
52
+ "b",
53
+ "i",
54
+ "sub",
55
+ "sup",
56
+ "ref",
57
+ "date",
58
+ "term",
59
+ "inline",
60
+ "shortTitle",
61
+ "del",
62
+ "ins"
63
+ ]);
64
+ var NOTE_ELEMENTS = /* @__PURE__ */ new Set([
65
+ "note",
66
+ "notes",
67
+ "sourceCredit",
68
+ "statutoryNote",
69
+ "editorialNote",
70
+ "changeNote"
71
+ ]);
72
+ var APPENDIX_LEVEL_ELEMENTS = /* @__PURE__ */ new Set([
73
+ "compiledAct",
74
+ "courtRules",
75
+ "courtRule",
76
+ "reorganizationPlans",
77
+ "reorganizationPlan"
78
+ ]);
79
+ var META_ELEMENTS = /* @__PURE__ */ new Set([
80
+ "meta",
81
+ "docNumber",
82
+ "docPublicationName",
83
+ "docReleasePoint",
84
+ "property"
85
+ ]);
86
+ var CONTAINER_ELEMENTS = /* @__PURE__ */ new Set([
87
+ "uscDoc",
88
+ "main",
89
+ "meta",
90
+ "toc",
91
+ "layout",
92
+ "header",
93
+ "row",
94
+ "column",
95
+ "tocItem"
96
+ ]);
97
+
98
+ // src/xml/parser.ts
99
+ var XMLParser = class {
100
+ saxParser;
101
+ defaultNs;
102
+ prefixMap;
103
+ listeners = /* @__PURE__ */ new Map();
104
+ constructor(options) {
105
+ this.defaultNs = options?.defaultNamespace ?? USLM_NS;
106
+ this.prefixMap = {
107
+ ...NAMESPACE_PREFIXES,
108
+ ...options?.namespacePrefixes
109
+ };
110
+ this.saxParser = new SaxesParser({ xmlns: true, position: true });
111
+ this.saxParser.on("opentag", (node) => {
112
+ const ns = node.uri;
113
+ const localName = node.local;
114
+ const normalizedName = this.normalizeName(localName, ns);
115
+ const attrs = this.normalizeAttributes(node.attributes);
116
+ this.emit("openElement", normalizedName, attrs, ns);
117
+ });
118
+ this.saxParser.on("closetag", (node) => {
119
+ const ns = node.uri;
120
+ const localName = node.local;
121
+ const normalizedName = this.normalizeName(localName, ns);
122
+ this.emit("closeElement", normalizedName, ns);
123
+ });
124
+ this.saxParser.on("text", (text) => {
125
+ this.emit("text", text);
126
+ });
127
+ this.saxParser.on("error", (err) => {
128
+ this.emit("error", err);
129
+ });
130
+ this.saxParser.on("end", () => {
131
+ this.emit("end");
132
+ });
133
+ }
134
+ /**
135
+ * Register an event listener.
136
+ */
137
+ on(event, handler) {
138
+ let handlers = this.listeners.get(event);
139
+ if (!handlers) {
140
+ handlers = [];
141
+ this.listeners.set(event, handlers);
142
+ }
143
+ handlers.push(handler);
144
+ return this;
145
+ }
146
+ /**
147
+ * Parse a complete XML string.
148
+ */
149
+ parseString(xml) {
150
+ this.saxParser.write(xml);
151
+ this.saxParser.close();
152
+ }
153
+ /**
154
+ * Parse from a readable stream (e.g., fs.createReadStream).
155
+ * Returns a promise that resolves when parsing is complete.
156
+ */
157
+ parseStream(stream) {
158
+ return new Promise((resolve2, reject) => {
159
+ stream.on("data", (chunk) => {
160
+ try {
161
+ this.saxParser.write(typeof chunk === "string" ? chunk : chunk.toString("utf-8"));
162
+ } catch (err) {
163
+ reject(err);
164
+ }
165
+ });
166
+ stream.on("end", () => {
167
+ try {
168
+ this.saxParser.close();
169
+ resolve2();
170
+ } catch (err) {
171
+ reject(err);
172
+ }
173
+ });
174
+ stream.on("error", (err) => {
175
+ reject(err);
176
+ });
177
+ });
178
+ }
179
+ /**
180
+ * Normalize an element name based on its namespace.
181
+ * Default namespace elements get bare names; others get prefixed.
182
+ */
183
+ normalizeName(localName, ns) {
184
+ if (ns === this.defaultNs || ns === "") {
185
+ return localName;
186
+ }
187
+ const prefix = this.prefixMap[ns];
188
+ if (prefix) {
189
+ return `${prefix}:${localName}`;
190
+ }
191
+ return `{${ns}}${localName}`;
192
+ }
193
+ /**
194
+ * Normalize saxes namespace-aware attributes to a flat record.
195
+ * Strips namespace prefixes from attribute names for simplicity,
196
+ * except for xmlns declarations which are dropped entirely.
197
+ */
198
+ normalizeAttributes(saxAttrs) {
199
+ const attrs = {};
200
+ for (const [, attr] of Object.entries(saxAttrs)) {
201
+ if (attr.prefix === "xmlns" || attr.local === "xmlns") {
202
+ continue;
203
+ }
204
+ const name = attr.prefix && attr.prefix !== "" ? `${attr.prefix}:${attr.local}` : attr.local;
205
+ attrs[name] = attr.value;
206
+ }
207
+ return attrs;
208
+ }
209
+ /**
210
+ * Emit an event to all registered listeners.
211
+ */
212
+ emit(event, ...args) {
213
+ const handlers = this.listeners.get(event);
214
+ if (handlers) {
215
+ for (const handler of handlers) {
216
+ handler(...args);
217
+ }
218
+ }
219
+ }
220
+ };
221
+
222
+ // src/ast/types.ts
223
+ var LEVEL_TYPES = [
224
+ // Big levels (above section)
225
+ "title",
226
+ "appendix",
227
+ "subtitle",
228
+ "chapter",
229
+ "subchapter",
230
+ "compiledAct",
231
+ "reorganizationPlans",
232
+ "reorganizationPlan",
233
+ "courtRules",
234
+ "courtRule",
235
+ "article",
236
+ "subarticle",
237
+ "part",
238
+ "subpart",
239
+ "division",
240
+ "subdivision",
241
+ "preliminary",
242
+ // Primary level
243
+ "section",
244
+ // Small levels (below section)
245
+ "subsection",
246
+ "paragraph",
247
+ "subparagraph",
248
+ "clause",
249
+ "subclause",
250
+ "item",
251
+ "subitem",
252
+ "subsubitem"
253
+ ];
254
+ var BIG_LEVELS = /* @__PURE__ */ new Set([
255
+ "title",
256
+ "appendix",
257
+ "subtitle",
258
+ "chapter",
259
+ "subchapter",
260
+ "compiledAct",
261
+ "reorganizationPlans",
262
+ "reorganizationPlan",
263
+ "courtRules",
264
+ "courtRule",
265
+ "article",
266
+ "subarticle",
267
+ "part",
268
+ "subpart",
269
+ "division",
270
+ "subdivision",
271
+ "preliminary"
272
+ ]);
273
+ var SMALL_LEVELS = /* @__PURE__ */ new Set([
274
+ "subsection",
275
+ "paragraph",
276
+ "subparagraph",
277
+ "clause",
278
+ "subclause",
279
+ "item",
280
+ "subitem",
281
+ "subsubitem"
282
+ ]);
283
+
284
+ // src/ast/builder.ts
285
+ var INLINE_TYPE_MAP = {
286
+ b: "bold",
287
+ i: "italic",
288
+ sub: "sub",
289
+ sup: "sup",
290
+ ref: "ref",
291
+ date: "date",
292
+ term: "term",
293
+ inline: "text",
294
+ shortTitle: "text",
295
+ del: "text",
296
+ ins: "text"
297
+ };
298
+ var ASTBuilder = class {
299
+ options;
300
+ stack = [];
301
+ ancestors = [];
302
+ documentMeta = {};
303
+ /** Whether we are currently inside the <meta> block */
304
+ inMeta = false;
305
+ /** Nesting depth inside <quotedContent> — levels inside quotes are not emitted */
306
+ quotedContentDepth = 0;
307
+ /** Active XHTML table collector (null when not inside a table) */
308
+ tableCollector = null;
309
+ /** Active USLM layout collector (null when not inside a layout) */
310
+ layoutCollector = null;
311
+ /** Nesting depth inside <toc> — elements inside toc are handled by layout collector only */
312
+ tocDepth = 0;
313
+ /** Current meta field being collected (e.g., "dc:title", "docNumber") */
314
+ metaField = null;
315
+ /** Attributes of the current meta property element */
316
+ metaPropertyAttrs = null;
317
+ constructor(options) {
318
+ this.options = options;
319
+ }
320
+ /** Returns the document metadata collected so far */
321
+ getDocumentMeta() {
322
+ return this.documentMeta;
323
+ }
324
+ /**
325
+ * Handle an openElement event from the parser.
326
+ */
327
+ onOpenElement(name, attrs) {
328
+ if (name === "meta") {
329
+ this.inMeta = true;
330
+ this.stack.push({ kind: "meta", node: null, elementName: name, textBuffer: "" });
331
+ return;
332
+ }
333
+ if (this.inMeta) {
334
+ this.handleMetaOpen(name, attrs);
335
+ return;
336
+ }
337
+ if (name === "uscDoc") {
338
+ if (attrs["identifier"]) {
339
+ this.documentMeta.identifier = attrs["identifier"];
340
+ }
341
+ return;
342
+ }
343
+ if (name === "main") {
344
+ return;
345
+ }
346
+ if (name === "xhtml:table") {
347
+ this.tableCollector = {
348
+ headers: [],
349
+ rows: [],
350
+ currentRow: [],
351
+ cellText: "",
352
+ inHead: false,
353
+ inCell: false,
354
+ isComplex: false,
355
+ cellDepth: 0
356
+ };
357
+ return;
358
+ }
359
+ if (this.tableCollector) {
360
+ this.handleTableOpen(name, attrs);
361
+ return;
362
+ }
363
+ if (name === "layout") {
364
+ this.layoutCollector = {
365
+ headers: [],
366
+ rows: [],
367
+ currentRow: [],
368
+ cellText: "",
369
+ inHead: false,
370
+ inCell: false,
371
+ isComplex: false,
372
+ cellDepth: 0
373
+ };
374
+ return;
375
+ }
376
+ if (this.layoutCollector) {
377
+ this.handleLayoutOpen(name, attrs);
378
+ return;
379
+ }
380
+ if (name === "toc") {
381
+ this.tocDepth++;
382
+ return;
383
+ }
384
+ if (this.tocDepth > 0) {
385
+ return;
386
+ }
387
+ if (LEVEL_ELEMENTS.has(name)) {
388
+ this.openLevel(name, attrs);
389
+ return;
390
+ }
391
+ if (CONTENT_ELEMENTS.has(name)) {
392
+ this.openContent(name, attrs);
393
+ return;
394
+ }
395
+ if (INLINE_ELEMENTS.has(name)) {
396
+ this.openInline(name, attrs);
397
+ return;
398
+ }
399
+ if (name === "notes") {
400
+ this.openNotesContainer(attrs);
401
+ return;
402
+ }
403
+ if (name === "note" || name === "statutoryNote" || name === "editorialNote" || name === "changeNote") {
404
+ this.openNote(name, attrs);
405
+ return;
406
+ }
407
+ if (name === "sourceCredit") {
408
+ this.openSourceCredit();
409
+ return;
410
+ }
411
+ if (name === "quotedContent") {
412
+ this.openQuotedContent(attrs);
413
+ return;
414
+ }
415
+ if (name === "num") {
416
+ const parentFrame = this.findParentFrame("level");
417
+ if (parentFrame && attrs["value"]) {
418
+ parentFrame.node.numValue = attrs["value"];
419
+ }
420
+ this.stack.push({ kind: "ignore", node: null, elementName: name, textBuffer: "" });
421
+ return;
422
+ }
423
+ if (name === "heading") {
424
+ this.stack.push({ kind: "ignore", node: null, elementName: name, textBuffer: "" });
425
+ return;
426
+ }
427
+ if (name === "p") {
428
+ this.stack.push({ kind: "ignore", node: null, elementName: name, textBuffer: "" });
429
+ return;
430
+ }
431
+ this.stack.push({ kind: "ignore", node: null, elementName: name, textBuffer: "" });
432
+ }
433
+ /**
434
+ * Handle a closeElement event from the parser.
435
+ */
436
+ onCloseElement(name) {
437
+ if (name === "meta") {
438
+ this.inMeta = false;
439
+ this.popFrame("meta");
440
+ return;
441
+ }
442
+ if (this.inMeta) {
443
+ this.handleMetaClose(name);
444
+ return;
445
+ }
446
+ if (name === "uscDoc" || name === "main") {
447
+ return;
448
+ }
449
+ if (name === "xhtml:table" && this.tableCollector) {
450
+ this.finishTable();
451
+ return;
452
+ }
453
+ if (this.tableCollector) {
454
+ this.handleTableClose(name);
455
+ return;
456
+ }
457
+ if (name === "toc") {
458
+ this.tocDepth = Math.max(0, this.tocDepth - 1);
459
+ return;
460
+ }
461
+ if (name === "layout" && this.layoutCollector) {
462
+ this.finishLayout();
463
+ return;
464
+ }
465
+ if (this.layoutCollector) {
466
+ this.handleLayoutClose(name);
467
+ return;
468
+ }
469
+ if (this.tocDepth > 0) {
470
+ return;
471
+ }
472
+ if (name === "num" || name === "heading") {
473
+ const frame2 = this.peekFrame();
474
+ if (frame2?.elementName === name) {
475
+ this.handleNumOrHeadingClose(name, frame2);
476
+ this.stack.pop();
477
+ return;
478
+ }
479
+ }
480
+ if (name === "p") {
481
+ const frame2 = this.peekFrame();
482
+ if (frame2?.elementName === "p") {
483
+ this.handlePClose(frame2);
484
+ this.stack.pop();
485
+ return;
486
+ }
487
+ }
488
+ if (LEVEL_ELEMENTS.has(name)) {
489
+ this.closeLevel(name);
490
+ return;
491
+ }
492
+ if (CONTENT_ELEMENTS.has(name)) {
493
+ this.closeContent();
494
+ return;
495
+ }
496
+ if (INLINE_ELEMENTS.has(name)) {
497
+ this.closeInline();
498
+ return;
499
+ }
500
+ if (name === "notes") {
501
+ this.closeNotesContainer();
502
+ return;
503
+ }
504
+ if (name === "note" || name === "statutoryNote" || name === "editorialNote" || name === "changeNote") {
505
+ this.closeNote();
506
+ return;
507
+ }
508
+ if (name === "sourceCredit") {
509
+ this.closeSourceCredit();
510
+ return;
511
+ }
512
+ if (name === "quotedContent") {
513
+ this.closeQuotedContent();
514
+ return;
515
+ }
516
+ const frame = this.peekFrame();
517
+ if (frame?.kind === "ignore" && frame.elementName === name) {
518
+ this.stack.pop();
519
+ }
520
+ }
521
+ /**
522
+ * Handle a text event from the parser.
523
+ */
524
+ onText(text) {
525
+ if (this.tableCollector?.inCell) {
526
+ this.tableCollector.cellText += text;
527
+ return;
528
+ }
529
+ if (this.tableCollector) {
530
+ return;
531
+ }
532
+ if (this.layoutCollector?.inCell) {
533
+ this.layoutCollector.cellText += text;
534
+ return;
535
+ }
536
+ if (this.layoutCollector) {
537
+ return;
538
+ }
539
+ if (this.tocDepth > 0) {
540
+ return;
541
+ }
542
+ if (this.inMeta) {
543
+ const frame2 = this.peekFrame();
544
+ if (frame2) {
545
+ frame2.textBuffer += text;
546
+ }
547
+ return;
548
+ }
549
+ const frame = this.peekFrame();
550
+ if (!frame) return;
551
+ if (frame.kind === "ignore") {
552
+ frame.textBuffer += text;
553
+ return;
554
+ }
555
+ if (frame.kind === "inline") {
556
+ const inlineNode = frame.node;
557
+ const textChild = { type: "inline", inlineType: "text", text };
558
+ if (!inlineNode.children) {
559
+ inlineNode.children = [];
560
+ }
561
+ inlineNode.children.push(textChild);
562
+ this.bubbleTextToCollector(text);
563
+ return;
564
+ }
565
+ if (frame.kind === "content" || frame.kind === "sourceCredit") {
566
+ if (!text.trim()) return;
567
+ const textNode = { type: "inline", inlineType: "text", text };
568
+ const parent = frame.node;
569
+ if (parent && "children" in parent && Array.isArray(parent.children)) {
570
+ parent.children.push(textNode);
571
+ }
572
+ return;
573
+ }
574
+ if (frame.kind === "note" || frame.kind === "quotedContent") {
575
+ const trimmed = text.trim();
576
+ if (trimmed) {
577
+ const textNode = { type: "inline", inlineType: "text", text };
578
+ const contentNode = {
579
+ type: "content",
580
+ variant: "content",
581
+ children: [textNode]
582
+ };
583
+ const parent = frame.node;
584
+ if (parent && "children" in parent && Array.isArray(parent.children)) {
585
+ parent.children.push(contentNode);
586
+ }
587
+ }
588
+ return;
589
+ }
590
+ if (frame.kind === "level") {
591
+ const trimmed = text.trim();
592
+ if (trimmed) {
593
+ const textNode = { type: "inline", inlineType: "text", text };
594
+ const contentNode = {
595
+ type: "content",
596
+ variant: "content",
597
+ children: [textNode]
598
+ };
599
+ frame.node.children.push(contentNode);
600
+ }
601
+ return;
602
+ }
603
+ }
604
+ // ---------------------------------------------------------------------------
605
+ // Meta handling
606
+ // ---------------------------------------------------------------------------
607
+ handleMetaOpen(name, attrs) {
608
+ this.metaField = name;
609
+ this.metaPropertyAttrs = attrs;
610
+ this.stack.push({ kind: "ignore", node: null, elementName: name, textBuffer: "" });
611
+ }
612
+ handleMetaClose(name) {
613
+ const frame = this.peekFrame();
614
+ if (frame?.elementName !== name) return;
615
+ const text = frame.textBuffer.trim();
616
+ this.stack.pop();
617
+ switch (this.metaField) {
618
+ case "dc:title":
619
+ this.documentMeta.dcTitle = text;
620
+ break;
621
+ case "dc:type":
622
+ this.documentMeta.dcType = text;
623
+ break;
624
+ case "docNumber":
625
+ this.documentMeta.docNumber = text;
626
+ break;
627
+ case "docPublicationName":
628
+ this.documentMeta.docPublicationName = text;
629
+ break;
630
+ case "docReleasePoint":
631
+ this.documentMeta.releasePoint = text;
632
+ break;
633
+ case "dc:publisher":
634
+ this.documentMeta.publisher = text;
635
+ break;
636
+ case "dcterms:created":
637
+ this.documentMeta.created = text;
638
+ break;
639
+ case "dc:creator":
640
+ this.documentMeta.creator = text;
641
+ break;
642
+ case "property":
643
+ if (this.metaPropertyAttrs?.["role"] === "is-positive-law") {
644
+ this.documentMeta.positivelaw = text.toLowerCase() === "yes";
645
+ }
646
+ break;
647
+ }
648
+ this.metaField = null;
649
+ this.metaPropertyAttrs = null;
650
+ }
651
+ // ---------------------------------------------------------------------------
652
+ // Level handling
653
+ // ---------------------------------------------------------------------------
654
+ openLevel(levelType, attrs) {
655
+ const node = {
656
+ type: "level",
657
+ levelType,
658
+ identifier: attrs["identifier"],
659
+ status: attrs["status"],
660
+ sourceElement: levelType,
661
+ children: []
662
+ };
663
+ const emitIndex = LEVEL_TYPES_ARRAY.indexOf(this.options.emitAt);
664
+ const thisIndex = LEVEL_TYPES_ARRAY.indexOf(levelType);
665
+ if (thisIndex >= 0 && thisIndex < emitIndex && this.quotedContentDepth === 0) {
666
+ this.ancestors.push({
667
+ levelType,
668
+ identifier: attrs["identifier"]
669
+ });
670
+ }
671
+ this.stack.push({ kind: "level", node, elementName: levelType, textBuffer: "" });
672
+ }
673
+ closeLevel(levelType) {
674
+ const frame = this.popFrame("level");
675
+ if (!frame) return;
676
+ const node = frame.node;
677
+ if (this.quotedContentDepth > 0) {
678
+ this.addToParent(node);
679
+ return;
680
+ }
681
+ if (levelType === this.options.emitAt) {
682
+ const context = {
683
+ ancestors: [...this.ancestors],
684
+ documentMeta: { ...this.documentMeta }
685
+ };
686
+ this.options.onEmit(node, context);
687
+ return;
688
+ }
689
+ const emitIndex = LEVEL_TYPES_ARRAY.indexOf(this.options.emitAt);
690
+ const thisIndex = LEVEL_TYPES_ARRAY.indexOf(levelType);
691
+ if (thisIndex < emitIndex) {
692
+ this.ancestors.pop();
693
+ return;
694
+ }
695
+ this.addToParent(node);
696
+ }
697
+ // ---------------------------------------------------------------------------
698
+ // Content handling
699
+ // ---------------------------------------------------------------------------
700
+ openContent(variant, _attrs) {
701
+ const node = {
702
+ type: "content",
703
+ variant,
704
+ sourceElement: variant,
705
+ children: []
706
+ };
707
+ this.stack.push({ kind: "content", node, elementName: variant, textBuffer: "" });
708
+ }
709
+ closeContent() {
710
+ const frame = this.popFrame("content");
711
+ if (!frame) return;
712
+ if (frame.node) this.addToParent(frame.node);
713
+ }
714
+ // ---------------------------------------------------------------------------
715
+ // Inline handling
716
+ // ---------------------------------------------------------------------------
717
+ openInline(name, attrs) {
718
+ const inlineType = INLINE_TYPE_MAP[name] ?? "text";
719
+ const node = {
720
+ type: "inline",
721
+ inlineType,
722
+ sourceElement: name
723
+ };
724
+ if (name === "ref") {
725
+ if (attrs["href"]) {
726
+ node.href = attrs["href"];
727
+ }
728
+ if (attrs["class"] === "footnoteRef") {
729
+ node.inlineType = "footnoteRef";
730
+ node.idref = attrs["idref"];
731
+ }
732
+ }
733
+ if (name === "date" && attrs["date"]) {
734
+ node.href = attrs["date"];
735
+ }
736
+ this.stack.push({ kind: "inline", node, elementName: name, textBuffer: "" });
737
+ }
738
+ closeInline() {
739
+ const frame = this.popFrame("inline");
740
+ if (!frame) return;
741
+ const node = frame.node;
742
+ const firstChild = node.children?.[0];
743
+ if (node.children?.length === 1 && firstChild?.inlineType === "text" && !firstChild.children) {
744
+ node.text = firstChild.text;
745
+ node.children = void 0;
746
+ }
747
+ this.addInlineToParent(node);
748
+ }
749
+ // ---------------------------------------------------------------------------
750
+ // Note handling
751
+ // ---------------------------------------------------------------------------
752
+ openNotesContainer(attrs) {
753
+ const node = {
754
+ type: "notesContainer",
755
+ notesType: attrs["type"],
756
+ children: []
757
+ };
758
+ this.stack.push({ kind: "notesContainer", node, elementName: "notes", textBuffer: "" });
759
+ }
760
+ closeNotesContainer() {
761
+ const frame = this.popFrame("notesContainer");
762
+ if (!frame) return;
763
+ if (frame.node) this.addToParent(frame.node);
764
+ }
765
+ openNote(name, attrs) {
766
+ const node = {
767
+ type: "note",
768
+ topic: attrs["topic"],
769
+ role: attrs["role"],
770
+ sourceElement: name,
771
+ children: []
772
+ };
773
+ this.stack.push({ kind: "note", node, elementName: name, textBuffer: "" });
774
+ }
775
+ closeNote() {
776
+ const frame = this.popFrame("note");
777
+ if (!frame) return;
778
+ if (frame.node) this.addToParent(frame.node);
779
+ }
780
+ // ---------------------------------------------------------------------------
781
+ // SourceCredit handling
782
+ // ---------------------------------------------------------------------------
783
+ openSourceCredit() {
784
+ const node = {
785
+ type: "sourceCredit",
786
+ children: []
787
+ };
788
+ this.stack.push({ kind: "sourceCredit", node, elementName: "sourceCredit", textBuffer: "" });
789
+ }
790
+ closeSourceCredit() {
791
+ const frame = this.popFrame("sourceCredit");
792
+ if (!frame) return;
793
+ if (frame.node) this.addToParent(frame.node);
794
+ }
795
+ // ---------------------------------------------------------------------------
796
+ // QuotedContent handling
797
+ // ---------------------------------------------------------------------------
798
+ openQuotedContent(attrs) {
799
+ this.quotedContentDepth++;
800
+ const node = {
801
+ type: "quotedContent",
802
+ origin: attrs["origin"],
803
+ children: []
804
+ };
805
+ this.stack.push({ kind: "quotedContent", node, elementName: "quotedContent", textBuffer: "" });
806
+ }
807
+ closeQuotedContent() {
808
+ this.quotedContentDepth--;
809
+ const frame = this.popFrame("quotedContent");
810
+ if (!frame) return;
811
+ const parentFrame = this.peekFrame();
812
+ if (parentFrame && (parentFrame.kind === "content" || parentFrame.kind === "inline" || parentFrame.kind === "sourceCredit")) {
813
+ const qNode = {
814
+ type: "inline",
815
+ inlineType: "quoted",
816
+ text: this.extractText(frame.node)
817
+ };
818
+ this.addInlineToParent(qNode);
819
+ } else {
820
+ if (frame.node) this.addToParent(frame.node);
821
+ }
822
+ }
823
+ // ---------------------------------------------------------------------------
824
+ // Num/Heading/P helpers
825
+ // ---------------------------------------------------------------------------
826
+ handleNumOrHeadingClose(name, frame) {
827
+ const text = frame.textBuffer.trim();
828
+ const noteFrame = this.findParentFrame("note");
829
+ const levelFrame = this.findParentFrame("level");
830
+ if (name === "heading" && noteFrame && (!levelFrame || this.stack.indexOf(noteFrame) > this.stack.indexOf(levelFrame))) {
831
+ noteFrame.node.heading = text;
832
+ return;
833
+ }
834
+ if (!levelFrame) return;
835
+ const levelNode = levelFrame.node;
836
+ if (name === "num") {
837
+ levelNode.num = text;
838
+ } else if (name === "heading") {
839
+ levelNode.heading = text;
840
+ }
841
+ const ancestor = this.ancestors.find(
842
+ (a) => a.levelType === levelNode.levelType && a.identifier === levelNode.identifier
843
+ );
844
+ if (ancestor) {
845
+ if (name === "num") {
846
+ ancestor.numValue = levelNode.numValue;
847
+ } else if (name === "heading") {
848
+ ancestor.heading = text;
849
+ }
850
+ }
851
+ }
852
+ handlePClose(frame) {
853
+ const text = frame.textBuffer;
854
+ if (!text) return;
855
+ const parentFrame = this.peekFrameAbove(frame);
856
+ if (!parentFrame) return;
857
+ if (parentFrame.kind === "content" || parentFrame.kind === "sourceCredit") {
858
+ const textNode = { type: "inline", inlineType: "text", text };
859
+ const parent = parentFrame.node;
860
+ if (parent && "children" in parent && Array.isArray(parent.children)) {
861
+ const children = parent.children;
862
+ if (children.length > 0) {
863
+ children.push({ type: "inline", inlineType: "text", text: "\n\n" });
864
+ }
865
+ children.push(textNode);
866
+ }
867
+ } else if (parentFrame.kind === "note" || parentFrame.kind === "level" || parentFrame.kind === "quotedContent") {
868
+ const textNode = { type: "inline", inlineType: "text", text };
869
+ const contentNode = {
870
+ type: "content",
871
+ variant: "content",
872
+ children: [textNode]
873
+ };
874
+ const parent = parentFrame.node;
875
+ if (parent && "children" in parent && Array.isArray(parent.children)) {
876
+ parent.children.push(contentNode);
877
+ }
878
+ }
879
+ }
880
+ /**
881
+ * Bubble text content up to the nearest heading/num ignore frame on the stack.
882
+ * This handles patterns like <heading><b>Editorial Notes</b></heading>
883
+ * where the text is inside an inline child but needs to be collected by the heading frame.
884
+ */
885
+ // ---------------------------------------------------------------------------
886
+ // XHTML table handling
887
+ // ---------------------------------------------------------------------------
888
+ handleTableOpen(name, attrs) {
889
+ const tc = this.tableCollector;
890
+ if (!tc) return;
891
+ switch (name) {
892
+ case "xhtml:thead":
893
+ tc.inHead = true;
894
+ break;
895
+ case "xhtml:tbody":
896
+ tc.inHead = false;
897
+ break;
898
+ case "xhtml:tr":
899
+ tc.currentRow = [];
900
+ break;
901
+ case "xhtml:th":
902
+ case "xhtml:td":
903
+ tc.inCell = true;
904
+ tc.cellText = "";
905
+ tc.cellDepth = 0;
906
+ if (attrs["colspan"] && attrs["colspan"] !== "1") {
907
+ tc.isComplex = true;
908
+ }
909
+ if (attrs["rowspan"] && attrs["rowspan"] !== "1") {
910
+ tc.isComplex = true;
911
+ }
912
+ break;
913
+ // Sub-elements inside cells (p, span, i, a, etc.) — track depth
914
+ default:
915
+ if (tc.inCell) {
916
+ tc.cellDepth++;
917
+ }
918
+ break;
919
+ }
920
+ }
921
+ handleTableClose(name) {
922
+ const tc = this.tableCollector;
923
+ if (!tc) return;
924
+ switch (name) {
925
+ case "xhtml:thead":
926
+ tc.inHead = false;
927
+ break;
928
+ case "xhtml:tbody":
929
+ break;
930
+ case "xhtml:tr":
931
+ if (tc.inHead) {
932
+ tc.headers.push(tc.currentRow);
933
+ } else {
934
+ tc.rows.push(tc.currentRow);
935
+ }
936
+ tc.currentRow = [];
937
+ break;
938
+ case "xhtml:th":
939
+ case "xhtml:td":
940
+ tc.currentRow.push(tc.cellText.trim());
941
+ tc.inCell = false;
942
+ tc.cellText = "";
943
+ break;
944
+ default:
945
+ if (tc.inCell) {
946
+ tc.cellDepth = Math.max(0, tc.cellDepth - 1);
947
+ }
948
+ break;
949
+ }
950
+ }
951
+ finishTable() {
952
+ const tc = this.tableCollector;
953
+ if (!tc) return;
954
+ this.tableCollector = null;
955
+ const node = {
956
+ type: "table",
957
+ variant: "xhtml",
958
+ headers: tc.headers,
959
+ rows: tc.rows
960
+ };
961
+ this.addToParent(node);
962
+ }
963
+ // ---------------------------------------------------------------------------
964
+ // USLM layout handling
965
+ // ---------------------------------------------------------------------------
966
+ handleLayoutOpen(name, _attrs) {
967
+ const lc = this.layoutCollector;
968
+ if (!lc) return;
969
+ switch (name) {
970
+ case "header":
971
+ lc.inHead = true;
972
+ lc.currentRow = [];
973
+ break;
974
+ case "tocItem":
975
+ case "row":
976
+ lc.currentRow = [];
977
+ break;
978
+ case "column":
979
+ lc.inCell = true;
980
+ lc.cellText = "";
981
+ break;
982
+ default:
983
+ break;
984
+ }
985
+ }
986
+ handleLayoutClose(name) {
987
+ const lc = this.layoutCollector;
988
+ if (!lc) return;
989
+ switch (name) {
990
+ case "header":
991
+ lc.headers.push(lc.currentRow);
992
+ lc.currentRow = [];
993
+ lc.inHead = false;
994
+ break;
995
+ case "tocItem":
996
+ case "row":
997
+ lc.rows.push(lc.currentRow);
998
+ lc.currentRow = [];
999
+ break;
1000
+ case "column":
1001
+ lc.currentRow.push(lc.cellText.trim());
1002
+ lc.inCell = false;
1003
+ lc.cellText = "";
1004
+ break;
1005
+ default:
1006
+ break;
1007
+ }
1008
+ }
1009
+ finishLayout() {
1010
+ const lc = this.layoutCollector;
1011
+ if (!lc) return;
1012
+ this.layoutCollector = null;
1013
+ if (lc.rows.length === 0 && lc.headers.length === 0) return;
1014
+ const node = {
1015
+ type: "table",
1016
+ variant: "layout",
1017
+ headers: lc.headers,
1018
+ rows: lc.rows
1019
+ };
1020
+ this.addToParent(node);
1021
+ }
1022
+ bubbleTextToCollector(text) {
1023
+ for (let i = this.stack.length - 2; i >= 0; i--) {
1024
+ const f = this.stack[i];
1025
+ if (f?.kind === "ignore" && (f.elementName === "heading" || f.elementName === "num")) {
1026
+ f.textBuffer += text;
1027
+ return;
1028
+ }
1029
+ if (f && f.kind !== "inline" && f.kind !== "ignore") {
1030
+ return;
1031
+ }
1032
+ }
1033
+ }
1034
+ // ---------------------------------------------------------------------------
1035
+ // Stack utilities
1036
+ // ---------------------------------------------------------------------------
1037
+ peekFrame() {
1038
+ return this.stack[this.stack.length - 1];
1039
+ }
1040
+ peekFrameAbove(belowFrame) {
1041
+ const idx = this.stack.lastIndexOf(belowFrame);
1042
+ if (idx > 0) {
1043
+ return this.stack[idx - 1];
1044
+ }
1045
+ return void 0;
1046
+ }
1047
+ popFrame(expectedKind) {
1048
+ const frame = this.stack[this.stack.length - 1];
1049
+ if (frame?.kind === expectedKind) {
1050
+ this.stack.pop();
1051
+ return frame;
1052
+ }
1053
+ for (let i = this.stack.length - 1; i >= 0; i--) {
1054
+ const f = this.stack[i];
1055
+ if (f?.kind === expectedKind) {
1056
+ return this.stack.splice(i, 1)[0];
1057
+ }
1058
+ }
1059
+ return void 0;
1060
+ }
1061
+ findParentFrame(kind) {
1062
+ for (let i = this.stack.length - 1; i >= 0; i--) {
1063
+ const f = this.stack[i];
1064
+ if (f?.kind === kind) {
1065
+ return f;
1066
+ }
1067
+ }
1068
+ return void 0;
1069
+ }
1070
+ /**
1071
+ * Add a block-level AST node to the nearest parent that accepts children.
1072
+ */
1073
+ addToParent(node) {
1074
+ for (let i = this.stack.length - 1; i >= 0; i--) {
1075
+ const frame = this.stack[i];
1076
+ if (!frame) continue;
1077
+ if (frame.kind === "level") {
1078
+ frame.node.children.push(node);
1079
+ return;
1080
+ }
1081
+ if (frame.kind === "note") {
1082
+ frame.node.children.push(node);
1083
+ return;
1084
+ }
1085
+ if (frame.kind === "notesContainer") {
1086
+ frame.node.children.push(node);
1087
+ return;
1088
+ }
1089
+ if (frame.kind === "quotedContent") {
1090
+ frame.node.children.push(node);
1091
+ return;
1092
+ }
1093
+ }
1094
+ }
1095
+ /**
1096
+ * Add an inline AST node to the nearest parent that accepts inline children.
1097
+ */
1098
+ addInlineToParent(node) {
1099
+ for (let i = this.stack.length - 1; i >= 0; i--) {
1100
+ const frame = this.stack[i];
1101
+ if (!frame) continue;
1102
+ if (frame.kind === "inline") {
1103
+ const parent = frame.node;
1104
+ if (!parent.children) {
1105
+ parent.children = [];
1106
+ }
1107
+ parent.children.push(node);
1108
+ return;
1109
+ }
1110
+ if (frame.kind === "content" || frame.kind === "sourceCredit") {
1111
+ const parent = frame.node;
1112
+ if (parent && "children" in parent && Array.isArray(parent.children)) {
1113
+ parent.children.push(node);
1114
+ }
1115
+ return;
1116
+ }
1117
+ }
1118
+ }
1119
+ /**
1120
+ * Extract plain text from a node tree (for flattening quotedContent).
1121
+ */
1122
+ extractText(node) {
1123
+ let result = "";
1124
+ for (const child of node.children) {
1125
+ if (child.type === "inline") {
1126
+ result += this.extractInlineText(child);
1127
+ } else if (child.type === "content") {
1128
+ for (const inline of child.children) {
1129
+ result += this.extractInlineText(inline);
1130
+ }
1131
+ }
1132
+ }
1133
+ return result;
1134
+ }
1135
+ extractInlineText(node) {
1136
+ if (node.text) return node.text;
1137
+ if (node.children) {
1138
+ return node.children.map((c) => this.extractInlineText(c)).join("");
1139
+ }
1140
+ return "";
1141
+ }
1142
+ };
1143
+ var LEVEL_TYPES_ARRAY = LEVEL_TYPES;
1144
+
1145
+ // src/markdown/frontmatter.ts
1146
+ import { readFileSync } from "fs";
1147
+ import { resolve, dirname } from "path";
1148
+ import { fileURLToPath } from "url";
1149
+ import { stringify } from "yaml";
1150
+ function readPackageVersion() {
1151
+ try {
1152
+ const dir = dirname(fileURLToPath(import.meta.url));
1153
+ const pkgPath = resolve(dir, "..", "..", "package.json");
1154
+ const raw = readFileSync(pkgPath, "utf-8");
1155
+ return JSON.parse(raw).version;
1156
+ } catch {
1157
+ return "0.0.0";
1158
+ }
1159
+ }
1160
+ var FORMAT_VERSION = "1.0.0";
1161
+ var GENERATOR = `lexbuild@${readPackageVersion()}`;
1162
+ function generateFrontmatter(data) {
1163
+ const fm = {
1164
+ identifier: data.identifier,
1165
+ title: data.title,
1166
+ title_number: data.title_number,
1167
+ title_name: data.title_name,
1168
+ section_number: data.section_number,
1169
+ section_name: data.section_name
1170
+ };
1171
+ if (data.chapter_number !== void 0) {
1172
+ fm["chapter_number"] = data.chapter_number;
1173
+ }
1174
+ if (data.chapter_name !== void 0) {
1175
+ fm["chapter_name"] = data.chapter_name;
1176
+ }
1177
+ if (data.subchapter_number !== void 0) {
1178
+ fm["subchapter_number"] = data.subchapter_number;
1179
+ }
1180
+ if (data.subchapter_name !== void 0) {
1181
+ fm["subchapter_name"] = data.subchapter_name;
1182
+ }
1183
+ if (data.part_number !== void 0) {
1184
+ fm["part_number"] = data.part_number;
1185
+ }
1186
+ if (data.part_name !== void 0) {
1187
+ fm["part_name"] = data.part_name;
1188
+ }
1189
+ fm["positive_law"] = data.positive_law;
1190
+ fm["currency"] = data.currency;
1191
+ fm["last_updated"] = data.last_updated;
1192
+ fm["format_version"] = FORMAT_VERSION;
1193
+ fm["generator"] = GENERATOR;
1194
+ if (data.source_credit !== void 0) {
1195
+ fm["source_credit"] = data.source_credit;
1196
+ }
1197
+ if (data.status !== void 0) {
1198
+ fm["status"] = data.status;
1199
+ }
1200
+ const yamlStr = stringify(fm, {
1201
+ lineWidth: 0,
1202
+ // Don't wrap long lines
1203
+ defaultStringType: "QUOTE_DOUBLE",
1204
+ defaultKeyType: "PLAIN"
1205
+ });
1206
+ return `---
1207
+ ${yamlStr}---`;
1208
+ }
1209
+
1210
+ // src/markdown/renderer.ts
1211
+ var DEFAULT_RENDER_OPTIONS = {
1212
+ headingOffset: 0,
1213
+ linkStyle: "plaintext"
1214
+ };
1215
+ function renderDocument(sectionNode, frontmatter, options = DEFAULT_RENDER_OPTIONS) {
1216
+ const fm = generateFrontmatter(frontmatter);
1217
+ const content = renderSection(sectionNode, options);
1218
+ return `${fm}
1219
+
1220
+ ${content}
1221
+ `;
1222
+ }
1223
+ function renderSection(node, options = DEFAULT_RENDER_OPTIONS) {
1224
+ const parts = [];
1225
+ const headingLevel = 1 + options.headingOffset;
1226
+ const prefix = "#".repeat(headingLevel);
1227
+ const numDisplay = node.num ?? "";
1228
+ const heading = node.heading ? ` ${node.heading}` : "";
1229
+ parts.push(`${prefix} ${numDisplay}${heading}`);
1230
+ for (const child of node.children) {
1231
+ const rendered = renderNode(child, options);
1232
+ if (rendered) {
1233
+ parts.push(rendered);
1234
+ }
1235
+ }
1236
+ return parts.join("\n\n");
1237
+ }
1238
+ function renderNode(node, options = DEFAULT_RENDER_OPTIONS) {
1239
+ switch (node.type) {
1240
+ case "level":
1241
+ return renderLevel(node, options);
1242
+ case "content":
1243
+ return renderContent(node);
1244
+ case "inline":
1245
+ return renderInline(node, options);
1246
+ case "sourceCredit":
1247
+ return renderSourceCredit(node, options);
1248
+ case "note":
1249
+ return renderNote(node, options);
1250
+ case "notesContainer":
1251
+ return renderNotesContainer(node, options);
1252
+ case "quotedContent":
1253
+ return renderQuotedContent(node, options);
1254
+ case "table":
1255
+ return renderTable(node);
1256
+ case "toc":
1257
+ case "tocItem":
1258
+ return "";
1259
+ default:
1260
+ return "";
1261
+ }
1262
+ }
1263
+ function renderLevel(node, options) {
1264
+ if (SMALL_LEVELS.has(node.levelType)) {
1265
+ return renderSmallLevel(node, options);
1266
+ }
1267
+ const parts = [];
1268
+ if (node.num ?? node.heading) {
1269
+ const numDisplay = node.num ?? "";
1270
+ const heading = node.heading ? ` ${node.heading}` : "";
1271
+ parts.push(`**${numDisplay}${heading}**`);
1272
+ }
1273
+ for (const child of node.children) {
1274
+ const rendered = renderNode(child, options);
1275
+ if (rendered) {
1276
+ parts.push(rendered);
1277
+ }
1278
+ }
1279
+ return parts.join("\n\n");
1280
+ }
1281
+ function renderSmallLevel(node, options) {
1282
+ const parts = [];
1283
+ const numDisplay = node.num ?? (node.numValue ? `(${node.numValue})` : "");
1284
+ let prefix = numDisplay ? `**${numDisplay}**` : "";
1285
+ if (node.heading) {
1286
+ prefix += ` **${node.heading}**`;
1287
+ }
1288
+ const contentParts = [];
1289
+ const childParts = [];
1290
+ for (const child of node.children) {
1291
+ if (child.type === "content") {
1292
+ contentParts.push(renderContent(child));
1293
+ } else if (child.type === "level") {
1294
+ childParts.push(renderNode(child, options));
1295
+ } else {
1296
+ const rendered = renderNode(child, options);
1297
+ if (rendered) {
1298
+ childParts.push(rendered);
1299
+ }
1300
+ }
1301
+ }
1302
+ if (contentParts.length > 0) {
1303
+ const firstContent = contentParts[0] ?? "";
1304
+ if (prefix) {
1305
+ parts.push(`${prefix} ${firstContent}`);
1306
+ } else {
1307
+ parts.push(firstContent);
1308
+ }
1309
+ for (let i = 1; i < contentParts.length; i++) {
1310
+ parts.push(contentParts[i] ?? "");
1311
+ }
1312
+ } else if (prefix) {
1313
+ parts.push(prefix);
1314
+ }
1315
+ for (const child of childParts) {
1316
+ parts.push(child);
1317
+ }
1318
+ return parts.join("\n\n");
1319
+ }
1320
+ function renderContent(node) {
1321
+ const raw = renderInlineChildren(node.children);
1322
+ return normalizeWhitespace(raw);
1323
+ }
1324
+ function renderInlineChildren(children, options) {
1325
+ return children.map((child) => renderInline(child, options)).join("");
1326
+ }
1327
+ function normalizeWhitespace(text) {
1328
+ return text.trim().replace(/\n\s*\n/g, "\n\n");
1329
+ }
1330
+ function renderInline(node, options) {
1331
+ switch (node.inlineType) {
1332
+ case "text":
1333
+ return node.text ?? renderInlineChildren(node.children ?? [], options);
1334
+ case "bold":
1335
+ return `**${getInlineText(node, options)}**`;
1336
+ case "italic":
1337
+ return `*${getInlineText(node, options)}*`;
1338
+ case "ref":
1339
+ return renderRef(node, options);
1340
+ case "date":
1341
+ return getInlineText(node, options);
1342
+ case "term":
1343
+ return `**${getInlineText(node, options)}**`;
1344
+ case "quoted":
1345
+ return `"${node.text ?? getInlineText(node, options)}"`;
1346
+ case "sup":
1347
+ return `<sup>${getInlineText(node, options)}</sup>`;
1348
+ case "sub":
1349
+ return `<sub>${getInlineText(node, options)}</sub>`;
1350
+ case "footnoteRef":
1351
+ return `[^${getInlineText(node, options)}]`;
1352
+ default:
1353
+ return getInlineText(node, options);
1354
+ }
1355
+ }
1356
+ function getInlineText(node, options) {
1357
+ if (node.text) return node.text;
1358
+ if (node.children) return renderInlineChildren(node.children, options);
1359
+ return "";
1360
+ }
1361
+ function renderRef(node, options) {
1362
+ const text = getInlineText(node, options);
1363
+ const href = node.href;
1364
+ if (!href) {
1365
+ return text;
1366
+ }
1367
+ const style = options?.linkStyle ?? "plaintext";
1368
+ if (style === "plaintext") {
1369
+ return text;
1370
+ }
1371
+ if (style === "relative" && options?.resolveLink) {
1372
+ const resolved = options.resolveLink(href);
1373
+ if (resolved) {
1374
+ return `[${text}](${resolved})`;
1375
+ }
1376
+ }
1377
+ if (href.startsWith("/us/usc/")) {
1378
+ const olrcUrl = buildOlrcUrl(href);
1379
+ return `[${text}](${olrcUrl})`;
1380
+ }
1381
+ return text;
1382
+ }
1383
+ function buildOlrcUrl(identifier) {
1384
+ const match = /^\/us\/usc\/t(\d+)(?:\/s(.+?))?(?:\/|$)/.exec(identifier);
1385
+ if (match) {
1386
+ const titleNum = match[1];
1387
+ const sectionNum = match[2];
1388
+ if (sectionNum) {
1389
+ return `https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title${titleNum}-section${sectionNum}`;
1390
+ }
1391
+ return `https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title${titleNum}`;
1392
+ }
1393
+ return `https://uscode.house.gov/view.xhtml?req=${encodeURIComponent(identifier)}`;
1394
+ }
1395
+ function renderTable(node) {
1396
+ const allRows = [...node.headers, ...node.rows];
1397
+ if (allRows.length === 0) return "";
1398
+ let colCount = 0;
1399
+ for (const row of allRows) {
1400
+ if (row.length > colCount) colCount = row.length;
1401
+ }
1402
+ if (colCount === 0) return "";
1403
+ const lines = [];
1404
+ let headerRow;
1405
+ for (let i = node.headers.length - 1; i >= 0; i--) {
1406
+ const row = node.headers[i];
1407
+ if (row && row.length === colCount) {
1408
+ headerRow = row;
1409
+ break;
1410
+ }
1411
+ }
1412
+ if (!headerRow) {
1413
+ headerRow = Array.from({ length: colCount }, () => "");
1414
+ }
1415
+ lines.push(`| ${headerRow.map((cell) => cell.replace(/\|/g, "\\|")).join(" | ")} |`);
1416
+ lines.push(`| ${Array.from({ length: colCount }, () => "---").join(" | ")} |`);
1417
+ for (const row of node.rows) {
1418
+ const paddedRow = Array.from({ length: colCount }, (_, i) => row[i] ?? "");
1419
+ lines.push(`| ${paddedRow.map((cell) => cell.replace(/\|/g, "\\|")).join(" | ")} |`);
1420
+ }
1421
+ return lines.join("\n");
1422
+ }
1423
+ function renderSourceCredit(node, options) {
1424
+ const text = renderInlineChildren(node.children, options);
1425
+ return `---
1426
+
1427
+ **Source Credit**: ${text}`;
1428
+ }
1429
+ function renderNotesContainer(node, options) {
1430
+ const filter = options.notesFilter;
1431
+ if (!filter) {
1432
+ const parts2 = [];
1433
+ for (const child of node.children) {
1434
+ const rendered = renderNode(child, options);
1435
+ if (rendered) {
1436
+ parts2.push(rendered);
1437
+ }
1438
+ }
1439
+ return parts2.join("\n\n");
1440
+ }
1441
+ const parts = [];
1442
+ let currentCategory = "unknown";
1443
+ for (const child of node.children) {
1444
+ if (child.type !== "note") {
1445
+ const rendered = renderNode(child, options);
1446
+ if (rendered) parts.push(rendered);
1447
+ continue;
1448
+ }
1449
+ if (child.role === "crossHeading") {
1450
+ if (child.topic === "editorialNotes") {
1451
+ currentCategory = "editorial";
1452
+ } else if (child.topic === "statutoryNotes") {
1453
+ currentCategory = "statutory";
1454
+ }
1455
+ if (shouldIncludeCategory(currentCategory, filter)) {
1456
+ const rendered = renderNode(child, options);
1457
+ if (rendered) parts.push(rendered);
1458
+ }
1459
+ continue;
1460
+ }
1461
+ if (shouldIncludeNote(child, currentCategory, filter)) {
1462
+ const rendered = renderNode(child, options);
1463
+ if (rendered) parts.push(rendered);
1464
+ }
1465
+ }
1466
+ return parts.join("\n\n");
1467
+ }
1468
+ var AMENDMENT_TOPICS = /* @__PURE__ */ new Set([
1469
+ "amendments",
1470
+ "effectiveDateOfAmendment",
1471
+ "shortTitleOfAmendment"
1472
+ ]);
1473
+ var EDITORIAL_TOPICS = /* @__PURE__ */ new Set(["codification", "dispositionOfSections"]);
1474
+ var STATUTORY_TOPICS = /* @__PURE__ */ new Set([
1475
+ "changeOfName",
1476
+ "regulations",
1477
+ "miscellaneous",
1478
+ "repeals",
1479
+ "separability",
1480
+ "crossReferences"
1481
+ ]);
1482
+ function shouldIncludeCategory(category, filter) {
1483
+ if (category === "editorial") return filter.editorial || filter.amendments;
1484
+ if (category === "statutory") return filter.statutory || filter.amendments;
1485
+ return filter.editorial || filter.statutory || filter.amendments;
1486
+ }
1487
+ function shouldIncludeNote(node, currentCategory, filter) {
1488
+ const topic = node.topic ?? "";
1489
+ if (AMENDMENT_TOPICS.has(topic)) return filter.amendments;
1490
+ if (EDITORIAL_TOPICS.has(topic)) return filter.editorial;
1491
+ if (STATUTORY_TOPICS.has(topic)) return filter.statutory;
1492
+ if (currentCategory === "editorial") return filter.editorial;
1493
+ if (currentCategory === "statutory") return filter.statutory;
1494
+ return filter.editorial || filter.statutory || filter.amendments;
1495
+ }
1496
+ function renderNote(node, options) {
1497
+ const parts = [];
1498
+ if (node.role === "crossHeading" && node.heading) {
1499
+ parts.push(`## ${node.heading}`);
1500
+ return parts.join("\n\n");
1501
+ }
1502
+ if (node.heading) {
1503
+ parts.push(`### ${node.heading}`);
1504
+ }
1505
+ for (const child of node.children) {
1506
+ const rendered = renderNode(child, options);
1507
+ if (rendered) {
1508
+ parts.push(rendered);
1509
+ }
1510
+ }
1511
+ return parts.join("\n\n");
1512
+ }
1513
+ function renderQuotedContent(node, options) {
1514
+ const parts = [];
1515
+ for (const child of node.children) {
1516
+ const rendered = renderNode(child, options);
1517
+ if (rendered) {
1518
+ parts.push(rendered);
1519
+ }
1520
+ }
1521
+ const inner = parts.join("\n\n");
1522
+ return inner.split("\n").map((line) => `> ${line}`).join("\n");
1523
+ }
1524
+
1525
+ // src/markdown/links.ts
1526
+ import { relative, dirname as dirname2 } from "path";
1527
+ function parseIdentifier(identifier) {
1528
+ const match = /^\/(\w+)\/(\w+)\/t(\w+)(?:\/s([^/]+)(?:\/(.+))?)?$/.exec(identifier);
1529
+ if (!match) return null;
1530
+ return {
1531
+ jurisdiction: match[1] ?? "",
1532
+ code: match[2] ?? "",
1533
+ titleNum: match[3],
1534
+ sectionNum: match[4],
1535
+ subPath: match[5]
1536
+ };
1537
+ }
1538
+ function createLinkResolver() {
1539
+ const registry = /* @__PURE__ */ new Map();
1540
+ return {
1541
+ register(identifier, filePath) {
1542
+ registry.set(identifier, filePath);
1543
+ },
1544
+ resolve(identifier, fromFile) {
1545
+ const targetPath = registry.get(identifier);
1546
+ if (!targetPath) {
1547
+ const parsed = parseIdentifier(identifier);
1548
+ if (parsed?.sectionNum && parsed.titleNum) {
1549
+ const sectionId = `/us/usc/t${parsed.titleNum}/s${parsed.sectionNum}`;
1550
+ const sectionPath = registry.get(sectionId);
1551
+ if (sectionPath) {
1552
+ return relative(dirname2(fromFile), sectionPath);
1553
+ }
1554
+ }
1555
+ return null;
1556
+ }
1557
+ return relative(dirname2(fromFile), targetPath);
1558
+ },
1559
+ fallbackUrl(identifier) {
1560
+ const parsed = parseIdentifier(identifier);
1561
+ if (!parsed || parsed.code !== "usc") return null;
1562
+ if (parsed.sectionNum) {
1563
+ return `https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title${parsed.titleNum}-section${parsed.sectionNum}`;
1564
+ }
1565
+ if (parsed.titleNum) {
1566
+ return `https://uscode.house.gov/view.xhtml?req=granuleid:USC-prelim-title${parsed.titleNum}`;
1567
+ }
1568
+ return null;
1569
+ }
1570
+ };
1571
+ }
1572
+ export {
1573
+ APPENDIX_LEVEL_ELEMENTS,
1574
+ ASTBuilder,
1575
+ BIG_LEVELS,
1576
+ CONTAINER_ELEMENTS,
1577
+ CONTENT_ELEMENTS,
1578
+ DCTERMS_NS,
1579
+ DC_NS,
1580
+ FORMAT_VERSION,
1581
+ GENERATOR,
1582
+ INLINE_ELEMENTS,
1583
+ LEVEL_ELEMENTS,
1584
+ LEVEL_TYPES,
1585
+ META_ELEMENTS,
1586
+ NAMESPACE_PREFIXES,
1587
+ NOTE_ELEMENTS,
1588
+ SMALL_LEVELS,
1589
+ USLM_NS,
1590
+ XHTML_NS,
1591
+ XMLParser,
1592
+ XSI_NS,
1593
+ createLinkResolver,
1594
+ generateFrontmatter,
1595
+ parseIdentifier,
1596
+ renderDocument,
1597
+ renderNode,
1598
+ renderSection
1599
+ };
1600
+ //# sourceMappingURL=index.js.map