@gmb/bitmark-parser-generator 5.24.0 → 5.25.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/cli/main.js CHANGED
@@ -2,7 +2,7 @@ import process2 from 'process';
2
2
  import { Command, Option, Help, Argument, InvalidArgumentError } from 'commander';
3
3
  import { Enum } from '@ncoderz/superenum';
4
4
  import path3 from 'path';
5
- import fs3 from 'fs-extra';
5
+ import fs4 from 'fs-extra';
6
6
  import 'fs';
7
7
  import os from 'os';
8
8
 
@@ -230,6 +230,7 @@ var BitType = {
230
230
  dangerAlt: "danger-alt",
231
231
  dangerCollapsible: "danger-collapsible",
232
232
  definitionList: "definition-list",
233
+ definitionListCollapsible: "definition-list-collapsible",
233
234
  definitionTerm: "definition-term",
234
235
  deleted: "deleted",
235
236
  details: "details",
@@ -510,6 +511,8 @@ var BitType = {
510
511
  smartStandardArticleNonNormativeCollapsible: "smart-standard-article-non-normative-collapsible",
511
512
  smartStandardArticleNormative: "smart-standard-article-normative",
512
513
  smartStandardArticleNormativeCollapsible: "smart-standard-article-normative-collapsible",
514
+ smartStandardDefinitionListNonNormative: "smart-standard-definition-list-non-normative",
515
+ smartStandardDefinitionListNormative: "smart-standard-definition-list-normative",
513
516
  smartStandardExampleNonNormative: "smart-standard-example-non-normative",
514
517
  smartStandardExampleNonNormativeCollapsible: "smart-standard-example-non-normative-collapsible",
515
518
  smartStandardExampleNormative: "smart-standard-example-normative",
@@ -560,6 +563,8 @@ var BitType = {
560
563
  smartStandardTableNormativeCollapsible: "smart-standard-table-normative-collapsible",
561
564
  standardArticleNonNormative: "standard-article-non-normative",
562
565
  standardArticleNormative: "standard-article-normative",
566
+ standardDefinitionListNonNormative: "standard-definition-list-non-normative",
567
+ standardDefinitionListNormative: "standard-definition-list-normative",
563
568
  standardExampleNonNormative: "standard-example-non-normative",
564
569
  standardExampleNormative: "standard-example-normative",
565
570
  standardImageFigureNonNormative: "standard-image-figure-non-normative",
@@ -711,6 +716,7 @@ var CardSetConfigKey = {
711
716
  flashcard: "flashcard",
712
717
  flashcard1: "flashcard1",
713
718
  definitionList: "definitionList",
719
+ definitionListPlain: "definitionListPlain",
714
720
  elements: "elements",
715
721
  statements: "statements",
716
722
  quiz: "quiz",
@@ -1309,6 +1315,7 @@ var propertyKeys = {
1309
1315
  property_enforceUpdateOverUserInput: "@enforceUpdateOverUserInput",
1310
1316
  property_extractorExtractionTimestamp: "@extractorExtractionTimestamp",
1311
1317
  property_isCaseSensitive: "@isCaseSensitive",
1318
+ property_isCollapsible: "@isCollapsible",
1312
1319
  property_isInfoOnly: "@isInfoOnly",
1313
1320
  property_isPublic: "@isPublic",
1314
1321
  property_isTemplate: "@isTemplate",
@@ -2504,6 +2511,126 @@ var CARDSETS = {
2504
2511
  ]
2505
2512
  },
2506
2513
  //
2514
+ // definition-list-plain
2515
+ //
2516
+ // Same shape as `definitionList`, but every variant body is rendered as
2517
+ // plain text (string), not bitmark+ AST. Used by
2518
+ // `meta-search-default-terms` / `meta-search-default-topics`, which need
2519
+ // term/definition text emitted verbatim instead of as a ProseMirror tree.
2520
+ //
2521
+ [CardSetConfigKey.definitionListPlain]: {
2522
+ jsonKey: "definitions",
2523
+ exportJsonKey: { definitions: "$" },
2524
+ sides: [
2525
+ {
2526
+ name: "term",
2527
+ variants: [
2528
+ {
2529
+ jsonKey: "term.text",
2530
+ exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
2531
+ format: TextFormat.plainText,
2532
+ tags: [
2533
+ {
2534
+ key: ConfigKey.group_standardTags,
2535
+ description: "Standard tags for the definition."
2536
+ },
2537
+ {
2538
+ key: ConfigKey.tag_title,
2539
+ description: "Title of the definition.",
2540
+ format: TagFormat.plainText,
2541
+ jsonKey: "^heading.forKeys",
2542
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2543
+ },
2544
+ {
2545
+ key: ConfigKey.group_resourceIcon,
2546
+ description: "Icon resource for the definition.",
2547
+ format: TagFormat.plainText,
2548
+ jsonKey: "term.icon|resource(type=image, key=image)",
2549
+ exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
2550
+ },
2551
+ {
2552
+ key: ConfigKey.property_example,
2553
+ exportJsonKey: [
2554
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2555
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2556
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2557
+ ],
2558
+ description: "Example marker for the definition term.",
2559
+ format: TagFormat.bitmarkText,
2560
+ nullable: true
2561
+ }
2562
+ ]
2563
+ }
2564
+ ]
2565
+ },
2566
+ {
2567
+ name: "definition",
2568
+ variants: [
2569
+ {
2570
+ jsonKey: "definition.text",
2571
+ exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
2572
+ format: TextFormat.plainText,
2573
+ tags: [
2574
+ {
2575
+ key: ConfigKey.group_standardTags,
2576
+ description: "Standard tags for the definition."
2577
+ },
2578
+ {
2579
+ key: ConfigKey.tag_title,
2580
+ description: "Title of the definition.",
2581
+ format: TagFormat.plainText,
2582
+ jsonKey: "^heading.forValues",
2583
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2584
+ },
2585
+ {
2586
+ key: ConfigKey.group_resourceIcon,
2587
+ description: "Icon resource for the definition.",
2588
+ jsonKey: "definition.icon|resource(type=image, key=image)",
2589
+ exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
2590
+ },
2591
+ {
2592
+ key: ConfigKey.property_example,
2593
+ exportJsonKey: [
2594
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2595
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2596
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2597
+ ],
2598
+ description: "Example marker for the definition.",
2599
+ format: TagFormat.bitmarkText,
2600
+ nullable: true
2601
+ }
2602
+ ]
2603
+ },
2604
+ {
2605
+ jsonKey: "alternativeDefinitions[].text",
2606
+ exportJsonKey: { alternativeDefinitions: [{ text: "$" }] },
2607
+ format: TextFormat.plainText,
2608
+ tags: [
2609
+ {
2610
+ key: ConfigKey.group_standardTags,
2611
+ description: "Standard tags for the definition."
2612
+ },
2613
+ {
2614
+ exportJsonKey: { title: "$" },
2615
+ key: ConfigKey.tag_title,
2616
+ description: "Title of the definition."
2617
+ },
2618
+ {
2619
+ key: ConfigKey.group_resourceIcon,
2620
+ description: "Icon resource for the definition.",
2621
+ jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)",
2622
+ exportJsonKey: {
2623
+ alternativeDefinitions: [{ icon: { type: "image", image: { src: "$" } } }]
2624
+ }
2625
+ }
2626
+ ],
2627
+ repeatCount: Count.infinity
2628
+ }
2629
+ ]
2630
+ }
2631
+ ]
2632
+ },
2633
+ //
2507
2634
  // match-pairs
2508
2635
  //
2509
2636
  [CardSetConfigKey.matchPairs]: {
@@ -3441,12 +3568,29 @@ var CARDSETS = {
3441
3568
  // ALIGN-EXAMPLE-CASCADE §3.10: `questions` is NOT in BPG's
3442
3569
  // `pushExampleDownTree` dispatch — bit-header `[@example]`
3443
3570
  // does not propagate `example`/`isExample` onto question
3444
- // entries. Only entry-local `[@example]` fires (handled by
3445
- // `@keyonly` and the unconditional fallback). No `@absent`
3446
- // rule.
3571
+ // entries. Only entry-local `[@example]` fires.
3572
+ //
3573
+ // BPG's `BaseBuilder.toExample` for interview questions
3574
+ // (Builder.ts:2575,2588) uses `data.__sampleSolutionAst`
3575
+ // as `defaultExample` — bare `[@example]` (which sets
3576
+ // `__isDefaultExample=true, example=undefined`) emits the
3577
+ // parsed sample-solution AST in the `example` slot. The
3578
+ // `@keyonly` rule mirrors that runtime behaviour via
3579
+ // `$parent.sampleSolution` — the chain-walker's post-walk
3580
+ // retry resolves this once the sibling `[@sampleSolution]`
3581
+ // chain has merged into the shared scope (interview's
3582
+ // `[@example]` typically precedes `[@sampleSolution]` in
3583
+ // source). Format coercion turns the resolved string
3584
+ // value into a PM tree.
3447
3585
  key: ConfigKey.property_example,
3448
3586
  exportJsonKey: [
3449
- { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
3587
+ {
3588
+ "@keyonly": {
3589
+ isExample: true,
3590
+ example: "$parent.sampleSolution",
3591
+ "@bit": { isExample: true }
3592
+ }
3593
+ },
3450
3594
  { isExample: true, example: "$", "@bit": { isExample: true } }
3451
3595
  ],
3452
3596
  description: "Example marker for the question (entry-local only; no bit-header cascade).",
@@ -5269,7 +5413,11 @@ var GROUPS = {
5269
5413
  {
5270
5414
  key: ConfigKey.property_mark,
5271
5415
  jsonKey: "marks.mark",
5272
- exportJsonKey: { marks: [{ mark: "$" }] },
5416
+ // `emphasis: 'underline'` is the per-entry default for mark
5417
+ // configs — mirrors the hardcoded default in BPG
5418
+ // `MarkConfigChainContentProcessor.ts:55-58`. Chained
5419
+ // `[@emphasis:…]` overrides via deep-merge into `marks[-1]`.
5420
+ exportJsonKey: { marks: [{ mark: "$", emphasis: "underline" }] },
5273
5421
  description: "The mark configuration",
5274
5422
  format: TagFormat.plainText,
5275
5423
  maxCount: Count.infinity,
@@ -5311,13 +5459,22 @@ var GROUPS = {
5311
5459
  {
5312
5460
  key: ConfigKey.property_example,
5313
5461
  jsonKey: "example",
5314
- exportJsonKey: [
5315
- { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
5316
- { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
5317
- { isExample: true, example: "$", "@bit": { isExample: true } }
5318
- ],
5462
+ // Per-mark `@example` is a boolean toggle. BPG's runtime
5463
+ // `ExampleTagContentProcessor.handleDefaultOnlyExample`
5464
+ // (lines 147-161) normalises ANY `[@example]` or
5465
+ // `[@example:V]` on a mark body-bit to
5466
+ // `__isDefaultExample=true, example=undefined`; the
5467
+ // downstream `toExample(_, _, defaultExample=true)` in
5468
+ // `BaseBuilder.ts:80-87` then emits `example: true`
5469
+ // regardless of source. Declare a single unconditional
5470
+ // boolean-shape rule so the exported schema matches that
5471
+ // runtime semantic instead of the misleading `example: $`
5472
+ // shape (which would only fire if `handleDefaultOnlyExample`
5473
+ // were removed). `@bit: {isExample: true}` cascades the
5474
+ // marker — but NOT the `example` value — to bit root.
5475
+ exportJsonKey: { isExample: true, example: true, "@bit": { isExample: true } },
5319
5476
  description: "An example for the marked content",
5320
- format: TagFormat.bitmarkText,
5477
+ format: TagFormat.boolean,
5321
5478
  nullable: true
5322
5479
  }
5323
5480
  ]
@@ -8093,6 +8250,12 @@ var BITS = {
8093
8250
  description: "The progress of the chapter, used for tracking reading progress",
8094
8251
  format: TagFormat.boolean,
8095
8252
  defaultValue: "true"
8253
+ },
8254
+ {
8255
+ key: ConfigKey.property_isCollapsible,
8256
+ description: "If true, the chapter is collapsible",
8257
+ format: TagFormat.boolean,
8258
+ defaultValue: "false"
8096
8259
  }
8097
8260
  ]
8098
8261
  },
@@ -8867,7 +9030,12 @@ var BITS = {
8867
9030
  maxCount: Count.infinity
8868
9031
  },
8869
9032
  {
8870
- // Override inherited @example from group_standardTags: string root example.
9033
+ // Override inherited @example from group_standardTags: emits both
9034
+ // `isExample` (bool) AND `example` (paragraph array). Declared as
9035
+ // bitmarkText because BPG `JsonGenerator.toExample` renders the
9036
+ // root-example value through the bit's textFormat (not this tag's
9037
+ // declared format) — `TagFormat.plainText` here would be ignored
9038
+ // at runtime and is misleading.
8871
9039
  key: ConfigKey.property_example,
8872
9040
  exportJsonKey: [
8873
9041
  { "@keyonly": { isExample: true, example: "true" } },
@@ -8875,7 +9043,7 @@ var BITS = {
8875
9043
  { isExample: true, example: "$" }
8876
9044
  ],
8877
9045
  description: "The example text for the essay bit",
8878
- format: TagFormat.plainText,
9046
+ format: TagFormat.bitmarkText,
8879
9047
  nullable: true
8880
9048
  }
8881
9049
  ],
@@ -10237,11 +10405,36 @@ var BITS = {
10237
10405
  ],
10238
10406
  cardSet: CardSetConfigKey.definitionList
10239
10407
  },
10408
+ [BitType.definitionListCollapsible]: {
10409
+ since: "5.25.0",
10410
+ baseBitType: BitType.definitionList,
10411
+ description: "Collapsible definition list bit, used to create collapsible lists of definitions in articles or books"
10412
+ },
10413
+ [BitType.standardDefinitionListNormative]: {
10414
+ since: "5.24.0",
10415
+ baseBitType: BitType.definitionList,
10416
+ description: "Standard normative definition list bit, used to create normative standard definition lists in articles or books"
10417
+ },
10418
+ [BitType.standardDefinitionListNonNormative]: {
10419
+ since: "5.24.0",
10420
+ baseBitType: BitType.definitionList,
10421
+ description: "Standard non-normative definition list bit, used to create non-normative standard definition lists in articles or books"
10422
+ },
10423
+ [BitType.smartStandardDefinitionListNormative]: {
10424
+ since: "5.24.0",
10425
+ baseBitType: BitType.standardDefinitionListNormative,
10426
+ description: "Smart standard normative definition list bit, used to create normative smart standard definition lists in articles or books"
10427
+ },
10428
+ [BitType.smartStandardDefinitionListNonNormative]: {
10429
+ since: "5.24.0",
10430
+ baseBitType: BitType.standardDefinitionListNonNormative,
10431
+ description: "Smart standard non-normative definition list bit, used to create non-normative smart standard definition lists in articles or books"
10432
+ },
10240
10433
  [BitType.metaSearchDefaultTerms]: {
10241
10434
  since: "3.12.0",
10242
10435
  baseBitType: BitType._standard,
10243
10436
  description: "Meta search default terms bit, used to define default search terms in articles",
10244
- cardSet: CardSetConfigKey.definitionList
10437
+ cardSet: CardSetConfigKey.definitionListPlain
10245
10438
  },
10246
10439
  [BitType.metaSearchDefaultTopics]: {
10247
10440
  since: "3.12.0",
@@ -10285,7 +10478,11 @@ var BITS = {
10285
10478
  [BitType.qAndACard]: {
10286
10479
  since: "3.25.0",
10287
10480
  baseBitType: BitType.flashcard1,
10288
- description: "Q&A card bit, used to create question and answer cards in articles or books"
10481
+ description: "Q&A card bit, used to create question and answer cards in articles or books",
10482
+ // PLAN-085: q-and-a-card permits multiple cards (some fixtures carry
10483
+ // two). Override the inherited `flashcard1` (max=1) cardset back to
10484
+ // the unbounded `flashcard` cardset.
10485
+ cardSet: CardSetConfigKey.flashcard
10289
10486
  },
10290
10487
  [BitType.focusImage]: {
10291
10488
  since: "1.3.0",
@@ -13371,7 +13568,7 @@ var instance2 = new Config();
13371
13568
  // src/generated/package_info.ts
13372
13569
  var PACKAGE_INFO = {
13373
13570
  "name": "@gmb/bitmark-parser-generator",
13374
- "version": "5.24.0",
13571
+ "version": "5.25.0",
13375
13572
  "license": "ISC"};
13376
13573
  var Environment = {
13377
13574
  unknown: "",
@@ -13985,6 +14182,8 @@ var NodeType = {
13985
14182
  internalPrintPdf: "internalPrintPdf",
13986
14183
  internalPrintPdfValue: "internalPrintPdfValue",
13987
14184
  isCaseSensitive: "isCaseSensitive",
14185
+ isCollapsible: "isCollapsible",
14186
+ isCollapsibleValue: "isCollapsibleValue",
13988
14187
  isCommented: "isCommented",
13989
14188
  isCorrect: "isCorrect",
13990
14189
  isEditable: "isEditable",
@@ -28039,6 +28238,12 @@ var Builder = class extends BaseBuilder {
28039
28238
  level: instance6.asNumber(data.level),
28040
28239
  toc: this.toAstProperty(bitType, ConfigKey.property_toc, data.toc, options),
28041
28240
  progress: this.toAstProperty(bitType, ConfigKey.property_progress, data.progress, options),
28241
+ isCollapsible: this.toAstProperty(
28242
+ bitType,
28243
+ ConfigKey.property_isCollapsible,
28244
+ data.isCollapsible,
28245
+ options
28246
+ ),
28042
28247
  anchor: data.anchor,
28043
28248
  reference: data.reference,
28044
28249
  referenceEnd: data.referenceEnd,
@@ -33560,6 +33765,7 @@ var JsonGenerator = class extends AstWalkerGenerator {
33560
33765
  if (instance2.isOfBitType(bitType, BitType.chapter)) {
33561
33766
  if (bitJson.toc == null) bitJson.toc = true;
33562
33767
  if (bitJson.progress == null) bitJson.progress = true;
33768
+ if (bitJson.isCollapsible == null) bitJson.isCollapsible = false;
33563
33769
  if (bitJson.level == null) bitJson.level = 1;
33564
33770
  if (bitJson.body == null) bitJson.body = this.bodyDefault;
33565
33771
  }
@@ -35707,6 +35913,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
35707
35913
  case CardSetConfigKey.flashcard:
35708
35914
  case CardSetConfigKey.flashcard1:
35709
35915
  case CardSetConfigKey.definitionList:
35916
+ case CardSetConfigKey.definitionListPlain:
35710
35917
  result = parseFlashcardOrDefinitionList(context, bitType, processedCardSet);
35711
35918
  break;
35712
35919
  case CardSetConfigKey.elements:
@@ -41780,8 +41987,8 @@ var FileWriter = class extends StreamWriter {
41780
41987
  async open() {
41781
41988
  return new Promise((resolve, reject) => {
41782
41989
  const flags = this._append ? "a" : "w";
41783
- fs3.ensureDirSync(path3.dirname(this._path.toString()));
41784
- const stream = fs3.createWriteStream(this._path, {
41990
+ fs4.ensureDirSync(path3.dirname(this._path.toString()));
41991
+ const stream = fs4.createWriteStream(this._path, {
41785
41992
  flags,
41786
41993
  encoding: "utf8"
41787
41994
  });
@@ -41855,8 +42062,8 @@ var SyncFileWriter = class {
41855
42062
  }
41856
42063
  openSync() {
41857
42064
  const flag = this._append ? "a" : "w";
41858
- fs3.ensureDirSync(path3.dirname(this._path.toString()));
41859
- fs3.writeFileSync(this._path, "", {
42065
+ fs4.ensureDirSync(path3.dirname(this._path.toString()));
42066
+ fs4.writeFileSync(this._path, "", {
41860
42067
  flag,
41861
42068
  encoding: this._encoding
41862
42069
  });
@@ -41873,7 +42080,7 @@ var SyncFileWriter = class {
41873
42080
  } else {
41874
42081
  line = this.endOfLineString;
41875
42082
  }
41876
- fs3.appendFileSync(this._path, line, {
42083
+ fs4.appendFileSync(this._path, line, {
41877
42084
  encoding: this._encoding
41878
42085
  });
41879
42086
  this.lastWrite = line;
@@ -41891,7 +42098,7 @@ var SyncFileWriter = class {
41891
42098
  }
41892
42099
  str += this.endOfLineString;
41893
42100
  }
41894
- fs3.appendFileSync(this._path, str, {
42101
+ fs4.appendFileSync(this._path, str, {
41895
42102
  encoding: this._encoding
41896
42103
  });
41897
42104
  this.lastWrite = str;
@@ -41901,7 +42108,7 @@ var SyncFileWriter = class {
41901
42108
  if (!this._open) return this;
41902
42109
  if (value == null) return this;
41903
42110
  if (value) this.lastWrite = value;
41904
- fs3.appendFileSync(this._path, value, {
42111
+ fs4.appendFileSync(this._path, value, {
41905
42112
  encoding: this._encoding
41906
42113
  });
41907
42114
  return this;
@@ -41950,6 +42157,233 @@ var BitmarkFileGenerator = class {
41950
42157
  this.generator.generateSync(_ast);
41951
42158
  }
41952
42159
  };
42160
+
42161
+ // src/generator/html/HtmlTableGenerator.ts
42162
+ var MARK_TO_TAG = {
42163
+ bold: "b",
42164
+ italic: "i",
42165
+ superscript: "sup",
42166
+ subscript: "sub",
42167
+ strike: "s",
42168
+ del: "del",
42169
+ ins: "ins",
42170
+ underline: "u",
42171
+ highlight: "mark",
42172
+ code: "code"
42173
+ };
42174
+ var HtmlTableGenerator = class {
42175
+ indentUnit = " ";
42176
+ /**
42177
+ * Generate HTML table fragments from a string or JSON value.
42178
+ * @returns the concatenated `<table>` fragments, blank-line separated (empty string if none).
42179
+ */
42180
+ generate(input) {
42181
+ let data = input;
42182
+ if (typeof data === "string") {
42183
+ try {
42184
+ data = JSON.parse(data);
42185
+ } catch {
42186
+ return "";
42187
+ }
42188
+ }
42189
+ const bits = this.collectBits(data);
42190
+ const fragments = [];
42191
+ for (const bit of bits) {
42192
+ const html = this.renderTableBit(bit);
42193
+ if (html) fragments.push(html);
42194
+ }
42195
+ return fragments.join("\n\n");
42196
+ }
42197
+ // --- bit collection -------------------------------------------------------
42198
+ collectBits(data) {
42199
+ const out = [];
42200
+ const visit = (value) => {
42201
+ if (value == null || typeof value !== "object") return;
42202
+ if (Array.isArray(value)) {
42203
+ value.forEach(visit);
42204
+ return;
42205
+ }
42206
+ const obj = value;
42207
+ if ("bit" in obj && obj.bit && typeof obj.bit === "object") {
42208
+ out.push(obj.bit);
42209
+ } else if ("type" in obj) {
42210
+ out.push(obj);
42211
+ }
42212
+ };
42213
+ visit(data);
42214
+ return out;
42215
+ }
42216
+ // --- table rendering ------------------------------------------------------
42217
+ renderTableBit(bit) {
42218
+ const table = bit.table;
42219
+ if (!table) return "";
42220
+ if (bit.type !== "table" && bit.type !== "table-extended") return "";
42221
+ const extended = this.toExtended(table);
42222
+ const lines = [];
42223
+ lines.push("<table>");
42224
+ if (bit.caption != null) {
42225
+ const caption = this.cellContentToHtml(this.asTextAst(bit.caption));
42226
+ if (caption) lines.push(`${this.indentUnit}<caption>${caption}</caption>`);
42227
+ }
42228
+ this.renderSection(lines, "thead", extended.header, "header");
42229
+ this.renderSection(lines, "tbody", extended.body, "body");
42230
+ this.renderSection(lines, "tfoot", extended.footer, "footer");
42231
+ lines.push("</table>");
42232
+ return lines.join("\n");
42233
+ }
42234
+ toExtended(table) {
42235
+ const t = table;
42236
+ if (t.header || t.body || t.footer) return table;
42237
+ return convertBasicToExtendedTableFormat(table);
42238
+ }
42239
+ renderSection(lines, tag, section, key) {
42240
+ if (!section || !section.rows || section.rows.length === 0) return;
42241
+ lines.push(`${this.indentUnit}<${tag}>`);
42242
+ for (const row of section.rows) {
42243
+ lines.push(`${this.indentUnit.repeat(2)}<tr>`);
42244
+ for (const cell of row.cells ?? []) {
42245
+ lines.push(`${this.indentUnit.repeat(3)}${this.renderCell(cell, key)}`);
42246
+ }
42247
+ lines.push(`${this.indentUnit.repeat(2)}</tr>`);
42248
+ }
42249
+ lines.push(`${this.indentUnit}</${tag}>`);
42250
+ }
42251
+ renderCell(cell, _key) {
42252
+ const tag = cell.title === true ? "th" : "td";
42253
+ const attrs = [];
42254
+ if (cell.rowspan && cell.rowspan > 1) attrs.push(`rowspan="${cell.rowspan}"`);
42255
+ if (cell.colspan && cell.colspan > 1) attrs.push(`colspan="${cell.colspan}"`);
42256
+ if (cell.scope) attrs.push(`scope="${cell.scope}"`);
42257
+ if (cell.colwidth && cell.colwidth > 0) attrs.push(`width="${cell.colwidth}"`);
42258
+ const open = attrs.length > 0 ? `<${tag} ${attrs.join(" ")}>` : `<${tag}>`;
42259
+ return `${open}${this.cellContentToHtml(this.asTextAst(cell.content))}</${tag}>`;
42260
+ }
42261
+ // --- cell content (TextAst -> HTML) ---------------------------------------
42262
+ asTextAst(content) {
42263
+ if (content == null) return [];
42264
+ if (typeof content === "string") return [{ type: "text", text: content }];
42265
+ return content;
42266
+ }
42267
+ cellContentToHtml(blocks) {
42268
+ if (blocks.length === 0) return "";
42269
+ if (blocks.length === 1 && blocks[0].type === "paragraph") {
42270
+ return this.inlineToHtml(blocks[0].content ?? []);
42271
+ }
42272
+ return blocks.map((b) => this.blockToHtml(b)).join("");
42273
+ }
42274
+ blockToHtml(node) {
42275
+ switch (node.type) {
42276
+ case "paragraph":
42277
+ return `<p>${this.inlineToHtml(node.content ?? [])}</p>`;
42278
+ case "bulletList":
42279
+ case "noBulletList":
42280
+ case "orderedList":
42281
+ case "orderedListRoman":
42282
+ case "orderedListRomanLower":
42283
+ case "letteredList":
42284
+ case "letteredListLower":
42285
+ case "taskList":
42286
+ return this.listToHtml(node);
42287
+ case "image":
42288
+ case "imageInline":
42289
+ return this.imageToHtml(node);
42290
+ default:
42291
+ return this.inlineToHtml([node]);
42292
+ }
42293
+ }
42294
+ inlineToHtml(nodes) {
42295
+ return nodes.map((n) => this.inlineNodeToHtml(n)).join("");
42296
+ }
42297
+ inlineNodeToHtml(node) {
42298
+ if (node.type === "hardBreak") return "<br>";
42299
+ if (node.type === "image" || node.type === "imageInline") return this.imageToHtml(node);
42300
+ if (node.type === "text") {
42301
+ return this.wrapMarks(this.escapeText(node.text ?? ""), node.marks);
42302
+ }
42303
+ if (node.content) return this.inlineToHtml(node.content);
42304
+ return this.escapeText(node.text ?? "");
42305
+ }
42306
+ wrapMarks(text, marks) {
42307
+ if (!marks || marks.length === 0) return text;
42308
+ let open = "";
42309
+ let close = "";
42310
+ for (const mark of marks) {
42311
+ if (mark.type === "link") {
42312
+ const href = mark.attrs?.href ?? "";
42313
+ open += `<a href="${this.escapeAttr(href)}">`;
42314
+ close = `</a>${close}`;
42315
+ } else {
42316
+ const tag = MARK_TO_TAG[mark.type];
42317
+ if (tag) {
42318
+ open += `<${tag}>`;
42319
+ close = `</${tag}>${close}`;
42320
+ }
42321
+ }
42322
+ }
42323
+ return `${open}${text}${close}`;
42324
+ }
42325
+ // --- lists ----------------------------------------------------------------
42326
+ listToHtml(node) {
42327
+ const { tag, attrs } = this.listTag(node);
42328
+ const items = (node.content ?? []).map((item) => this.listItemToHtml(item, node.type === "taskList")).join("");
42329
+ return `<${tag}${attrs}>${items}</${tag}>`;
42330
+ }
42331
+ listTag(node) {
42332
+ const start = node.attrs?.start;
42333
+ const startAttr = typeof start === "number" && start !== 1 ? ` start="${start}"` : "";
42334
+ switch (node.type) {
42335
+ case "orderedList":
42336
+ return { tag: "ol", attrs: startAttr };
42337
+ case "orderedListRoman":
42338
+ return { tag: "ol", attrs: ` type="I"${startAttr}` };
42339
+ case "orderedListRomanLower":
42340
+ return { tag: "ol", attrs: ` type="i"${startAttr}` };
42341
+ case "letteredList":
42342
+ return { tag: "ol", attrs: ` type="A"${startAttr}` };
42343
+ case "letteredListLower":
42344
+ return { tag: "ol", attrs: ` type="a"${startAttr}` };
42345
+ case "noBulletList":
42346
+ return { tag: "ul", attrs: ' style="list-style:none"' };
42347
+ case "taskList":
42348
+ case "bulletList":
42349
+ default:
42350
+ return { tag: "ul", attrs: "" };
42351
+ }
42352
+ }
42353
+ listItemToHtml(item, task) {
42354
+ const inner = (item.content ?? []).map((b) => {
42355
+ if (b.type === "paragraph") return this.inlineToHtml(b.content ?? []);
42356
+ return this.blockToHtml(b);
42357
+ }).join("");
42358
+ if (task) {
42359
+ const checked = item.attrs?.checked ? " checked" : "";
42360
+ return `<li><input type="checkbox"${checked}>${inner}</li>`;
42361
+ }
42362
+ return `<li>${inner}</li>`;
42363
+ }
42364
+ // --- images ---------------------------------------------------------------
42365
+ imageToHtml(node) {
42366
+ const attrs = node.attrs ?? {};
42367
+ const parts = [];
42368
+ const src = attrs.src;
42369
+ parts.push(`src="${this.escapeAttr(typeof src === "string" ? src : "")}"`);
42370
+ if (typeof attrs.alt === "string") parts.push(`alt="${this.escapeAttr(attrs.alt)}"`);
42371
+ if (typeof attrs.title === "string") parts.push(`title="${this.escapeAttr(attrs.title)}"`);
42372
+ if (attrs.width != null) parts.push(`width="${attrs.width}"`);
42373
+ if (attrs.height != null) parts.push(`height="${attrs.height}"`);
42374
+ if (typeof attrs.class === "string" && attrs.class) {
42375
+ parts.push(`class="${this.escapeAttr(attrs.class)}"`);
42376
+ }
42377
+ return `<img ${parts.join(" ")}>`;
42378
+ }
42379
+ // --- escaping -------------------------------------------------------------
42380
+ escapeText(text) {
42381
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42382
+ }
42383
+ escapeAttr(text) {
42384
+ return this.escapeText(text).replace(/"/g, "&quot;");
42385
+ }
42386
+ };
41953
42387
  var JsonFileGenerator = class {
41954
42388
  generator;
41955
42389
  async;
@@ -42286,20 +42720,20 @@ var ConfigBuilder = class {
42286
42720
  const outputFolderBits = path3.join(outputFolder, "bits");
42287
42721
  const outputFolderGroups = path3.join(outputFolder, "partials");
42288
42722
  const outputFolderCards = path3.join(outputFolder, "cards");
42289
- fs3.ensureDirSync(outputFolderBits);
42290
- fs3.ensureDirSync(outputFolderGroups);
42291
- fs3.ensureDirSync(outputFolderCards);
42292
- const bitsFiles = fs3.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
42723
+ fs4.ensureDirSync(outputFolderBits);
42724
+ fs4.ensureDirSync(outputFolderGroups);
42725
+ fs4.ensureDirSync(outputFolderCards);
42726
+ const bitsFiles = fs4.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
42293
42727
  for (const file of bitsFiles) {
42294
- fs3.removeSync(path3.join(outputFolderBits, file));
42728
+ fs4.removeSync(path3.join(outputFolderBits, file));
42295
42729
  }
42296
- const partialsFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
42730
+ const partialsFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
42297
42731
  for (const file of partialsFiles) {
42298
- fs3.removeSync(path3.join(outputFolderGroups, file));
42732
+ fs4.removeSync(path3.join(outputFolderGroups, file));
42299
42733
  }
42300
- const cardsFiles = fs3.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc"));
42734
+ const cardsFiles = fs4.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc"));
42301
42735
  for (const file of cardsFiles) {
42302
- fs3.removeSync(path3.join(outputFolderCards, file));
42736
+ fs4.removeSync(path3.join(outputFolderCards, file));
42303
42737
  }
42304
42738
  const fileWrites = [];
42305
42739
  const tagEntriesTypeOrder = [
@@ -42644,7 +43078,7 @@ var ConfigBuilder = class {
42644
43078
  };
42645
43079
  const output = path3.join(outputFolderBits, `${b.bitType}.jsonc`);
42646
43080
  const str = JSON.stringify(bitJson, null, 2);
42647
- fileWrites.push(fs3.writeFile(output, str));
43081
+ fileWrites.push(fs4.writeFile(output, str));
42648
43082
  }
42649
43083
  const writeGroupConfigs = (groupConfigs2) => {
42650
43084
  for (const g of groupConfigs2) {
@@ -42703,7 +43137,7 @@ var ConfigBuilder = class {
42703
43137
  };
42704
43138
  const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
42705
43139
  const str = JSON.stringify(bitJson, null, 2);
42706
- fs3.writeFileSync(output, str);
43140
+ fs4.writeFileSync(output, str);
42707
43141
  }
42708
43142
  };
42709
43143
  writeGroupConfigs(groupConfigs);
@@ -42713,7 +43147,7 @@ var ConfigBuilder = class {
42713
43147
  if (!cardJson) continue;
42714
43148
  const output = path3.join(outputFolderCards, `${cardJson.name}.jsonc`);
42715
43149
  const str = JSON.stringify(cardJson, null, 2);
42716
- fileWrites.push(fs3.writeFile(output, str));
43150
+ fileWrites.push(fs4.writeFile(output, str));
42717
43151
  }
42718
43152
  };
42719
43153
  const writeBitsAsGroupConfigs = (bitsAsGroupConfigs) => {
@@ -42762,7 +43196,7 @@ var ConfigBuilder = class {
42762
43196
  };
42763
43197
  const output = path3.join(outputFolderGroups, `${groupKey}.jsonc`);
42764
43198
  const str = JSON.stringify(bitJson, null, 2);
42765
- fs3.writeFileSync(output, str);
43199
+ fs4.writeFileSync(output, str);
42766
43200
  }
42767
43201
  };
42768
43202
  writeBitsAsGroupConfigs(bitGroupConfigs);
@@ -42801,15 +43235,15 @@ var ConfigBuilder = class {
42801
43235
  const outputFolderGroups = path3.join(outputFolder, "partials");
42802
43236
  const outputFolderCards = path3.join(outputFolder, "cards");
42803
43237
  const availableGroups = /* @__PURE__ */ new Set();
42804
- if (fs3.existsSync(outputFolderGroups)) {
42805
- const groupFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
43238
+ if (fs4.existsSync(outputFolderGroups)) {
43239
+ const groupFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
42806
43240
  for (const file of groupFiles) {
42807
43241
  const groupName = file.replace(".jsonc", "");
42808
43242
  availableGroups.add(groupName);
42809
43243
  }
42810
43244
  }
42811
43245
  const availableCards = /* @__PURE__ */ new Set();
42812
- const cardFiles = fs3.existsSync(outputFolderCards) ? fs3.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc")) : [];
43246
+ const cardFiles = fs4.existsSync(outputFolderCards) ? fs4.readdirSync(outputFolderCards).filter((f) => f.endsWith(".jsonc")) : [];
42813
43247
  for (const file of cardFiles) {
42814
43248
  const cardName = file.replace(".jsonc", "");
42815
43249
  availableCards.add(cardName);
@@ -42828,12 +43262,12 @@ var ConfigBuilder = class {
42828
43262
  }
42829
43263
  }
42830
43264
  };
42831
- if (fs3.existsSync(outputFolderBits)) {
42832
- const bitFiles = fs3.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
43265
+ if (fs4.existsSync(outputFolderBits)) {
43266
+ const bitFiles = fs4.readdirSync(outputFolderBits).filter((f) => f.endsWith(".jsonc"));
42833
43267
  for (const file of bitFiles) {
42834
43268
  const filePath = path3.join(outputFolderBits, file);
42835
43269
  try {
42836
- const content = fs3.readFileSync(filePath, "utf-8");
43270
+ const content = fs4.readFileSync(filePath, "utf-8");
42837
43271
  const config = JSON.parse(content);
42838
43272
  if (config.tags && Array.isArray(config.tags)) {
42839
43273
  checkTags(config.tags, `bits/${file}`, 15);
@@ -42864,12 +43298,12 @@ var ConfigBuilder = class {
42864
43298
  }
42865
43299
  }
42866
43300
  }
42867
- if (fs3.existsSync(outputFolderGroups)) {
42868
- const groupFiles = fs3.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
43301
+ if (fs4.existsSync(outputFolderGroups)) {
43302
+ const groupFiles = fs4.readdirSync(outputFolderGroups).filter((f) => f.endsWith(".jsonc"));
42869
43303
  for (const file of groupFiles) {
42870
43304
  const filePath = path3.join(outputFolderGroups, file);
42871
43305
  try {
42872
- const content = fs3.readFileSync(filePath, "utf-8");
43306
+ const content = fs4.readFileSync(filePath, "utf-8");
42873
43307
  const config = JSON.parse(content);
42874
43308
  if (config.tags && Array.isArray(config.tags)) {
42875
43309
  checkTags(config.tags, `partials/${file}`, 15);
@@ -42883,7 +43317,7 @@ var ConfigBuilder = class {
42883
43317
  for (const file of cardFiles) {
42884
43318
  const filePath = path3.join(outputFolderCards, file);
42885
43319
  try {
42886
- const content = fs3.readFileSync(filePath, "utf-8");
43320
+ const content = fs4.readFileSync(filePath, "utf-8");
42887
43321
  const config = JSON.parse(content);
42888
43322
  const expectedKey = file.replace(".jsonc", "");
42889
43323
  if (config.key && config.key !== expectedKey) {
@@ -42919,6 +43353,497 @@ var ConfigBuilder = class {
42919
43353
  }
42920
43354
  };
42921
43355
 
43356
+ // src/parser/html/HtmlDom.ts
43357
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
43358
+ "area",
43359
+ "base",
43360
+ "br",
43361
+ "col",
43362
+ "embed",
43363
+ "hr",
43364
+ "img",
43365
+ "input",
43366
+ "link",
43367
+ "meta",
43368
+ "param",
43369
+ "source",
43370
+ "track",
43371
+ "wbr"
43372
+ ]);
43373
+ var IMPLIED_CLOSE = {
43374
+ li: /* @__PURE__ */ new Set(["li"]),
43375
+ td: /* @__PURE__ */ new Set(["td", "th"]),
43376
+ th: /* @__PURE__ */ new Set(["td", "th"]),
43377
+ tr: /* @__PURE__ */ new Set(["td", "th", "tr"]),
43378
+ thead: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43379
+ tbody: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43380
+ tfoot: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43381
+ p: /* @__PURE__ */ new Set(["p"]),
43382
+ option: /* @__PURE__ */ new Set(["option"])
43383
+ };
43384
+ var TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9-]*)((?:\s+(?:"[^"]*"|'[^']*'|[^<>"'])*)?)(\/)?>/g;
43385
+ var ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9:._-]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
43386
+ function parseAttrs(raw) {
43387
+ const attrs = {};
43388
+ if (!raw) return attrs;
43389
+ ATTR_RE.lastIndex = 0;
43390
+ let m;
43391
+ while ((m = ATTR_RE.exec(raw)) !== null) {
43392
+ const name = m[1].toLowerCase();
43393
+ let value = m[2] ?? "";
43394
+ if (value.length >= 2 && (value[0] === '"' || value[0] === "'")) {
43395
+ value = value.slice(1, -1);
43396
+ }
43397
+ attrs[name] = value;
43398
+ }
43399
+ return attrs;
43400
+ }
43401
+ function stripNonContent(html) {
43402
+ return html.replace(/<!--[\s\S]*?-->/g, "").replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, "").replace(/<!doctype[^>]*>/gi, "").replace(/<script\b[\s\S]*?<\/script>/gi, "").replace(/<style\b[\s\S]*?<\/style>/gi, "");
43403
+ }
43404
+ function tokenize(html) {
43405
+ const tokens = [];
43406
+ const clean = stripNonContent(html);
43407
+ TAG_RE.lastIndex = 0;
43408
+ let lastIndex = 0;
43409
+ let m;
43410
+ while ((m = TAG_RE.exec(clean)) !== null) {
43411
+ if (m.index > lastIndex) {
43412
+ tokens.push({ kind: "text", text: clean.slice(lastIndex, m.index) });
43413
+ }
43414
+ lastIndex = TAG_RE.lastIndex;
43415
+ const isClose = m[1] === "/";
43416
+ const name = m[2].toLowerCase();
43417
+ if (isClose) {
43418
+ tokens.push({ kind: "close", name });
43419
+ } else {
43420
+ tokens.push({
43421
+ kind: "open",
43422
+ name,
43423
+ attrs: parseAttrs(m[3]),
43424
+ selfClose: m[4] === "/"
43425
+ });
43426
+ }
43427
+ }
43428
+ if (lastIndex < clean.length) {
43429
+ tokens.push({ kind: "text", text: clean.slice(lastIndex) });
43430
+ }
43431
+ return tokens;
43432
+ }
43433
+ function parseHtml(html) {
43434
+ const root = { type: "element", name: "#root", attrs: {}, children: [] };
43435
+ const stack = [root];
43436
+ const top = () => stack[stack.length - 1];
43437
+ for (const token of tokenize(html)) {
43438
+ if (token.kind === "text") {
43439
+ if (token.text) top().children.push({ type: "text", value: token.text });
43440
+ continue;
43441
+ }
43442
+ if (token.kind === "open") {
43443
+ const name = token.name;
43444
+ const closes = IMPLIED_CLOSE[name];
43445
+ if (closes) {
43446
+ while (stack.length > 1 && closes.has(top().name)) stack.pop();
43447
+ }
43448
+ const el = {
43449
+ type: "element",
43450
+ name,
43451
+ attrs: token.attrs ?? {},
43452
+ children: []
43453
+ };
43454
+ top().children.push(el);
43455
+ if (!token.selfClose && !VOID_ELEMENTS.has(name)) {
43456
+ stack.push(el);
43457
+ }
43458
+ continue;
43459
+ }
43460
+ for (let i = stack.length - 1; i > 0; i--) {
43461
+ if (stack[i].name === token.name) {
43462
+ stack.length = i;
43463
+ break;
43464
+ }
43465
+ }
43466
+ }
43467
+ return root.children;
43468
+ }
43469
+ function childElements(el, names) {
43470
+ return el.children.filter((c) => c.type === "element" && names.has(c.name));
43471
+ }
43472
+
43473
+ // src/parser/html/HtmlTableParser.ts
43474
+ var MARK_TAGS = {
43475
+ b: "bold",
43476
+ strong: "bold",
43477
+ i: "italic",
43478
+ em: "italic",
43479
+ sup: "superscript",
43480
+ sub: "subscript",
43481
+ s: "strike",
43482
+ strike: "strike",
43483
+ del: "del",
43484
+ ins: "ins",
43485
+ u: "underline",
43486
+ mark: "highlight",
43487
+ code: "code"
43488
+ };
43489
+ var SECTION_TAGS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
43490
+ var ROW_TAGS = /* @__PURE__ */ new Set(["tr"]);
43491
+ var CELL_TAGS = /* @__PURE__ */ new Set(["td", "th"]);
43492
+ var LI_TAGS = /* @__PURE__ */ new Set(["li"]);
43493
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
43494
+ var NAMED_ENTITIES = {
43495
+ amp: "&",
43496
+ lt: "<",
43497
+ gt: ">",
43498
+ quot: '"',
43499
+ apos: "'",
43500
+ nbsp: "\xA0"
43501
+ };
43502
+ function decodeEntities(text) {
43503
+ return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body) => {
43504
+ if (body[0] === "#") {
43505
+ const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
43506
+ if (Number.isFinite(code) && code >= 0 && code <= 1114111) {
43507
+ try {
43508
+ return String.fromCodePoint(code);
43509
+ } catch {
43510
+ return whole;
43511
+ }
43512
+ }
43513
+ return whole;
43514
+ }
43515
+ return NAMED_ENTITIES[body] ?? whole;
43516
+ });
43517
+ }
43518
+ function intAttr(value) {
43519
+ if (value == null) return void 0;
43520
+ const n = Number.parseInt(value, 10);
43521
+ return Number.isInteger(n) ? n : void 0;
43522
+ }
43523
+ function isElement(node) {
43524
+ return node.type === "element";
43525
+ }
43526
+ var HtmlTableParser = class {
43527
+ keepUnknownTags = false;
43528
+ /**
43529
+ * Parse an HTML string and return one table bit per top-level `<table>`.
43530
+ */
43531
+ parse(html, options) {
43532
+ this.keepUnknownTags = options?.keepUnknownTags ?? false;
43533
+ const tableFormat = options?.tableFormat ?? "table-extended";
43534
+ const onWarning = options?.onWarning;
43535
+ const tables = this.collectTopLevelTables(parseHtml(html));
43536
+ const bits = [];
43537
+ for (const table of tables) {
43538
+ const bit = this.tableToBit(table, tableFormat, onWarning);
43539
+ if (bit) bits.push(bit);
43540
+ }
43541
+ return bits;
43542
+ }
43543
+ // --- table extraction -----------------------------------------------------
43544
+ /** Collect every `<table>` element, NOT descending into nested tables. */
43545
+ collectTopLevelTables(nodes) {
43546
+ const tables = [];
43547
+ const visit = (node) => {
43548
+ if (!isElement(node)) return;
43549
+ if (node.name === "table") {
43550
+ tables.push(node);
43551
+ return;
43552
+ }
43553
+ node.children.forEach(visit);
43554
+ };
43555
+ nodes.forEach(visit);
43556
+ return tables;
43557
+ }
43558
+ tableToBit(table, tableFormat, onWarning) {
43559
+ const header = [];
43560
+ const body = [];
43561
+ const footer = [];
43562
+ let caption;
43563
+ for (const child of table.children) {
43564
+ if (!isElement(child)) continue;
43565
+ if (child.name === "caption") {
43566
+ const capContent = this.inlineNodes(child.children, []);
43567
+ const trimmed = this.trimInline(capContent);
43568
+ if (trimmed.length > 0) caption = trimmed;
43569
+ } else if (SECTION_TAGS.has(child.name)) {
43570
+ const target = child.name === "thead" ? header : child.name === "tfoot" ? footer : body;
43571
+ for (const tr of childElements(child, ROW_TAGS)) {
43572
+ target.push(this.rowToCells(tr));
43573
+ }
43574
+ } else if (ROW_TAGS.has(child.name)) {
43575
+ body.push(this.rowToCells(child));
43576
+ }
43577
+ }
43578
+ if (header.length === 0 && body.length === 0 && footer.length === 0) {
43579
+ return void 0;
43580
+ }
43581
+ const tableExtended = {};
43582
+ if (header.length > 0) tableExtended.header = { rows: header };
43583
+ if (body.length > 0) tableExtended.body = { rows: body };
43584
+ if (footer.length > 0) tableExtended.footer = { rows: footer };
43585
+ let tableData = tableExtended;
43586
+ if (tableFormat === "table") {
43587
+ if (this.isLossyAsBasic(tableExtended)) {
43588
+ onWarning?.(
43589
+ "Converting an HTML table to [.table] loses rowspan/colspan/scope and multi-section structure; use --tableFormat table-extended to preserve them."
43590
+ );
43591
+ }
43592
+ tableData = convertExtendedToBasicTableFormat(tableExtended);
43593
+ }
43594
+ const bit = {
43595
+ type: tableFormat,
43596
+ format: "bitmark++",
43597
+ table: tableData
43598
+ };
43599
+ if (caption) bit.caption = caption;
43600
+ return bit;
43601
+ }
43602
+ rowToCells(tr) {
43603
+ const cells = [];
43604
+ for (const cell of childElements(tr, CELL_TAGS)) {
43605
+ cells.push(this.cellToJson(cell));
43606
+ }
43607
+ return { cells };
43608
+ }
43609
+ cellToJson(cell) {
43610
+ const json = {
43611
+ content: this.blockNodes(cell.children)
43612
+ };
43613
+ if (cell.name === "th") json.title = true;
43614
+ const rowspan = intAttr(cell.attrs.rowspan);
43615
+ if (rowspan != null && rowspan >= 2) json.rowspan = rowspan;
43616
+ const colspan = intAttr(cell.attrs.colspan);
43617
+ if (colspan != null && colspan >= 2) json.colspan = colspan;
43618
+ const scope = cell.attrs.scope?.toLowerCase();
43619
+ if (scope && VALID_SCOPES.has(scope)) {
43620
+ json.scope = scope;
43621
+ }
43622
+ const width = this.widthFromCell(cell);
43623
+ if (width != null) json.colwidth = width;
43624
+ return json;
43625
+ }
43626
+ widthFromCell(cell) {
43627
+ const attrWidth = intAttr(cell.attrs.width);
43628
+ if (attrWidth != null && attrWidth > 0) return attrWidth;
43629
+ const style = cell.attrs.style;
43630
+ if (style) {
43631
+ const m = style.match(/width\s*:\s*(\d+)\s*px/i);
43632
+ if (m) {
43633
+ const n = Number.parseInt(m[1], 10);
43634
+ if (Number.isInteger(n) && n > 0) return n;
43635
+ }
43636
+ }
43637
+ return void 0;
43638
+ }
43639
+ isLossyAsBasic(table) {
43640
+ if (table.footer) return true;
43641
+ if (table.header && table.header.rows.length > 1) return true;
43642
+ const sections = [table.header, table.body, table.footer];
43643
+ for (const section of sections) {
43644
+ if (!section) continue;
43645
+ for (const row of section.rows) {
43646
+ for (const cell of row.cells) {
43647
+ if (cell.rowspan || cell.colspan || cell.scope) return true;
43648
+ }
43649
+ }
43650
+ }
43651
+ return false;
43652
+ }
43653
+ // --- content conversion (HTML -> TextAst) ---------------------------------
43654
+ /**
43655
+ * Convert a cell/li's child nodes into block-level TextAst nodes (paragraphs, lists, images).
43656
+ */
43657
+ blockNodes(nodes) {
43658
+ const blocks = [];
43659
+ let inline = [];
43660
+ const flush = () => {
43661
+ const trimmed = this.trimInline(inline);
43662
+ inline = [];
43663
+ if (trimmed.length === 0) return;
43664
+ if (trimmed.length === 1 && trimmed[0].type === "imageInline") {
43665
+ blocks.push({ ...trimmed[0], type: "image" });
43666
+ } else {
43667
+ blocks.push({ type: "paragraph", content: trimmed, attrs: {} });
43668
+ }
43669
+ };
43670
+ for (const node of nodes) {
43671
+ if (!isElement(node)) {
43672
+ inline.push(...this.inlineText(node.value, []));
43673
+ continue;
43674
+ }
43675
+ const name = node.name;
43676
+ if (name === "p") {
43677
+ flush();
43678
+ const content = this.trimInline(this.inlineNodes(node.children, []));
43679
+ if (content.length > 0) {
43680
+ blocks.push({ type: "paragraph", content, attrs: {} });
43681
+ }
43682
+ } else if (this.isListTag(name)) {
43683
+ flush();
43684
+ const list = this.listNode(node);
43685
+ if (list) blocks.push(list);
43686
+ } else {
43687
+ inline.push(...this.inlineFromElement(node, []));
43688
+ }
43689
+ }
43690
+ flush();
43691
+ return blocks;
43692
+ }
43693
+ /** Convert child nodes to a flat list of inline TextNodes, applying the given marks. */
43694
+ inlineNodes(nodes, marks) {
43695
+ const out = [];
43696
+ for (const node of nodes) {
43697
+ if (!isElement(node)) {
43698
+ out.push(...this.inlineText(node.value, marks));
43699
+ } else {
43700
+ out.push(...this.inlineFromElement(node, marks));
43701
+ }
43702
+ }
43703
+ return out;
43704
+ }
43705
+ inlineFromElement(el, marks) {
43706
+ const name = el.name;
43707
+ if (name === "br") {
43708
+ return [{ type: "hardBreak" }];
43709
+ }
43710
+ if (name === "img") {
43711
+ return [this.imageNode(el, true)];
43712
+ }
43713
+ const markType = MARK_TAGS[name];
43714
+ if (markType) {
43715
+ return this.inlineNodes(el.children, [...marks, { type: markType }]);
43716
+ }
43717
+ if (name === "a") {
43718
+ const linkMark = {
43719
+ type: "link",
43720
+ attrs: {
43721
+ href: el.attrs.href ?? "",
43722
+ ...el.attrs.target ? { target: el.attrs.target } : {}
43723
+ }
43724
+ };
43725
+ return this.inlineNodes(el.children, [...marks, linkMark]);
43726
+ }
43727
+ if (this.keepUnknownTags) {
43728
+ const open = this.serializeOpenTag(el);
43729
+ const close = `</${name}>`;
43730
+ return [
43731
+ ...this.inlineText(open, marks),
43732
+ ...this.inlineNodes(el.children, marks),
43733
+ ...this.inlineText(close, marks)
43734
+ ];
43735
+ }
43736
+ return this.inlineNodes(el.children, marks);
43737
+ }
43738
+ inlineText(raw, marks) {
43739
+ const text = decodeEntities(raw).replace(/\s+/g, " ");
43740
+ if (text === "") return [];
43741
+ const node = { text, type: "text" };
43742
+ if (marks.length > 0) node.marks = marks.map((m) => ({ ...m }));
43743
+ return [node];
43744
+ }
43745
+ serializeOpenTag(el) {
43746
+ const attrs = Object.entries(el.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
43747
+ return `<${el.name}${attrs}>`;
43748
+ }
43749
+ // --- lists ----------------------------------------------------------------
43750
+ isListTag(name) {
43751
+ return name === "ul" || name === "ol";
43752
+ }
43753
+ listNode(el) {
43754
+ const items = childElements(el, LI_TAGS);
43755
+ if (items.length === 0) return void 0;
43756
+ const isTask = el.name === "ul" && items.some((li) => {
43757
+ const firstEl = li.children.find(isElement);
43758
+ return firstEl?.name === "input" && firstEl.attrs.type?.toLowerCase() === "checkbox";
43759
+ });
43760
+ if (isTask) {
43761
+ return {
43762
+ type: "taskList",
43763
+ content: items.map((li) => this.taskItemNode(li))
43764
+ };
43765
+ }
43766
+ const type = this.listType(el);
43767
+ const node = {
43768
+ type,
43769
+ content: items.map((li) => this.listItemNode(li))
43770
+ };
43771
+ if (el.name === "ol") {
43772
+ const start = intAttr(el.attrs.start);
43773
+ if (start != null) {
43774
+ node.attrs = { start };
43775
+ }
43776
+ }
43777
+ return node;
43778
+ }
43779
+ listType(el) {
43780
+ if (el.name === "ol") {
43781
+ switch (el.attrs.type) {
43782
+ case "i":
43783
+ return "orderedListRomanLower";
43784
+ case "I":
43785
+ return "orderedListRoman";
43786
+ case "a":
43787
+ return "letteredListLower";
43788
+ case "A":
43789
+ return "letteredList";
43790
+ default:
43791
+ return "orderedList";
43792
+ }
43793
+ }
43794
+ const style = el.attrs.style ?? "";
43795
+ if (/list-style(-type)?\s*:\s*none/i.test(style)) return "noBulletList";
43796
+ return "bulletList";
43797
+ }
43798
+ listItemNode(li) {
43799
+ let content = this.blockNodes(li.children);
43800
+ if (content.length === 0) {
43801
+ content = [{ type: "paragraph", content: [], attrs: {} }];
43802
+ }
43803
+ return { type: "listItem", content };
43804
+ }
43805
+ taskItemNode(li) {
43806
+ const checkbox = li.children.find(
43807
+ (c) => isElement(c) && c.name === "input" && c.attrs.type?.toLowerCase() === "checkbox"
43808
+ );
43809
+ const checked = checkbox ? "checked" in checkbox.attrs : false;
43810
+ const rest = li.children.filter((c) => c !== checkbox);
43811
+ let content = this.blockNodes(rest);
43812
+ if (content.length === 0) {
43813
+ content = [{ type: "paragraph", content: [], attrs: {} }];
43814
+ }
43815
+ return { type: "taskItem", attrs: { checked }, content };
43816
+ }
43817
+ // --- images ---------------------------------------------------------------
43818
+ imageNode(el, inline) {
43819
+ const attrs = { src: el.attrs.src ?? "" };
43820
+ attrs.alt = el.attrs.alt ?? null;
43821
+ attrs.title = el.attrs.title ?? null;
43822
+ const width = intAttr(el.attrs.width);
43823
+ if (width != null) attrs.width = width;
43824
+ const height = intAttr(el.attrs.height);
43825
+ if (height != null) attrs.height = height;
43826
+ if (el.attrs.class) attrs.class = el.attrs.class;
43827
+ return { type: inline ? "imageInline" : "image", attrs };
43828
+ }
43829
+ // --- whitespace -----------------------------------------------------------
43830
+ /** Trim leading/trailing whitespace from inline content (edges only). */
43831
+ trimInline(nodes) {
43832
+ const out = nodes.slice();
43833
+ const isWs = (n) => n.type === "text" && /^\s*$/.test(n.text ?? "");
43834
+ while (out.length > 0 && isWs(out[0])) out.shift();
43835
+ if (out.length > 0 && out[0].type === "text") {
43836
+ out[0] = { ...out[0], text: (out[0].text ?? "").replace(/^\s+/, "") };
43837
+ }
43838
+ while (out.length > 0 && isWs(out[out.length - 1])) out.pop();
43839
+ if (out.length > 0 && out[out.length - 1].type === "text") {
43840
+ const last = out.length - 1;
43841
+ out[last] = { ...out[last], text: (out[last].text ?? "").replace(/\s+$/, "") };
43842
+ }
43843
+ return out.filter((n) => !(n.type === "text" && n.text === ""));
43844
+ }
43845
+ };
43846
+
42922
43847
  // src/BitmarkParserGenerator.ts
42923
43848
  var Input = {
42924
43849
  /**
@@ -42956,7 +43881,11 @@ var InputFormat = {
42956
43881
  /**
42957
43882
  * Input is plain text
42958
43883
  */
42959
- plainText: "plainText"
43884
+ plainText: "plainText",
43885
+ /**
43886
+ * Input is HTML (used by convertHtmlTable)
43887
+ */
43888
+ html: "html"
42960
43889
  };
42961
43890
  var BitmarkParserGenerator = class {
42962
43891
  ast = new Ast();
@@ -43077,8 +44006,8 @@ var BitmarkParserGenerator = class {
43077
44006
  }
43078
44007
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43079
44008
  if (env.isNode) {
43080
- if (inputIsString && fs3.existsSync(inStr)) {
43081
- inStr = fs3.readFileSync(inStr, {
44009
+ if (inputIsString && fs4.existsSync(inStr)) {
44010
+ inStr = fs4.readFileSync(inStr, {
43082
44011
  encoding: "utf8"
43083
44012
  });
43084
44013
  }
@@ -43124,7 +44053,7 @@ var BitmarkParserGenerator = class {
43124
44053
  const textGenerator = new PlainTextGenerator();
43125
44054
  const str = textGenerator.generate(json);
43126
44055
  if (opts.outputFile) {
43127
- fs3.writeFileSync(opts.outputFile, str, {
44056
+ fs4.writeFileSync(opts.outputFile, str, {
43128
44057
  encoding: "utf8"
43129
44058
  });
43130
44059
  } else {
@@ -43159,7 +44088,7 @@ var BitmarkParserGenerator = class {
43159
44088
  const textGenerator = new PlainTextGenerator();
43160
44089
  const str = textGenerator.generate(json);
43161
44090
  if (opts.outputFile) {
43162
- fs3.writeFileSync(opts.outputFile, str, {
44091
+ fs4.writeFileSync(opts.outputFile, str, {
43163
44092
  encoding: "utf8"
43164
44093
  });
43165
44094
  } else {
@@ -43184,7 +44113,7 @@ var BitmarkParserGenerator = class {
43184
44113
  const textGenerator = new PlainTextGenerator();
43185
44114
  const str = textGenerator.generate(json);
43186
44115
  if (opts.outputFile) {
43187
- fs3.writeFileSync(opts.outputFile, str, {
44116
+ fs4.writeFileSync(opts.outputFile, str, {
43188
44117
  encoding: "utf8"
43189
44118
  });
43190
44119
  } else {
@@ -43268,8 +44197,8 @@ var BitmarkParserGenerator = class {
43268
44197
  }
43269
44198
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43270
44199
  if (env.isNode) {
43271
- if (inputIsString && fs3.existsSync(inStr)) {
43272
- inStr = fs3.readFileSync(inStr, {
44200
+ if (inputIsString && fs4.existsSync(inStr)) {
44201
+ inStr = fs4.readFileSync(inStr, {
43273
44202
  encoding: "utf8"
43274
44203
  });
43275
44204
  }
@@ -43327,8 +44256,8 @@ var BitmarkParserGenerator = class {
43327
44256
  const opts = Object.assign({}, options);
43328
44257
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43329
44258
  if (env.isNode) {
43330
- if (inputIsString && fs3.existsSync(inStr)) {
43331
- inStr = fs3.readFileSync(inStr, {
44259
+ if (inputIsString && fs4.existsSync(inStr)) {
44260
+ inStr = fs4.readFileSync(inStr, {
43332
44261
  encoding: "utf8"
43333
44262
  });
43334
44263
  }
@@ -43388,8 +44317,8 @@ var BitmarkParserGenerator = class {
43388
44317
  }
43389
44318
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43390
44319
  if (env.isNode) {
43391
- if (inputIsString && fs3.existsSync(inStr)) {
43392
- inStr = fs3.readFileSync(inStr, {
44320
+ if (inputIsString && fs4.existsSync(inStr)) {
44321
+ inStr = fs4.readFileSync(inStr, {
43393
44322
  encoding: "utf8"
43394
44323
  });
43395
44324
  }
@@ -43412,8 +44341,8 @@ var BitmarkParserGenerator = class {
43412
44341
  strRes = this.jsonStringifyPrettify(preRes, jsonOptions, true);
43413
44342
  }
43414
44343
  const flag = fileOptions.append ? "a" : "w";
43415
- fs3.ensureDirSync(path3.dirname(output));
43416
- fs3.writeFileSync(output, strRes, {
44344
+ fs4.ensureDirSync(path3.dirname(output));
44345
+ fs4.writeFileSync(output, strRes, {
43417
44346
  flag
43418
44347
  });
43419
44348
  } else {
@@ -43473,6 +44402,87 @@ var BitmarkParserGenerator = class {
43473
44402
  const res = generator.generate(data);
43474
44403
  return res;
43475
44404
  }
44405
+ /**
44406
+ * Convert between HTML tables and bitmark tables.
44407
+ *
44408
+ * Direction is auto-detected (override with `options.inputFormat`):
44409
+ * - HTML input: every `<table>` is extracted and emitted as a `[.table-extended]` (or `[.table]`)
44410
+ * bit; output as bitmark (default), JSON, AST, or HTML (re-emit).
44411
+ * - bitmark / JSON / AST input: each `table` / `table-extended` bit is rendered as an HTML
44412
+ * `<table>` fragment.
44413
+ *
44414
+ * Cell content is reproduced verbatim except inline conversion to bitmark marks (bold, italic,
44415
+ * superscript, etc.), with lists and images mapped to their bitmark equivalents.
44416
+ *
44417
+ * @param input - HTML, bitmark, JSON or AST string; or a parsed JSON/AST value; or a file path.
44418
+ * @param options - the conversion options
44419
+ * @returns the converted output as a string (or object for JSON/AST), or void when writing a file
44420
+ * @throws Error if any error occurs
44421
+ */
44422
+ convertHtmlTable(input, options) {
44423
+ const opts = Object.assign({}, options);
44424
+ if (env.isBrowser && opts.outputFile) {
44425
+ throw new Error("Cannot write to file in browser environment");
44426
+ }
44427
+ let inStr = input;
44428
+ const inputIsString = typeof input === "string";
44429
+ if (env.isNode && inputIsString && fs4.existsSync(inStr)) {
44430
+ inStr = fs4.readFileSync(inStr, { encoding: "utf8" });
44431
+ }
44432
+ const isHtmlString = (s) => {
44433
+ const t = s.replace(/^\s+/, "");
44434
+ return t.startsWith("<") && /<table/i.test(t);
44435
+ };
44436
+ let htmlInput;
44437
+ if (opts.inputFormat === InputFormat.html) {
44438
+ htmlInput = true;
44439
+ } else if (opts.inputFormat != null) {
44440
+ htmlInput = false;
44441
+ } else {
44442
+ htmlInput = inputIsString && isHtmlString(inStr);
44443
+ }
44444
+ if (htmlInput) {
44445
+ const parser2 = new HtmlTableParser();
44446
+ const bits = parser2.parse(inStr, {
44447
+ tableFormat: opts.tableFormat ?? "table-extended",
44448
+ keepUnknownTags: opts.keepUnknownTags,
44449
+ onWarning: (m) => console.warn(m)
44450
+ });
44451
+ const outputFormat2 = opts.outputFormat ?? Output.bitmark;
44452
+ if (outputFormat2 === "html") {
44453
+ const json2 = this.convert(bits, { outputFormat: Output.json });
44454
+ return this.outputHtml(new HtmlTableGenerator().generate(json2), opts);
44455
+ }
44456
+ return this.convert(bits, {
44457
+ outputFormat: Enum(Output).fromValue(outputFormat2),
44458
+ outputFile: opts.outputFile,
44459
+ fileOptions: opts.fileOptions,
44460
+ jsonOptions: opts.jsonOptions,
44461
+ bitmarkOptions: opts.bitmarkOptions
44462
+ });
44463
+ }
44464
+ const outputFormat = opts.outputFormat ?? "html";
44465
+ if (outputFormat !== "html") {
44466
+ throw new Error(
44467
+ "convertHtmlTable: non-HTML input can only be converted to HTML. Use convert() for bitmark/JSON/AST conversions."
44468
+ );
44469
+ }
44470
+ const json = this.convert(inputIsString ? inStr : input, { outputFormat: Output.json });
44471
+ return this.outputHtml(new HtmlTableGenerator().generate(json), opts);
44472
+ }
44473
+ /**
44474
+ * Write an HTML string to the configured output file, or return it.
44475
+ */
44476
+ outputHtml(html, opts) {
44477
+ if (opts.outputFile) {
44478
+ const output = opts.outputFile.toString();
44479
+ const flag = opts.fileOptions?.append ? "a" : "w";
44480
+ fs4.ensureDirSync(path3.dirname(output));
44481
+ fs4.writeFileSync(output, html, { flag });
44482
+ return;
44483
+ }
44484
+ return html;
44485
+ }
43476
44486
  /**
43477
44487
  * Breakscape bitmark text.
43478
44488
  *
@@ -43504,8 +44514,8 @@ var BitmarkParserGenerator = class {
43504
44514
  }
43505
44515
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43506
44516
  if (env.isNode) {
43507
- if (inputIsString && fs3.existsSync(inStr)) {
43508
- inStr = fs3.readFileSync(inStr, {
44517
+ if (inputIsString && fs4.existsSync(inStr)) {
44518
+ inStr = fs4.readFileSync(inStr, {
43509
44519
  encoding: "utf8"
43510
44520
  });
43511
44521
  }
@@ -43518,8 +44528,8 @@ var BitmarkParserGenerator = class {
43518
44528
  if (opts.outputFile) {
43519
44529
  const output = opts.outputFile.toString();
43520
44530
  const flag = fileOptions.append ? "a" : "w";
43521
- fs3.ensureDirSync(path3.dirname(output));
43522
- fs3.writeFileSync(output, res, {
44531
+ fs4.ensureDirSync(path3.dirname(output));
44532
+ fs4.writeFileSync(output, res, {
43523
44533
  flag
43524
44534
  });
43525
44535
  } else {
@@ -43557,8 +44567,8 @@ var BitmarkParserGenerator = class {
43557
44567
  }
43558
44568
  if (!opts.inputFormat || opts.inputFormat === Input.file) {
43559
44569
  if (env.isNode) {
43560
- if (inputIsString && fs3.existsSync(inStr)) {
43561
- inStr = fs3.readFileSync(inStr, {
44570
+ if (inputIsString && fs4.existsSync(inStr)) {
44571
+ inStr = fs4.readFileSync(inStr, {
43562
44572
  encoding: "utf8"
43563
44573
  });
43564
44574
  }
@@ -43571,8 +44581,8 @@ var BitmarkParserGenerator = class {
43571
44581
  if (opts.outputFile) {
43572
44582
  const output = opts.outputFile.toString();
43573
44583
  const flag = fileOptions.append ? "a" : "w";
43574
- fs3.ensureDirSync(path3.dirname(output));
43575
- fs3.writeFileSync(output, res, {
44584
+ fs4.ensureDirSync(path3.dirname(output));
44585
+ fs4.writeFileSync(output, res, {
43576
44586
  flag
43577
44587
  });
43578
44588
  } else {
@@ -43627,8 +44637,8 @@ var BitmarkParserGenerator = class {
43627
44637
  init();
43628
44638
  async function readInput(input) {
43629
44639
  if (input !== void 0) {
43630
- if (await fs3.pathExists(input)) {
43631
- return fs3.readFile(input, "utf8");
44640
+ if (await fs4.pathExists(input)) {
44641
+ return fs4.readFile(input, "utf8");
43632
44642
  }
43633
44643
  return input;
43634
44644
  }
@@ -43641,8 +44651,8 @@ async function readInput(input) {
43641
44651
  async function writeOutput(content, outputFile, append = false) {
43642
44652
  const text = typeof content === "string" ? content : JSON.stringify(content);
43643
44653
  if (outputFile) {
43644
- await fs3.ensureDir(path3.dirname(outputFile));
43645
- await fs3.writeFile(outputFile, text, { flag: append ? "a" : "w" });
44654
+ await fs4.ensureDir(path3.dirname(outputFile));
44655
+ await fs4.writeFile(outputFile, text, { flag: append ? "a" : "w" });
43646
44656
  } else {
43647
44657
  console.log(text);
43648
44658
  }
@@ -43791,6 +44801,70 @@ Examples:
43791
44801
  );
43792
44802
  return cmd;
43793
44803
  }
44804
+ var OUTPUT_FORMAT_CHOICES2 = ["html", "bitmark", "json", "ast"];
44805
+ var INPUT_FORMAT_CHOICES = ["html", "bitmark", "json"];
44806
+ var TABLE_FORMAT_CHOICES = ["table", "table-extended"];
44807
+ function createConvertHtmlTableCommand() {
44808
+ const bpg = new BitmarkParserGenerator();
44809
+ const cmd = new Command("convertHtmlTable").description("Convert between HTML tables and bitmark tables").argument(
44810
+ "[input]",
44811
+ "file to read, or HTML / bitmark / json string. If not specified, input will be from <stdin>"
44812
+ ).addOption(
44813
+ new Option(
44814
+ "-f, --format <format>",
44815
+ "output format (default: bitmark for HTML input, html for bitmark/json input)"
44816
+ ).choices(OUTPUT_FORMAT_CHOICES2)
44817
+ ).addOption(
44818
+ new Option("--inputFormat <format>", "force input format (auto-detected by default)").choices(
44819
+ INPUT_FORMAT_CHOICES
44820
+ )
44821
+ ).addOption(
44822
+ new Option("--tableFormat <format>", "bit type to emit for HTML input").choices(TABLE_FORMAT_CHOICES).default("table-extended")
44823
+ ).option("--keepUnknownTags", "keep unmappable HTML tags as literal text (HTML input only)").option("-p, --pretty", "prettify the JSON output with indent").option(
44824
+ "--indent <indent>",
44825
+ "prettify indent (default: 2 when --pretty is set)",
44826
+ (v) => Number.parseInt(v, 10)
44827
+ ).option("-a, --append", "append to the output file (default is to overwrite)").option("-o, --output <file>", "output file. If not specified, output will be to <stdout>").action(async (input, options) => {
44828
+ try {
44829
+ const dataIn = await readInput(input);
44830
+ const prettify = options.pretty ? options.indent ?? 2 : void 0;
44831
+ const inputFormat = options.inputFormat === "html" ? InputFormat.html : options.inputFormat != null ? InputFormat.bitmark : void 0;
44832
+ const result = bpg.convertHtmlTable(dataIn, {
44833
+ inputFormat,
44834
+ outputFormat: options.format,
44835
+ tableFormat: Enum({ table: "table", "table-extended": "table-extended" }).fromValue(
44836
+ options.tableFormat
44837
+ ),
44838
+ keepUnknownTags: options.keepUnknownTags,
44839
+ jsonOptions: {
44840
+ prettify
44841
+ }
44842
+ });
44843
+ let outputValue = result ?? "";
44844
+ if (typeof outputValue !== "string") {
44845
+ outputValue = formatJson(outputValue, options.pretty, options.indent);
44846
+ }
44847
+ await writeOutput(outputValue, options.output, options.append);
44848
+ } catch (error) {
44849
+ console.error("Error:", error instanceof Error ? error.message : String(error));
44850
+ process.exit(1);
44851
+ }
44852
+ }).addHelpText(
44853
+ "after",
44854
+ `
44855
+ Examples:
44856
+ $ bitmark-parser convertHtmlTable '<table><tr><th>Name</th></tr><tr><td>John</td></tr></table>'
44857
+
44858
+ $ bitmark-parser convertHtmlTable input.html -o output.bitmark
44859
+
44860
+ $ bitmark-parser convertHtmlTable input.html -f json -p -o output.json
44861
+
44862
+ $ bitmark-parser convertHtmlTable input.bitmark -o output.html
44863
+
44864
+ $ bitmark-parser convertHtmlTable --tableFormat table input.html`
44865
+ );
44866
+ return cmd;
44867
+ }
43794
44868
  var TEXT_FORMAT_CHOICES = (() => {
43795
44869
  const choices = enumChoices(BodyTextFormat);
43796
44870
  const preferred = BodyTextFormat.bitmarkPlusPlus;
@@ -43847,7 +44921,7 @@ Examples:
43847
44921
  );
43848
44922
  return cmd;
43849
44923
  }
43850
- var INPUT_FORMAT_CHOICES = enumChoices(InputFormat);
44924
+ var INPUT_FORMAT_CHOICES2 = enumChoices(InputFormat);
43851
44925
  function createExtractPlainTextCommand() {
43852
44926
  const bpg = new BitmarkParserGenerator();
43853
44927
  const cmd = new Command("extractPlainText").description("Extract plain text from bitmark, bitmark text, or JSON").argument(
@@ -43857,7 +44931,7 @@ function createExtractPlainTextCommand() {
43857
44931
  new Option(
43858
44932
  "-f, --inputFormat <format>",
43859
44933
  "force input format (auto-detected by default)"
43860
- ).choices([...INPUT_FORMAT_CHOICES])
44934
+ ).choices([...INPUT_FORMAT_CHOICES2])
43861
44935
  ).option("-a, --append", "append to the output file (default is to overwrite)").option("-o, --output <file>", "output file. If not specified, output will be to <stdout>").action(async (input, options) => {
43862
44936
  try {
43863
44937
  const dataIn = await readInput(input);
@@ -44009,6 +45083,7 @@ async function asyncInit() {
44009
45083
  program.outputHelp();
44010
45084
  });
44011
45085
  program.addCommand(createConvertCommand());
45086
+ program.addCommand(createConvertHtmlTableCommand());
44012
45087
  program.addCommand(createConvertTextCommand());
44013
45088
  program.addCommand(createExtractPlainTextCommand());
44014
45089
  program.addCommand(createBreakscapeCommand());