@gmb/bitmark-parser-generator 5.26.0 → 5.28.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.cjs CHANGED
@@ -610,6 +610,14 @@ var BitType = {
610
610
  smartStandardRemarkNonNormativeCollapsible: "smart-standard-remark-non-normative-collapsible",
611
611
  smartStandardRemarkNormative: "smart-standard-remark-normative",
612
612
  smartStandardRemarkNormativeCollapsible: "smart-standard-remark-normative-collapsible",
613
+ smartStandardRemarkTableExtendedImageNonNormative: "smart-standard-remark-table-extended-image-non-normative",
614
+ smartStandardRemarkTableExtendedImageNonNormativeCollapsible: "smart-standard-remark-table-extended-image-non-normative-collapsible",
615
+ smartStandardRemarkTableExtendedImageNormative: "smart-standard-remark-table-extended-image-normative",
616
+ smartStandardRemarkTableExtendedImageNormativeCollapsible: "smart-standard-remark-table-extended-image-normative-collapsible",
617
+ smartStandardRemarkTableExtendedNonNormative: "smart-standard-remark-table-extended-non-normative",
618
+ smartStandardRemarkTableExtendedNonNormativeCollapsible: "smart-standard-remark-table-extended-non-normative-collapsible",
619
+ smartStandardRemarkTableExtendedNormative: "smart-standard-remark-table-extended-normative",
620
+ smartStandardRemarkTableExtendedNormativeCollapsible: "smart-standard-remark-table-extended-normative-collapsible",
613
621
  smartStandardRemarkTableImageNonNormative: "smart-standard-remark-table-image-non-normative",
614
622
  smartStandardRemarkTableImageNonNormativeCollapsible: "smart-standard-remark-table-image-non-normative-collapsible",
615
623
  smartStandardRemarkTableImageNormative: "smart-standard-remark-table-image-normative",
@@ -618,6 +626,14 @@ var BitType = {
618
626
  smartStandardRemarkTableNonNormativeCollapsible: "smart-standard-remark-table-non-normative-collapsible",
619
627
  smartStandardRemarkTableNormative: "smart-standard-remark-table-normative",
620
628
  smartStandardRemarkTableNormativeCollapsible: "smart-standard-remark-table-normative-collapsible",
629
+ smartStandardTableExtendedImageNonNormative: "smart-standard-table-extended-image-non-normative",
630
+ smartStandardTableExtendedImageNonNormativeCollapsible: "smart-standard-table-extended-image-non-normative-collapsible",
631
+ smartStandardTableExtendedImageNormative: "smart-standard-table-extended-image-normative",
632
+ smartStandardTableExtendedImageNormativeCollapsible: "smart-standard-table-extended-image-normative-collapsible",
633
+ smartStandardTableExtendedNonNormative: "smart-standard-table-extended-non-normative",
634
+ smartStandardTableExtendedNonNormativeCollapsible: "smart-standard-table-extended-non-normative-collapsible",
635
+ smartStandardTableExtendedNormative: "smart-standard-table-extended-normative",
636
+ smartStandardTableExtendedNormativeCollapsible: "smart-standard-table-extended-normative-collapsible",
621
637
  smartStandardTableImageNonNormative: "smart-standard-table-image-non-normative",
622
638
  smartStandardTableImageNonNormativeCollapsible: "smart-standard-table-image-non-normative-collapsible",
623
639
  smartStandardTableImageNormative: "smart-standard-table-image-normative",
@@ -640,10 +656,18 @@ var BitType = {
640
656
  standardNoteNormative: "standard-note-normative",
641
657
  standardRemarkNonNormative: "standard-remark-non-normative",
642
658
  standardRemarkNormative: "standard-remark-normative",
659
+ standardRemarkTableExtendedImageNonNormative: "standard-remark-table-extended-image-non-normative",
660
+ standardRemarkTableExtendedImageNormative: "standard-remark-table-extended-image-normative",
661
+ standardRemarkTableExtendedNonNormative: "standard-remark-table-extended-non-normative",
662
+ standardRemarkTableExtendedNormative: "standard-remark-table-extended-normative",
643
663
  standardRemarkTableImageNonNormative: "standard-remark-table-image-non-normative",
644
664
  standardRemarkTableImageNormative: "standard-remark-table-image-normative",
645
665
  standardRemarkTableNonNormative: "standard-remark-table-non-normative",
646
666
  standardRemarkTableNormative: "standard-remark-table-normative",
667
+ standardTableExtendedImageNonNormative: "standard-table-extended-image-non-normative",
668
+ standardTableExtendedImageNormative: "standard-table-extended-image-normative",
669
+ standardTableExtendedNonNormative: "standard-table-extended-non-normative",
670
+ standardTableExtendedNormative: "standard-table-extended-normative",
647
671
  standardTableImageNonNormative: "standard-table-image-non-normative",
648
672
  standardTableImageNormative: "standard-table-image-normative",
649
673
  standardTableNonNormative: "standard-table-non-normative",
@@ -870,6 +894,10 @@ var AbstractTagConfig = class {
870
894
  // New JSON-pattern jsonKey (export-only)
871
895
  __publicField(this, "hasExportJsonKey");
872
896
  // True iff exportJsonKey was explicitly set
897
+ __publicField(this, "htmlKey");
898
+ // HTML key-pattern (export-only)
899
+ __publicField(this, "hasHtmlKey");
900
+ // True iff htmlKey was explicitly set
873
901
  __publicField(this, "deprecated");
874
902
  this.type = params.type;
875
903
  this.configKey = params.configKey;
@@ -880,6 +908,8 @@ var AbstractTagConfig = class {
880
908
  this.jsonKey = params.jsonKey;
881
909
  this.exportJsonKey = params.exportJsonKey;
882
910
  this.hasExportJsonKey = params.hasExportJsonKey;
911
+ this.htmlKey = params.htmlKey;
912
+ this.hasHtmlKey = params.hasHtmlKey;
883
913
  this.deprecated = params.deprecated;
884
914
  }
885
915
  };
@@ -1001,7 +1031,7 @@ var ResourceTagConfig = class extends AbstractTagConfig {
1001
1031
  // src/model/config/CardVariantConfig.ts
1002
1032
  var CardVariantConfig = class {
1003
1033
  // Body text format. Default: bitmark++
1004
- constructor(tags2, bodyAllowed, bodyRequired, repeatCount, jsonKey, exportJsonKey, hasExportJsonKey = false, format) {
1034
+ constructor(tags2, bodyAllowed, bodyRequired, repeatCount, jsonKey, exportJsonKey, hasExportJsonKey = false, format, htmlKey, hasHtmlKey = false) {
1005
1035
  __publicField(this, "tags");
1006
1036
  __publicField(this, "bodyAllowed");
1007
1037
  // Default: true
@@ -1015,6 +1045,9 @@ var CardVariantConfig = class {
1015
1045
  __publicField(this, "exportJsonKey");
1016
1046
  // New JSON-pattern jsonKey
1017
1047
  __publicField(this, "hasExportJsonKey");
1048
+ __publicField(this, "htmlKey");
1049
+ // HTML key-pattern
1050
+ __publicField(this, "hasHtmlKey");
1018
1051
  __publicField(this, "format");
1019
1052
  this.tags = tags2;
1020
1053
  this.bodyAllowed = bodyAllowed == null ? true : bodyAllowed;
@@ -1023,6 +1056,8 @@ var CardVariantConfig = class {
1023
1056
  this.jsonKey = jsonKey;
1024
1057
  this.exportJsonKey = exportJsonKey;
1025
1058
  this.hasExportJsonKey = hasExportJsonKey;
1059
+ this.htmlKey = htmlKey;
1060
+ this.hasHtmlKey = hasHtmlKey;
1026
1061
  this.format = format;
1027
1062
  }
1028
1063
  toString(options) {
@@ -1047,18 +1082,22 @@ ${tag.toString(opts)}`;
1047
1082
 
1048
1083
  // src/model/config/CardSideConfig.ts
1049
1084
  var CardSideConfig = class {
1050
- constructor(name, repeat, variants, jsonKey, exportJsonKey, hasExportJsonKey = false) {
1085
+ constructor(name, repeat, variants, jsonKey, exportJsonKey, hasExportJsonKey = false, htmlKey, hasHtmlKey = false) {
1051
1086
  __publicField(this, "name");
1052
1087
  __publicField(this, "repeat");
1053
1088
  __publicField(this, "jsonKey");
1054
1089
  __publicField(this, "exportJsonKey");
1055
1090
  __publicField(this, "hasExportJsonKey");
1091
+ __publicField(this, "htmlKey");
1092
+ __publicField(this, "hasHtmlKey");
1056
1093
  __publicField(this, "variants");
1057
1094
  this.name = name;
1058
1095
  this.repeat = repeat;
1059
1096
  this.jsonKey = jsonKey;
1060
1097
  this.exportJsonKey = exportJsonKey;
1061
1098
  this.hasExportJsonKey = hasExportJsonKey;
1099
+ this.htmlKey = htmlKey;
1100
+ this.hasHtmlKey = hasHtmlKey;
1062
1101
  this.variants = variants;
1063
1102
  }
1064
1103
  toString(options) {
@@ -1080,17 +1119,21 @@ var CardSideConfig = class {
1080
1119
 
1081
1120
  // src/model/config/CardSetConfig.ts
1082
1121
  var CardSetConfig = class {
1083
- constructor(configKey, jsonKey, exportJsonKey, hasExportJsonKey, sections, sides) {
1122
+ constructor(configKey, jsonKey, exportJsonKey, hasExportJsonKey, sections, sides, htmlKey = void 0, hasHtmlKey = false) {
1084
1123
  __publicField(this, "configKey");
1085
1124
  __publicField(this, "jsonKey");
1086
1125
  __publicField(this, "exportJsonKey");
1087
1126
  __publicField(this, "hasExportJsonKey");
1127
+ __publicField(this, "htmlKey");
1128
+ __publicField(this, "hasHtmlKey");
1088
1129
  __publicField(this, "sections");
1089
1130
  __publicField(this, "sides");
1090
1131
  this.configKey = configKey;
1091
1132
  this.jsonKey = jsonKey;
1092
1133
  this.exportJsonKey = exportJsonKey;
1093
1134
  this.hasExportJsonKey = hasExportJsonKey;
1135
+ this.htmlKey = htmlKey;
1136
+ this.hasHtmlKey = hasHtmlKey;
1094
1137
  this.sections = sections;
1095
1138
  this.sides = sides;
1096
1139
  }
@@ -1387,6 +1430,8 @@ var propertyKeys = {
1387
1430
  property_externalLinkText: "@externalLinkText",
1388
1431
  property_feedbackEngine: "@feedbackEngine",
1389
1432
  property_feedbackType: "@feedbackType",
1433
+ property_userFeedbackTranslationUrl: "@userFeedbackTranslationUrl",
1434
+ property_userFeedbackContentUrl: "@userFeedbackContentUrl",
1390
1435
  property_flashcardSet: "@flashcardSet",
1391
1436
  property_focusX: "@focusX",
1392
1437
  property_focusY: "@focusY",
@@ -3770,6 +3815,16 @@ var CARDSETS = {
3770
3815
  [CardSetConfigKey.table]: {
3771
3816
  jsonKey: null,
3772
3817
  exportJsonKey: null,
3818
+ // PLAN-109 / HTML.md §8 — idiomatic HTML mapping. The classic (non-extended)
3819
+ // table produces the same `<table>`/`<thead>`/`<tbody>`/`<tfoot>` structure as
3820
+ // table-extended, so the structural htmlKeys mirror that cardSet. NOTE: the
3821
+ // JSON side routes the FIRST header card → `columns` (header) and SUBSEQUENT
3822
+ // header cards → `data` (body); HTML uses a uniform `<thead>` for the whole
3823
+ // `table-header` section, so a multi-card header (e.g. a second header card
3824
+ // that JSON sends to `data`) renders as additional `<thead>` rows rather than
3825
+ // moving to `<tbody>`. Per-card header/body splitting needs engine-level
3826
+ // routing not yet expressible in the pattern vocabulary — see PLAN-109.
3827
+ htmlKey: { "@el": "table", "@children": "$" },
3773
3828
  sections: {
3774
3829
  "table-header": {
3775
3830
  // First header card's row of cells → `columns`. Subsequent header
@@ -3788,25 +3843,36 @@ var CARDSETS = {
3788
3843
  },
3789
3844
  { "@cardIndex=0": { "@bit": { table: { columns: "$" } } } },
3790
3845
  { "@bit": { table: { data: ["$"] } } }
3791
- ]
3846
+ ],
3847
+ // Header section → `<thead>` rows of `<th>`; `@tableColWidth` bubbles a
3848
+ // `width` attr onto the enclosing `<th>` (HTML-C2).
3849
+ htmlKey: { "@el": "thead", "@children": { "@el": "tr", "@children": "$" } },
3850
+ sideHtmlKey: { "@el": "th", "@children": "$" }
3792
3851
  },
3793
3852
  "table-body": {
3794
3853
  // Batched emission: `$` is the entire 2D array of body card rows.
3795
3854
  // This is the default section (no qualifier → cards land here).
3796
3855
  jsonKey: "table.data",
3797
3856
  exportJsonKey: { "@bit": { table: { data: "$" } } },
3857
+ htmlKey: { "@el": "tbody", "@children": { "@el": "tr", "@children": "$" } },
3798
3858
  isDefault: true
3799
3859
  },
3800
3860
  "table-footer": {
3801
3861
  // Batched emission: `$` is the entire 2D array of footer rows.
3802
3862
  jsonKey: "table.data",
3803
- exportJsonKey: { "@bit": { table: { data: "$" } } }
3863
+ exportJsonKey: { "@bit": { table: { data: "$" } } },
3864
+ // Footer cells route to `data` (not `columns`), so they are `<td>` (no
3865
+ // sideHtmlKey override — the cell side default applies).
3866
+ htmlKey: { "@el": "tfoot", "@children": { "@el": "tr", "@children": "$" } }
3804
3867
  }
3805
3868
  },
3806
3869
  sides: [
3807
3870
  {
3808
3871
  name: "cell",
3809
3872
  repeat: true,
3873
+ // Default (body/footer) cell → `<td>`; the header section overrides to
3874
+ // `<th>` via its sideHtmlKey (HTML.md §8).
3875
+ htmlKey: { "@el": "td", "@children": "$" },
3810
3876
  variants: [
3811
3877
  {
3812
3878
  jsonKey: null,
@@ -3823,21 +3889,32 @@ var CARDSETS = {
3823
3889
  },
3824
3890
  {
3825
3891
  key: ConfigKey.property_tableCellType,
3892
+ // HTML.md §8: first-match predicate selecting `<th>` vs `<td>`.
3893
+ htmlKey: [{ "@value=th": { "@el": "th" } }, { "@absent": { "@el": "td" } }],
3826
3894
  description: "Table cell type (th/td).",
3827
3895
  format: TagFormat.plainText
3828
3896
  },
3829
3897
  {
3830
3898
  key: ConfigKey.property_tableRowSpan,
3899
+ // HTML-C2: contribute a `rowspan` attribute onto the enclosing
3900
+ // cell element (`<th>`/`<td>`).
3901
+ htmlKey: { "@el": { "@attr": { rowspan: "$" } } },
3831
3902
  description: "Number of rows the cell spans.",
3832
3903
  format: TagFormat.number
3833
3904
  },
3834
3905
  {
3835
3906
  key: ConfigKey.property_tableColSpan,
3907
+ // HTML-C2: contribute a `colspan` attribute onto the enclosing
3908
+ // cell element (`<th>`/`<td>`).
3909
+ htmlKey: { "@el": { "@attr": { colspan: "$" } } },
3836
3910
  description: "Number of columns the cell spans.",
3837
3911
  format: TagFormat.number
3838
3912
  },
3839
3913
  {
3840
3914
  key: ConfigKey.property_tableScope,
3915
+ // HTML-C2: contribute a `scope` attribute onto the enclosing
3916
+ // cell element (`<th>`/`<td>`).
3917
+ htmlKey: { "@el": { "@attr": { scope: "$" } } },
3841
3918
  description: "Scope attribute for header cells.",
3842
3919
  format: TagFormat.plainText
3843
3920
  },
@@ -3845,6 +3922,9 @@ var CARDSETS = {
3845
3922
  key: ConfigKey.property_tableColWidth,
3846
3923
  jsonKey: "^table.columnWidths[{s}]",
3847
3924
  exportJsonKey: { "@bit": { table: { columnWidths: { $s: "$" } } } },
3925
+ // HTML.md §8 + HTML-C2: contribute a `width` attribute onto the
3926
+ // enclosing cell element (`<th>`/`<td>`).
3927
+ htmlKey: { "@el": { "@attr": { width: "$" } } },
3848
3928
  description: "Width for the column.",
3849
3929
  format: TagFormat.number
3850
3930
  }
@@ -3860,6 +3940,12 @@ var CARDSETS = {
3860
3940
  //
3861
3941
  [CardSetConfigKey.tableExtended]: {
3862
3942
  jsonKey: null,
3943
+ // PLAN-109 / HTML.md §8 — idiomatic HTML mapping for the extended table.
3944
+ // The card-set container is the `<table>` element; each section is a
3945
+ // table section element (`<thead>`/`<tbody>`/`<tfoot>`) whose children are
3946
+ // `<tr>` rows, and each cell side is a `<td>` (body) or `<th>` (header /
3947
+ // footer, via the section sideHtmlKey override).
3948
+ htmlKey: { "@el": "table", "@children": "$" },
3863
3949
  sections: {
3864
3950
  "table-header": {
3865
3951
  jsonKey: "table.header.rows",
@@ -3879,19 +3965,29 @@ var CARDSETS = {
3879
3965
  },
3880
3966
  { table: { header: { rows: ["$"] } } }
3881
3967
  ],
3968
+ htmlKey: { "@el": "thead", "@children": { "@el": "tr", "@children": "$" } },
3882
3969
  sideJsonKey: "cells[{s}]|set(title=true)",
3883
- sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } }
3970
+ sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } },
3971
+ // Header cells are `<th>`. The `width` attribute is contributed by the
3972
+ // `@tableColWidth` tag, which bubbles it onto the enclosing `<th>`
3973
+ // (HTML-C2) — so it is not repeated here.
3974
+ sideHtmlKey: { "@el": "th", "@children": "$" }
3884
3975
  },
3885
3976
  "table-body": {
3886
3977
  jsonKey: "table.body.rows",
3887
3978
  exportJsonKey: { table: { body: { rows: "$" } } },
3979
+ htmlKey: { "@el": "tbody", "@children": { "@el": "tr", "@children": "$" } },
3888
3980
  isDefault: true
3889
3981
  },
3890
3982
  "table-footer": {
3891
3983
  jsonKey: "table.footer.rows",
3892
3984
  exportJsonKey: { table: { footer: { rows: "$" } } },
3985
+ htmlKey: { "@el": "tfoot", "@children": { "@el": "tr", "@children": "$" } },
3893
3986
  sideJsonKey: "cells[{s}]|set(title=true)",
3894
- sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } }
3987
+ sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } },
3988
+ // Footer cells render as `<th>` (mirrors the header `title: true`
3989
+ // mapping); §8 lists only the header side, so this parallels it.
3990
+ sideHtmlKey: { "@el": "th", "@children": "$" }
3895
3991
  }
3896
3992
  },
3897
3993
  sides: [
@@ -3900,6 +3996,9 @@ var CARDSETS = {
3900
3996
  repeat: true,
3901
3997
  jsonKey: "cells[{s}]",
3902
3998
  exportJsonKey: { cells: { $s: "$" } },
3999
+ // Default (body) cell → `<td>`; the header/footer sections override the
4000
+ // side to `<th>` via their sideHtmlKey (HTML.md §8).
4001
+ htmlKey: { "@el": "td", "@children": "$" },
3903
4002
  variants: [
3904
4003
  {
3905
4004
  jsonKey: "content",
@@ -3923,6 +4022,9 @@ var CARDSETS = {
3923
4022
  key: ConfigKey.property_tableCellType,
3924
4023
  jsonKey: "title|bool(th)",
3925
4024
  exportJsonKey: [{ "@value=th": { title: true } }],
4025
+ // HTML.md §8: same first-match predicate machinery selecting
4026
+ // `<th>` vs `<td>` instead of `title:true` vs nothing.
4027
+ htmlKey: [{ "@value=th": { "@el": "th" } }, { "@absent": { "@el": "td" } }],
3926
4028
  description: "Table cell type (th/td).",
3927
4029
  format: TagFormat.plainText
3928
4030
  },
@@ -3930,6 +4032,9 @@ var CARDSETS = {
3930
4032
  key: ConfigKey.property_tableRowSpan,
3931
4033
  jsonKey: "rowspan",
3932
4034
  exportJsonKey: { rowspan: "$" },
4035
+ // HTML-C2: contribute a `rowspan` attribute onto the enclosing
4036
+ // cell element (`<th>`/`<td>`).
4037
+ htmlKey: { "@el": { "@attr": { rowspan: "$" } } },
3933
4038
  description: "Number of rows the cell spans.",
3934
4039
  format: TagFormat.number
3935
4040
  },
@@ -3937,6 +4042,9 @@ var CARDSETS = {
3937
4042
  key: ConfigKey.property_tableColSpan,
3938
4043
  jsonKey: "colspan",
3939
4044
  exportJsonKey: { colspan: "$" },
4045
+ // HTML-C2: contribute a `colspan` attribute onto the enclosing
4046
+ // cell element (`<th>`/`<td>`).
4047
+ htmlKey: { "@el": { "@attr": { colspan: "$" } } },
3940
4048
  description: "Number of columns the cell spans.",
3941
4049
  format: TagFormat.number
3942
4050
  },
@@ -3944,6 +4052,9 @@ var CARDSETS = {
3944
4052
  key: ConfigKey.property_tableScope,
3945
4053
  jsonKey: "scope",
3946
4054
  exportJsonKey: { scope: "$" },
4055
+ // HTML-C2: contribute a `scope` attribute onto the enclosing
4056
+ // cell element (`<th>`/`<td>`).
4057
+ htmlKey: { "@el": { "@attr": { scope: "$" } } },
3947
4058
  description: "Scope attribute for header cells.",
3948
4059
  format: TagFormat.plainText
3949
4060
  },
@@ -3952,6 +4063,9 @@ var CARDSETS = {
3952
4063
  // PLAN-080: write to the cell's `colwidth` field.
3953
4064
  jsonKey: "colwidth",
3954
4065
  exportJsonKey: { colwidth: "$" },
4066
+ // HTML.md §8 + HTML-C2: contribute a `width` attribute onto the
4067
+ // enclosing cell element (`<th>`/`<td>`).
4068
+ htmlKey: { "@el": { "@attr": { width: "$" } } },
3955
4069
  description: "Width for the column.",
3956
4070
  format: TagFormat.number
3957
4071
  }
@@ -5751,6 +5865,16 @@ var GROUPS = {
5751
5865
  description: "External Id of the translated book",
5752
5866
  format: TagFormat.plainText
5753
5867
  },
5868
+ {
5869
+ key: ConfigKey.property_userFeedbackTranslationUrl,
5870
+ description: "URL to an external feedback form for book translation feedback",
5871
+ format: TagFormat.plainText
5872
+ },
5873
+ {
5874
+ key: ConfigKey.property_userFeedbackContentUrl,
5875
+ description: "URL to an external feedback form for book content feedback",
5876
+ format: TagFormat.plainText
5877
+ },
5754
5878
  {
5755
5879
  key: ConfigKey.property_duration,
5756
5880
  description: "The duration of the book",
@@ -6880,7 +7004,9 @@ var ConfigHydrator = class {
6880
7004
  variantsOfSide,
6881
7005
  _side.jsonKey,
6882
7006
  _side.exportJsonKey,
6883
- Object.prototype.hasOwnProperty.call(_side, "exportJsonKey")
7007
+ Object.prototype.hasOwnProperty.call(_side, "exportJsonKey"),
7008
+ _side.htmlKey,
7009
+ Object.prototype.hasOwnProperty.call(_side, "htmlKey")
6884
7010
  );
6885
7011
  sides.push(sideConfig);
6886
7012
  }
@@ -6890,7 +7016,9 @@ var ConfigHydrator = class {
6890
7016
  _cardSetConfig.exportJsonKey,
6891
7017
  Object.prototype.hasOwnProperty.call(_cardSetConfig, "exportJsonKey"),
6892
7018
  this.hydrateCardSections(_cardSetConfig.sections),
6893
- sides
7019
+ sides,
7020
+ _cardSetConfig.htmlKey,
7021
+ Object.prototype.hasOwnProperty.call(_cardSetConfig, "htmlKey")
6894
7022
  );
6895
7023
  return cardSetConfig;
6896
7024
  }
@@ -6902,10 +7030,14 @@ var ConfigHydrator = class {
6902
7030
  jsonKey: v.jsonKey,
6903
7031
  exportJsonKey: v.exportJsonKey,
6904
7032
  hasExportJsonKey: Object.prototype.hasOwnProperty.call(v, "exportJsonKey"),
7033
+ htmlKey: v.htmlKey,
7034
+ hasHtmlKey: Object.prototype.hasOwnProperty.call(v, "htmlKey"),
6905
7035
  isDefault: v.isDefault,
6906
7036
  sideJsonKey: v.sideJsonKey,
6907
7037
  sideExportJsonKey: v.sideExportJsonKey,
6908
7038
  hasSideExportJsonKey: Object.prototype.hasOwnProperty.call(v, "sideExportJsonKey"),
7039
+ sideHtmlKey: v.sideHtmlKey,
7040
+ hasSideHtmlKey: Object.prototype.hasOwnProperty.call(v, "sideHtmlKey"),
6909
7041
  // PLAN-085: cardinality fields pass through; defaults applied
6910
7042
  // downstream by the config consumer (Rust treats `0` / undefined
6911
7043
  // as unbounded).
@@ -6923,7 +7055,8 @@ var ConfigHydrator = class {
6923
7055
  chain: _chain,
6924
7056
  deprecated,
6925
7057
  jsonKey,
6926
- exportJsonKey
7058
+ exportJsonKey,
7059
+ htmlKey
6927
7060
  } = _tag;
6928
7061
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
6929
7062
  if (!configKey) throw new Error(`No tag key found for config key '${configKey}'`);
@@ -6942,6 +7075,8 @@ var ConfigHydrator = class {
6942
7075
  jsonKey,
6943
7076
  exportJsonKey,
6944
7077
  hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
7078
+ htmlKey,
7079
+ hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
6945
7080
  deprecated
6946
7081
  });
6947
7082
  return {
@@ -6961,7 +7096,8 @@ var ConfigHydrator = class {
6961
7096
  values,
6962
7097
  defaultValue,
6963
7098
  jsonKey,
6964
- exportJsonKey
7099
+ exportJsonKey,
7100
+ htmlKey
6965
7101
  } = _tag;
6966
7102
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
6967
7103
  if (!configKey) throw new Error(`No property key found for config key '${configKey}'`);
@@ -6979,6 +7115,8 @@ var ConfigHydrator = class {
6979
7115
  jsonKey,
6980
7116
  exportJsonKey,
6981
7117
  hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
7118
+ htmlKey,
7119
+ hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
6982
7120
  format,
6983
7121
  values,
6984
7122
  defaultValue,
@@ -6998,7 +7136,8 @@ var ConfigHydrator = class {
6998
7136
  chain: _chain,
6999
7137
  deprecated,
7000
7138
  jsonKey,
7001
- exportJsonKey
7139
+ exportJsonKey,
7140
+ htmlKey
7002
7141
  } = _tag;
7003
7142
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
7004
7143
  if (!configKey) throw new Error(`No resource key found for config key '${configKey}'`);
@@ -7016,6 +7155,8 @@ var ConfigHydrator = class {
7016
7155
  jsonKey,
7017
7156
  exportJsonKey,
7018
7157
  hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
7158
+ htmlKey,
7159
+ hasHtmlKey: Object.prototype.hasOwnProperty.call(_tag, "htmlKey"),
7019
7160
  deprecated
7020
7161
  });
7021
7162
  return {
@@ -7054,6 +7195,7 @@ var ConfigHydrator = class {
7054
7195
  repeatCount,
7055
7196
  jsonKey,
7056
7197
  exportJsonKey,
7198
+ htmlKey,
7057
7199
  format
7058
7200
  } = _variant;
7059
7201
  const tags2 = this.hydrateTagsConfig(_tags);
@@ -7065,7 +7207,9 @@ var ConfigHydrator = class {
7065
7207
  jsonKey,
7066
7208
  exportJsonKey,
7067
7209
  Object.prototype.hasOwnProperty.call(_variant, "exportJsonKey"),
7068
- format
7210
+ format,
7211
+ htmlKey,
7212
+ Object.prototype.hasOwnProperty.call(_variant, "htmlKey")
7069
7213
  );
7070
7214
  return cardSetConfig;
7071
7215
  }
@@ -10198,11 +10342,6 @@ var BITS = {
10198
10342
  baseBitType: BitType._standard,
10199
10343
  description: "Extractor rule bit, used to define extraction rules with reference images and instructions",
10200
10344
  tags: [
10201
- {
10202
- exportJsonKey: { title: "$" },
10203
- key: ConfigKey.tag_title,
10204
- description: "The name of the extraction rule"
10205
- },
10206
10345
  {
10207
10346
  key: ConfigKey.group_resourceBitTags,
10208
10347
  description: "Resource bit tags for extraction rule images"
@@ -10838,7 +10977,9 @@ var BITS = {
10838
10977
  {
10839
10978
  key: ConfigKey.property_caption,
10840
10979
  description: "Caption for the table image, used to provide a description for the image",
10841
- format: TagFormat.bitmarkText
10980
+ format: TagFormat.bitmarkText,
10981
+ // HTML-10 / HTML-C2: render as the table's `<caption>` (see table bit).
10982
+ htmlKey: { "@el": "caption", "@children": "$" }
10842
10983
  },
10843
10984
  {
10844
10985
  key: ConfigKey.group_backgroundWallpaper,
@@ -10881,6 +11022,26 @@ var BITS = {
10881
11022
  baseBitType: BitType.tableImage,
10882
11023
  description: "Standard non-normative remark table image bit, used to create non-normative remark images in tables in articles or books"
10883
11024
  },
11025
+ [BitType.standardTableExtendedImageNormative]: {
11026
+ since: "1.16.0",
11027
+ baseBitType: BitType.tableExtendedImage,
11028
+ description: "Standard normative extended table image bit, used to create normative extended images in tables in articles or books"
11029
+ },
11030
+ [BitType.standardTableExtendedImageNonNormative]: {
11031
+ since: "1.16.0",
11032
+ baseBitType: BitType.tableExtendedImage,
11033
+ description: "Standard non-normative extended table image bit, used to create non-normative extended images in tables in articles or books"
11034
+ },
11035
+ [BitType.standardRemarkTableExtendedImageNormative]: {
11036
+ since: "1.17.0",
11037
+ baseBitType: BitType.tableExtendedImage,
11038
+ description: "Standard normative remark extended table image bit, used to create normative remark extended images in tables in articles or books"
11039
+ },
11040
+ [BitType.standardRemarkTableExtendedImageNonNormative]: {
11041
+ since: "1.17.0",
11042
+ baseBitType: BitType.tableExtendedImage,
11043
+ description: "Standard non-normative remark extended table image bit, used to create non-normative remark extended images in tables in articles or books"
11044
+ },
10884
11045
  [BitType.smartStandardTableImageNormative]: {
10885
11046
  since: "1.28.0",
10886
11047
  baseBitType: BitType.standardTableImageNormative,
@@ -10901,6 +11062,26 @@ var BITS = {
10901
11062
  baseBitType: BitType.standardRemarkTableImageNonNormative,
10902
11063
  description: "Smart standard non-normative remark table image bit, used to create smart standard non-normative remark images in tables in articles or books"
10903
11064
  },
11065
+ [BitType.smartStandardTableExtendedImageNormative]: {
11066
+ since: "1.28.0",
11067
+ baseBitType: BitType.standardTableExtendedImageNormative,
11068
+ description: "Smart standard normative extended table image bit, used to create smart standard normative extended images in tables in articles or books"
11069
+ },
11070
+ [BitType.smartStandardTableExtendedImageNonNormative]: {
11071
+ since: "1.28.0",
11072
+ baseBitType: BitType.standardTableExtendedImageNonNormative,
11073
+ description: "Smart standard non-normative extended table image bit, used to create smart standard non-normative extended images in tables in articles or books"
11074
+ },
11075
+ [BitType.smartStandardRemarkTableExtendedImageNormative]: {
11076
+ since: "1.28.0",
11077
+ baseBitType: BitType.standardRemarkTableExtendedImageNormative,
11078
+ description: "Smart standard normative remark extended table image bit, used to create smart standard normative remark extended images in tables in articles or books"
11079
+ },
11080
+ [BitType.smartStandardRemarkTableExtendedImageNonNormative]: {
11081
+ since: "1.28.0",
11082
+ baseBitType: BitType.standardRemarkTableExtendedImageNonNormative,
11083
+ description: "Smart standard non-normative remark extended table image bit, used to create smart standard non-normative remark extended images in tables in articles or books"
11084
+ },
10904
11085
  [BitType.smartStandardTableImageNormativeCollapsible]: {
10905
11086
  since: "1.28.0",
10906
11087
  baseBitType: BitType.smartStandardTableImageNormative,
@@ -10921,6 +11102,26 @@ var BITS = {
10921
11102
  baseBitType: BitType.smartStandardRemarkTableImageNonNormative,
10922
11103
  description: "Collapsible smart standard non-normative remark table image bit, used to create collapsible smart standard non-normative remark images in tables in articles or books"
10923
11104
  },
11105
+ [BitType.smartStandardTableExtendedImageNormativeCollapsible]: {
11106
+ since: "1.28.0",
11107
+ baseBitType: BitType.smartStandardTableExtendedImageNormative,
11108
+ description: "Collapsible smart standard normative extended table image bit, used to create collapsible smart standard normative extended images in tables in articles or books"
11109
+ },
11110
+ [BitType.smartStandardTableExtendedImageNonNormativeCollapsible]: {
11111
+ since: "1.28.0",
11112
+ baseBitType: BitType.smartStandardTableExtendedImageNonNormative,
11113
+ description: "Collapsible smart standard non-normative extended table image bit, used to create collapsible smart standard non-normative extended images in tables in articles or books"
11114
+ },
11115
+ [BitType.smartStandardRemarkTableExtendedImageNormativeCollapsible]: {
11116
+ since: "1.28.0",
11117
+ baseBitType: BitType.smartStandardRemarkTableExtendedImageNormative,
11118
+ description: "Collapsible smart standard normative remark extended table image bit, used to create collapsible smart standard normative remark extended images in tables in articles or books"
11119
+ },
11120
+ [BitType.smartStandardRemarkTableExtendedImageNonNormativeCollapsible]: {
11121
+ since: "1.28.0",
11122
+ baseBitType: BitType.smartStandardRemarkTableExtendedImageNonNormative,
11123
+ description: "Collapsible smart standard non-normative remark extended table image bit, used to create collapsible smart standard non-normative remark extended images in tables in articles or books"
11124
+ },
10924
11125
  [BitType.imageLink]: {
10925
11126
  since: "1.3.0",
10926
11127
  baseBitType: BitType._standard,
@@ -12469,7 +12670,12 @@ var BITS = {
12469
12670
  {
12470
12671
  key: ConfigKey.property_caption,
12471
12672
  description: "Caption for the table, used to define the title of the table",
12472
- format: TagFormat.bitmarkText
12673
+ format: TagFormat.bitmarkText,
12674
+ // HTML-10 / HTML-C2: a bit-level caption renders as the table's
12675
+ // `<caption>` — placed inside the idiomatic `<table>` container as
12676
+ // its first child. Outside a table context it falls back to the
12677
+ // residual carrier.
12678
+ htmlKey: { "@el": "caption", "@children": "$" }
12473
12679
  },
12474
12680
  {
12475
12681
  key: ConfigKey.property_tableFixedHeader,
@@ -12554,7 +12760,9 @@ var BITS = {
12554
12760
  {
12555
12761
  key: ConfigKey.property_caption,
12556
12762
  description: "Caption for the table image, used to provide a description for the image",
12557
- format: TagFormat.bitmarkText
12763
+ format: TagFormat.bitmarkText,
12764
+ // HTML-10 / HTML-C2: render as the table's `<caption>` (see table bit).
12765
+ htmlKey: { "@el": "caption", "@children": "$" }
12558
12766
  },
12559
12767
  {
12560
12768
  key: ConfigKey.group_backgroundWallpaper,
@@ -12597,6 +12805,26 @@ var BITS = {
12597
12805
  baseBitType: BitType.table,
12598
12806
  description: "Standard non-normative remark table bit, used to create standard non-normative remark tables in articles or books"
12599
12807
  },
12808
+ [BitType.standardTableExtendedNormative]: {
12809
+ since: "1.16.0",
12810
+ baseBitType: BitType.tableExtended,
12811
+ description: "Standard normative extended table bit, used to create standard normative extended tables in articles or books"
12812
+ },
12813
+ [BitType.standardTableExtendedNonNormative]: {
12814
+ since: "1.16.0",
12815
+ baseBitType: BitType.tableExtended,
12816
+ description: "Standard non-normative extended table bit, used to create standard non-normative extended tables in articles or books"
12817
+ },
12818
+ [BitType.standardRemarkTableExtendedNormative]: {
12819
+ since: "1.17.0",
12820
+ baseBitType: BitType.tableExtended,
12821
+ description: "Standard normative remark extended table bit, used to create standard normative remark extended tables in articles or books"
12822
+ },
12823
+ [BitType.standardRemarkTableExtendedNonNormative]: {
12824
+ since: "1.17.0",
12825
+ baseBitType: BitType.tableExtended,
12826
+ description: "Standard non-normative remark extended table bit, used to create standard non-normative remark extended tables in articles or books"
12827
+ },
12600
12828
  [BitType.smartStandardTableNormative]: {
12601
12829
  since: "1.28.0",
12602
12830
  baseBitType: BitType.standardTableNormative,
@@ -12617,6 +12845,26 @@ var BITS = {
12617
12845
  baseBitType: BitType.standardRemarkTableNonNormative,
12618
12846
  description: "Smart standard non-normative remark table bit, used to create smart standard non-normative remark tables in articles or books"
12619
12847
  },
12848
+ [BitType.smartStandardTableExtendedNormative]: {
12849
+ since: "1.28.0",
12850
+ baseBitType: BitType.standardTableExtendedNormative,
12851
+ description: "Smart standard normative extended table bit, used to create smart standard normative extended tables in articles or books"
12852
+ },
12853
+ [BitType.smartStandardTableExtendedNonNormative]: {
12854
+ since: "1.28.0",
12855
+ baseBitType: BitType.standardTableExtendedNonNormative,
12856
+ description: "Smart standard non-normative extended table bit, used to create smart standard non-normative extended tables in articles or books"
12857
+ },
12858
+ [BitType.smartStandardRemarkTableExtendedNormative]: {
12859
+ since: "1.28.0",
12860
+ baseBitType: BitType.standardRemarkTableExtendedNormative,
12861
+ description: "Smart standard normative remark extended table bit, used to create smart standard normative remark extended tables in articles or books"
12862
+ },
12863
+ [BitType.smartStandardRemarkTableExtendedNonNormative]: {
12864
+ since: "1.28.0",
12865
+ baseBitType: BitType.standardRemarkTableExtendedNonNormative,
12866
+ description: "Smart standard non-normative remark extended table bit, used to create smart standard non-normative remark extended tables in articles or books"
12867
+ },
12620
12868
  [BitType.smartStandardTableNormativeCollapsible]: {
12621
12869
  since: "1.28.0",
12622
12870
  baseBitType: BitType.smartStandardTableNormative,
@@ -12637,6 +12885,26 @@ var BITS = {
12637
12885
  baseBitType: BitType.smartStandardRemarkTableNonNormative,
12638
12886
  description: "Collapsible smart standard non-normative remark table bit, used to create collapsible smart standard non-normative remark tables in articles or books"
12639
12887
  },
12888
+ [BitType.smartStandardTableExtendedNormativeCollapsible]: {
12889
+ since: "1.28.0",
12890
+ baseBitType: BitType.smartStandardTableExtendedNormative,
12891
+ description: "Collapsible smart standard normative extended table bit, used to create collapsible smart standard normative extended tables in articles or books"
12892
+ },
12893
+ [BitType.smartStandardTableExtendedNonNormativeCollapsible]: {
12894
+ since: "1.28.0",
12895
+ baseBitType: BitType.smartStandardTableExtendedNonNormative,
12896
+ description: "Collapsible smart standard non-normative extended table bit, used to create collapsible smart standard non-normative extended tables in articles or books"
12897
+ },
12898
+ [BitType.smartStandardRemarkTableExtendedNormativeCollapsible]: {
12899
+ since: "1.28.0",
12900
+ baseBitType: BitType.smartStandardRemarkTableExtendedNormative,
12901
+ description: "Collapsible smart standard normative remark extended table bit, used to create collapsible smart standard normative remark extended tables in articles or books"
12902
+ },
12903
+ [BitType.smartStandardRemarkTableExtendedNonNormativeCollapsible]: {
12904
+ since: "1.28.0",
12905
+ baseBitType: BitType.smartStandardRemarkTableExtendedNonNormative,
12906
+ description: "Collapsible smart standard non-normative remark extended table bit, used to create collapsible smart standard non-normative remark extended tables in articles or books"
12907
+ },
12640
12908
  [BitType.parameters]: {
12641
12909
  since: "1.18.0",
12642
12910
  baseBitType: BitType.table,
@@ -13578,6 +13846,8 @@ var Config = class {
13578
13846
  jsonKey: tag.jsonKey,
13579
13847
  exportJsonKey: tag.exportJsonKey,
13580
13848
  hasExportJsonKey: tag.hasExportJsonKey,
13849
+ htmlKey: tag.htmlKey,
13850
+ hasHtmlKey: tag.hasHtmlKey,
13581
13851
  deprecated: tag.deprecated
13582
13852
  });
13583
13853
  finalResourceTags[k] = newTag;
@@ -13682,7 +13952,7 @@ var instance2 = new Config();
13682
13952
  // src/generated/package_info.ts
13683
13953
  var PACKAGE_INFO = {
13684
13954
  "name": "@gmb/bitmark-parser-generator",
13685
- "version": "5.26.0",
13955
+ "version": "5.28.0",
13686
13956
  "author": "Get More Brain Ltd",
13687
13957
  "license": "ISC",
13688
13958
  "description": "A bitmark parser and generator using Peggy.js"
@@ -14572,6 +14842,10 @@ var NodeType = {
14572
14842
  relatedBookValue: "relatedBookValue",
14573
14843
  translationOfBook: "translationOfBook",
14574
14844
  translationOfBookValue: "translationOfBookValue",
14845
+ userFeedbackTranslationUrl: "userFeedbackTranslationUrl",
14846
+ userFeedbackTranslationUrlValue: "userFeedbackTranslationUrlValue",
14847
+ userFeedbackContentUrl: "userFeedbackContentUrl",
14848
+ userFeedbackContentUrlValue: "userFeedbackContentUrlValue",
14575
14849
  releaseDate: "releaseDate",
14576
14850
  releaseDateValue: "releaseDateValue",
14577
14851
  releaseKind: "releaseKind",
@@ -27781,6 +28055,18 @@ var Builder = class extends BaseBuilder {
27781
28055
  data.translationOfBook,
27782
28056
  options
27783
28057
  ),
28058
+ userFeedbackTranslationUrl: this.toAstProperty(
28059
+ bitType,
28060
+ ConfigKey.property_userFeedbackTranslationUrl,
28061
+ data.userFeedbackTranslationUrl,
28062
+ options
28063
+ ),
28064
+ userFeedbackContentUrl: this.toAstProperty(
28065
+ bitType,
28066
+ ConfigKey.property_userFeedbackContentUrl,
28067
+ data.userFeedbackContentUrl,
28068
+ options
28069
+ ),
27784
28070
  author: this.toAstProperty(bitType, ConfigKey.property_author, data.author, options),
27785
28071
  subject: this.toAstProperty(bitType, ConfigKey.property_subject, data.subject, options),
27786
28072
  date: this.toAstProperty(bitType, ConfigKey.property_date, data.date, options),
@@ -31572,7 +31858,8 @@ var BitmarkGenerator = class extends AstWalkerGenerator {
31572
31858
  this.writeNL();
31573
31859
  }
31574
31860
  const content = cell.content ?? instance3.EMPTY_STRING;
31575
- this.writeTextOrValue(content, this.textFormat, TextLocation.body);
31861
+ const options = this.options.noBreakscaping ? { noBreakscaping: true } : void 0;
31862
+ this.writeTextOrValue(content, this.textFormat, TextLocation.body, options);
31576
31863
  }
31577
31864
  writeTableCellProperty(name, value) {
31578
31865
  this.writeOPA();
@@ -33038,6 +33325,278 @@ var BitmarkStringGenerator = class {
33038
33325
  }
33039
33326
  };
33040
33327
 
33328
+ // src/generator/html/HtmlTableGenerator.ts
33329
+ var MARK_TO_TAG = {
33330
+ bold: "b",
33331
+ italic: "i",
33332
+ superscript: "sup",
33333
+ subscript: "sub",
33334
+ strike: "s",
33335
+ del: "del",
33336
+ ins: "ins",
33337
+ underline: "u",
33338
+ highlight: "mark",
33339
+ code: "code"
33340
+ };
33341
+ var INLINE_IMAGE_RE = /==([^=]*(?:=(?!=)[^=]*)*)==\|imageInline:([^|\s]+)((?:\|(?:@?[A-Za-z][A-Za-z0-9_-]*:[^|\s]+|#[^|\s]*))*)\|?/g;
33342
+ var MAX_IMAGE_DIMENSION = 9999;
33343
+ var HtmlTableGenerator = class {
33344
+ constructor() {
33345
+ __publicField(this, "indentUnit", " ");
33346
+ }
33347
+ /**
33348
+ * Generate HTML table fragments from a string or JSON value.
33349
+ * @returns the concatenated `<table>` fragments, blank-line separated (empty string if none).
33350
+ */
33351
+ generate(input) {
33352
+ let data = input;
33353
+ if (typeof data === "string") {
33354
+ try {
33355
+ data = JSON.parse(data);
33356
+ } catch {
33357
+ return "";
33358
+ }
33359
+ }
33360
+ const bits = this.collectBits(data);
33361
+ const fragments = [];
33362
+ for (const bit of bits) {
33363
+ const html = this.renderTableBit(bit);
33364
+ if (html) fragments.push(html);
33365
+ }
33366
+ return fragments.join("\n\n");
33367
+ }
33368
+ // --- bit collection -------------------------------------------------------
33369
+ collectBits(data) {
33370
+ const out = [];
33371
+ const visit = (value) => {
33372
+ if (value == null || typeof value !== "object") return;
33373
+ if (Array.isArray(value)) {
33374
+ value.forEach(visit);
33375
+ return;
33376
+ }
33377
+ const obj = value;
33378
+ if ("bit" in obj && obj.bit && typeof obj.bit === "object") {
33379
+ out.push(obj.bit);
33380
+ } else if ("type" in obj) {
33381
+ out.push(obj);
33382
+ }
33383
+ };
33384
+ visit(data);
33385
+ return out;
33386
+ }
33387
+ // --- table rendering ------------------------------------------------------
33388
+ renderTableBit(bit) {
33389
+ const table = bit.table;
33390
+ if (!table) return "";
33391
+ if (bit.type !== "table" && bit.type !== "table-extended") return "";
33392
+ const extended = this.toExtended(table);
33393
+ const lines = [];
33394
+ lines.push("<table>");
33395
+ if (bit.caption != null) {
33396
+ const caption = this.cellContentToHtml(this.asTextAst(bit.caption));
33397
+ if (caption) lines.push(`${this.indentUnit}<caption>${caption}</caption>`);
33398
+ }
33399
+ this.renderSection(lines, "thead", extended.header, "header");
33400
+ this.renderSection(lines, "tbody", extended.body, "body");
33401
+ this.renderSection(lines, "tfoot", extended.footer, "footer");
33402
+ lines.push("</table>");
33403
+ return lines.join("\n");
33404
+ }
33405
+ toExtended(table) {
33406
+ const t = table;
33407
+ if (t.header || t.body || t.footer) return table;
33408
+ return convertBasicToExtendedTableFormat(table);
33409
+ }
33410
+ renderSection(lines, tag, section, key) {
33411
+ if (!section || !section.rows || section.rows.length === 0) return;
33412
+ lines.push(`${this.indentUnit}<${tag}>`);
33413
+ for (const row of section.rows) {
33414
+ lines.push(`${this.indentUnit.repeat(2)}<tr>`);
33415
+ for (const cell of row.cells ?? []) {
33416
+ lines.push(`${this.indentUnit.repeat(3)}${this.renderCell(cell, key)}`);
33417
+ }
33418
+ lines.push(`${this.indentUnit.repeat(2)}</tr>`);
33419
+ }
33420
+ lines.push(`${this.indentUnit}</${tag}>`);
33421
+ }
33422
+ renderCell(cell, _key) {
33423
+ const tag = cell.title === true ? "th" : "td";
33424
+ const attrs = [];
33425
+ if (cell.rowspan && cell.rowspan > 1) attrs.push(`rowspan="${cell.rowspan}"`);
33426
+ if (cell.colspan && cell.colspan > 1) attrs.push(`colspan="${cell.colspan}"`);
33427
+ if (cell.scope) attrs.push(`scope="${cell.scope}"`);
33428
+ if (cell.colwidth && cell.colwidth > 0) attrs.push(`width="${cell.colwidth}"`);
33429
+ const open = attrs.length > 0 ? `<${tag} ${attrs.join(" ")}>` : `<${tag}>`;
33430
+ return `${open}${this.cellContentToHtml(this.asTextAst(cell.content))}</${tag}>`;
33431
+ }
33432
+ // --- cell content (TextAst -> HTML) ---------------------------------------
33433
+ asTextAst(content) {
33434
+ if (content == null) return [];
33435
+ if (typeof content === "string") return [{ type: "text", text: content }];
33436
+ return content;
33437
+ }
33438
+ cellContentToHtml(blocks) {
33439
+ if (blocks.length === 0) return "";
33440
+ if (blocks.length === 1 && blocks[0].type === "paragraph") {
33441
+ return this.inlineToHtml(blocks[0].content ?? []);
33442
+ }
33443
+ return blocks.map((b) => this.blockToHtml(b)).join("");
33444
+ }
33445
+ blockToHtml(node) {
33446
+ switch (node.type) {
33447
+ case "paragraph":
33448
+ return `<p>${this.inlineToHtml(node.content ?? [])}</p>`;
33449
+ case "bulletList":
33450
+ case "noBulletList":
33451
+ case "orderedList":
33452
+ case "orderedListRoman":
33453
+ case "orderedListRomanLower":
33454
+ case "letteredList":
33455
+ case "letteredListLower":
33456
+ case "taskList":
33457
+ return this.listToHtml(node);
33458
+ case "image":
33459
+ case "imageInline":
33460
+ return this.imageToHtml(node);
33461
+ case "latex":
33462
+ return this.latexToHtml(node);
33463
+ default:
33464
+ return this.inlineToHtml([node]);
33465
+ }
33466
+ }
33467
+ inlineToHtml(nodes) {
33468
+ return nodes.map((n) => this.inlineNodeToHtml(n)).join("");
33469
+ }
33470
+ inlineNodeToHtml(node) {
33471
+ if (node.type === "hardBreak") return "<br>";
33472
+ if (node.type === "image" || node.type === "imageInline") return this.imageToHtml(node);
33473
+ if (node.type === "latex") return this.latexToHtml(node);
33474
+ if (node.type === "text") {
33475
+ return this.wrapMarks(this.textToHtml(node.text ?? ""), node.marks);
33476
+ }
33477
+ if (node.content) return this.inlineToHtml(node.content);
33478
+ return this.escapeText(node.text ?? "");
33479
+ }
33480
+ wrapMarks(text, marks) {
33481
+ if (!marks || marks.length === 0) return text;
33482
+ let open = "";
33483
+ let close = "";
33484
+ for (const mark of marks) {
33485
+ if (mark.type === "link") {
33486
+ const href = mark.attrs?.href ?? "";
33487
+ open += `<a href="${this.escapeAttr(href)}">`;
33488
+ close = `</a>${close}`;
33489
+ } else {
33490
+ const tag = MARK_TO_TAG[mark.type];
33491
+ if (tag) {
33492
+ open += `<${tag}>`;
33493
+ close = `</${tag}>${close}`;
33494
+ }
33495
+ }
33496
+ }
33497
+ return `${open}${text}${close}`;
33498
+ }
33499
+ // --- lists ----------------------------------------------------------------
33500
+ listToHtml(node) {
33501
+ const { tag, attrs } = this.listTag(node);
33502
+ const items = (node.content ?? []).map((item) => this.listItemToHtml(item, node.type === "taskList")).join("");
33503
+ return `<${tag}${attrs}>${items}</${tag}>`;
33504
+ }
33505
+ listTag(node) {
33506
+ const start = node.attrs?.start;
33507
+ const startAttr = typeof start === "number" && start !== 1 ? ` start="${start}"` : "";
33508
+ switch (node.type) {
33509
+ case "orderedList":
33510
+ return { tag: "ol", attrs: startAttr };
33511
+ case "orderedListRoman":
33512
+ return { tag: "ol", attrs: ` type="I"${startAttr}` };
33513
+ case "orderedListRomanLower":
33514
+ return { tag: "ol", attrs: ` type="i"${startAttr}` };
33515
+ case "letteredList":
33516
+ return { tag: "ol", attrs: ` type="A"${startAttr}` };
33517
+ case "letteredListLower":
33518
+ return { tag: "ol", attrs: ` type="a"${startAttr}` };
33519
+ case "noBulletList":
33520
+ return { tag: "ul", attrs: ' style="list-style:none"' };
33521
+ case "taskList":
33522
+ case "bulletList":
33523
+ default:
33524
+ return { tag: "ul", attrs: "" };
33525
+ }
33526
+ }
33527
+ listItemToHtml(item, task) {
33528
+ const inner = (item.content ?? []).map((b) => {
33529
+ if (b.type === "paragraph") return this.inlineToHtml(b.content ?? []);
33530
+ return this.blockToHtml(b);
33531
+ }).join("");
33532
+ if (task) {
33533
+ const checked = item.attrs?.checked ? " checked" : "";
33534
+ return `<li><input type="checkbox"${checked}>${inner}</li>`;
33535
+ }
33536
+ return `<li>${inner}</li>`;
33537
+ }
33538
+ // --- images ---------------------------------------------------------------
33539
+ imageToHtml(node) {
33540
+ const attrs = node.attrs ?? {};
33541
+ const parts = [];
33542
+ const src = attrs.src;
33543
+ parts.push(`src="${this.escapeAttr(typeof src === "string" ? src : "")}"`);
33544
+ if (typeof attrs.alt === "string") parts.push(`alt="${this.escapeAttr(attrs.alt)}"`);
33545
+ if (typeof attrs.title === "string") parts.push(`title="${this.escapeAttr(attrs.title)}"`);
33546
+ if (attrs.width != null) parts.push(`width="${attrs.width}"`);
33547
+ if (attrs.height != null) parts.push(`height="${attrs.height}"`);
33548
+ if (typeof attrs.class === "string" && attrs.class) {
33549
+ parts.push(`class="${this.escapeAttr(attrs.class)}"`);
33550
+ }
33551
+ return `<img ${parts.join(" ")}>`;
33552
+ }
33553
+ textToHtml(text) {
33554
+ let html = "";
33555
+ let lastIndex = 0;
33556
+ for (const match of text.matchAll(INLINE_IMAGE_RE)) {
33557
+ const index = match.index ?? 0;
33558
+ html += this.escapeText(text.slice(lastIndex, index));
33559
+ html += this.inlineImageTokenToHtml(match);
33560
+ lastIndex = index + match[0].length;
33561
+ }
33562
+ html += this.escapeText(text.slice(lastIndex));
33563
+ return html;
33564
+ }
33565
+ inlineImageTokenToHtml(match) {
33566
+ const attrs = {
33567
+ alt: match[1],
33568
+ src: match[2]
33569
+ };
33570
+ const rawAttrs = match[3] ?? "";
33571
+ for (const attr of rawAttrs.split("|")) {
33572
+ if (!attr || attr.startsWith("#")) continue;
33573
+ const colon = attr.indexOf(":");
33574
+ if (colon < 1) continue;
33575
+ const key = attr.slice(0, colon).replace(/^@/, "");
33576
+ const value = attr.slice(colon + 1);
33577
+ if (key === "width" || key === "height") {
33578
+ const n = Number.parseInt(value, 10);
33579
+ if (!Number.isNaN(n) && n > 0 && n <= MAX_IMAGE_DIMENSION) attrs[key] = n;
33580
+ } else if (key === "class" || key === "title") {
33581
+ attrs[key] = value;
33582
+ }
33583
+ }
33584
+ return this.imageToHtml({ type: "imageInline", attrs });
33585
+ }
33586
+ // --- formulas -------------------------------------------------------------
33587
+ latexToHtml(node) {
33588
+ const formula = node.attrs?.formula ?? "";
33589
+ return `<math alttext="${this.escapeAttr(formula)}"></math>`;
33590
+ }
33591
+ // --- escaping -------------------------------------------------------------
33592
+ escapeText(text) {
33593
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
33594
+ }
33595
+ escapeAttr(text) {
33596
+ return this.escapeText(text).replace(/"/g, "&quot;");
33597
+ }
33598
+ };
33599
+
33041
33600
  // src/generator/json/JsonGenerator.ts
33042
33601
  var import_superenum33 = require("@ncoderz/superenum");
33043
33602
  var MOVE_BODY_RECURSION_LIMIT = 5e3;
@@ -33340,6 +33899,7 @@ var JsonGenerator = class extends AstWalkerGenerator {
33340
33899
  this.addProperty(this.bitJson, k, values, { array: true });
33341
33900
  }
33342
33901
  }
33902
+ return false;
33343
33903
  }
33344
33904
  // bitmarkAst -> bits -> bitsValue -> cardNode -> cardBits -> cardBitsValue
33345
33905
  enter_cardBitsValue(node, route) {
@@ -37983,6 +38543,8 @@ function propertyContentProcessor(context, contentDepth, tagsConfig, content, ta
37983
38543
  jsonKey: void 0,
37984
38544
  exportJsonKey: void 0,
37985
38545
  hasExportJsonKey: false,
38546
+ htmlKey: void 0,
38547
+ hasHtmlKey: false,
37986
38548
  format: TagFormat.bitmarkText,
37987
38549
  values: void 0,
37988
38550
  defaultValue: void 0,
@@ -42380,6 +42942,539 @@ var BitmarkParser = class {
42380
42942
  }
42381
42943
  };
42382
42944
 
42945
+ // src/parser/html/HtmlDom.ts
42946
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
42947
+ "area",
42948
+ "base",
42949
+ "br",
42950
+ "col",
42951
+ "embed",
42952
+ "hr",
42953
+ "img",
42954
+ "input",
42955
+ "link",
42956
+ "meta",
42957
+ "param",
42958
+ "source",
42959
+ "track",
42960
+ "wbr"
42961
+ ]);
42962
+ var IMPLIED_CLOSE = {
42963
+ li: /* @__PURE__ */ new Set(["li"]),
42964
+ td: /* @__PURE__ */ new Set(["td", "th"]),
42965
+ th: /* @__PURE__ */ new Set(["td", "th"]),
42966
+ tr: /* @__PURE__ */ new Set(["td", "th", "tr"]),
42967
+ thead: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
42968
+ tbody: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
42969
+ tfoot: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
42970
+ p: /* @__PURE__ */ new Set(["p"]),
42971
+ option: /* @__PURE__ */ new Set(["option"])
42972
+ };
42973
+ var TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9:-]*)((?:\s+(?:"[^"]*"|'[^']*'|[^<>"'])*)?)(\/)?>/g;
42974
+ var ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9:._-]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
42975
+ function parseAttrs(raw) {
42976
+ const attrs = {};
42977
+ if (!raw) return attrs;
42978
+ ATTR_RE.lastIndex = 0;
42979
+ let m;
42980
+ while ((m = ATTR_RE.exec(raw)) !== null) {
42981
+ const name = m[1].toLowerCase();
42982
+ let value = m[2] ?? "";
42983
+ if (value.length >= 2 && (value[0] === '"' || value[0] === "'")) {
42984
+ value = value.slice(1, -1);
42985
+ }
42986
+ attrs[name] = value;
42987
+ }
42988
+ return attrs;
42989
+ }
42990
+ function stripNonContent(html) {
42991
+ 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, "");
42992
+ }
42993
+ function tokenize(html) {
42994
+ const tokens = [];
42995
+ const clean = stripNonContent(html);
42996
+ TAG_RE.lastIndex = 0;
42997
+ let lastIndex = 0;
42998
+ let m;
42999
+ while ((m = TAG_RE.exec(clean)) !== null) {
43000
+ if (m.index > lastIndex) {
43001
+ tokens.push({ kind: "text", text: clean.slice(lastIndex, m.index) });
43002
+ }
43003
+ lastIndex = TAG_RE.lastIndex;
43004
+ const isClose = m[1] === "/";
43005
+ const name = m[2].toLowerCase();
43006
+ if (isClose) {
43007
+ tokens.push({ kind: "close", name });
43008
+ } else {
43009
+ tokens.push({
43010
+ kind: "open",
43011
+ name,
43012
+ attrs: parseAttrs(m[3]),
43013
+ selfClose: m[4] === "/"
43014
+ });
43015
+ }
43016
+ }
43017
+ if (lastIndex < clean.length) {
43018
+ tokens.push({ kind: "text", text: clean.slice(lastIndex) });
43019
+ }
43020
+ return tokens;
43021
+ }
43022
+ function parseHtml(html) {
43023
+ const root = { type: "element", name: "#root", attrs: {}, children: [] };
43024
+ const stack = [root];
43025
+ const top = () => stack[stack.length - 1];
43026
+ for (const token of tokenize(html)) {
43027
+ if (token.kind === "text") {
43028
+ if (token.text) top().children.push({ type: "text", value: token.text });
43029
+ continue;
43030
+ }
43031
+ if (token.kind === "open") {
43032
+ const name = token.name;
43033
+ const closes = IMPLIED_CLOSE[name];
43034
+ if (closes) {
43035
+ while (stack.length > 1 && closes.has(top().name)) stack.pop();
43036
+ }
43037
+ const el = {
43038
+ type: "element",
43039
+ name,
43040
+ attrs: token.attrs ?? {},
43041
+ children: []
43042
+ };
43043
+ top().children.push(el);
43044
+ if (!token.selfClose && !VOID_ELEMENTS.has(name)) {
43045
+ stack.push(el);
43046
+ }
43047
+ continue;
43048
+ }
43049
+ for (let i = stack.length - 1; i > 0; i--) {
43050
+ if (stack[i].name === token.name) {
43051
+ stack.length = i;
43052
+ break;
43053
+ }
43054
+ }
43055
+ }
43056
+ return root.children;
43057
+ }
43058
+ function childElements(el, names) {
43059
+ return el.children.filter((c) => c.type === "element" && names.has(c.name));
43060
+ }
43061
+
43062
+ // src/parser/html/HtmlTableParser.ts
43063
+ var MARK_TAGS = {
43064
+ b: "bold",
43065
+ strong: "bold",
43066
+ i: "italic",
43067
+ em: "italic",
43068
+ sup: "superscript",
43069
+ sub: "subscript",
43070
+ s: "strike",
43071
+ strike: "strike",
43072
+ del: "del",
43073
+ ins: "ins",
43074
+ u: "underline",
43075
+ mark: "highlight",
43076
+ code: "code"
43077
+ };
43078
+ var SECTION_TAGS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
43079
+ var ROW_TAGS = /* @__PURE__ */ new Set(["tr"]);
43080
+ var CELL_TAGS = /* @__PURE__ */ new Set(["td", "th"]);
43081
+ var LI_TAGS = /* @__PURE__ */ new Set(["li"]);
43082
+ var VALID_SCOPES = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
43083
+ var NAMED_ENTITIES = {
43084
+ amp: "&",
43085
+ lt: "<",
43086
+ gt: ">",
43087
+ quot: '"',
43088
+ apos: "'",
43089
+ nbsp: "\xA0"
43090
+ };
43091
+ function decodeEntities(text) {
43092
+ return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body) => {
43093
+ if (body[0] === "#") {
43094
+ const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
43095
+ if (Number.isFinite(code) && code >= 0 && code <= 1114111) {
43096
+ try {
43097
+ return String.fromCodePoint(code);
43098
+ } catch {
43099
+ return whole;
43100
+ }
43101
+ }
43102
+ return whole;
43103
+ }
43104
+ return NAMED_ENTITIES[body] ?? whole;
43105
+ });
43106
+ }
43107
+ function intAttr(value) {
43108
+ if (value == null) return void 0;
43109
+ const n = Number.parseInt(value, 10);
43110
+ return Number.isInteger(n) ? n : void 0;
43111
+ }
43112
+ function isElement(node) {
43113
+ return node.type === "element";
43114
+ }
43115
+ var HtmlTableParser = class {
43116
+ constructor() {
43117
+ __publicField(this, "keepUnknownTags", false);
43118
+ }
43119
+ /**
43120
+ * Parse an HTML string and return one table bit per top-level `<table>`.
43121
+ */
43122
+ parse(html, options) {
43123
+ this.keepUnknownTags = options?.keepUnknownTags ?? false;
43124
+ const tableFormat = options?.tableFormat ?? "table-extended";
43125
+ const onWarning = options?.onWarning;
43126
+ const tables = this.collectTopLevelTables(parseHtml(html));
43127
+ const bits = [];
43128
+ for (const table of tables) {
43129
+ const bit = this.tableToBit(table, tableFormat, onWarning);
43130
+ if (bit) bits.push(bit);
43131
+ }
43132
+ return bits;
43133
+ }
43134
+ // --- table extraction -----------------------------------------------------
43135
+ /** Collect every `<table>` element, NOT descending into nested tables. */
43136
+ collectTopLevelTables(nodes) {
43137
+ const tables = [];
43138
+ const visit = (node) => {
43139
+ if (!isElement(node)) return;
43140
+ if (node.name === "table") {
43141
+ tables.push(node);
43142
+ return;
43143
+ }
43144
+ node.children.forEach(visit);
43145
+ };
43146
+ nodes.forEach(visit);
43147
+ return tables;
43148
+ }
43149
+ tableToBit(table, tableFormat, onWarning) {
43150
+ const header = [];
43151
+ const body = [];
43152
+ const footer = [];
43153
+ let caption;
43154
+ for (const child of table.children) {
43155
+ if (!isElement(child)) continue;
43156
+ if (child.name === "caption") {
43157
+ const capContent = this.inlineNodes(child.children, []);
43158
+ const trimmed = this.trimInline(capContent);
43159
+ if (trimmed.length > 0) caption = trimmed;
43160
+ } else if (SECTION_TAGS.has(child.name)) {
43161
+ const target = child.name === "thead" ? header : child.name === "tfoot" ? footer : body;
43162
+ for (const tr of childElements(child, ROW_TAGS)) {
43163
+ target.push(this.rowToCells(tr));
43164
+ }
43165
+ } else if (ROW_TAGS.has(child.name)) {
43166
+ body.push(this.rowToCells(child));
43167
+ }
43168
+ }
43169
+ if (header.length === 0 && body.length === 0 && footer.length === 0) {
43170
+ return void 0;
43171
+ }
43172
+ const tableExtended = {};
43173
+ if (header.length > 0) tableExtended.header = { rows: header };
43174
+ if (body.length > 0) tableExtended.body = { rows: body };
43175
+ if (footer.length > 0) tableExtended.footer = { rows: footer };
43176
+ let tableData = tableExtended;
43177
+ if (tableFormat === "table") {
43178
+ if (this.isLossyAsBasic(tableExtended)) {
43179
+ onWarning?.(
43180
+ "Converting an HTML table to [.table] loses rowspan/colspan/scope and multi-section structure; use --tableFormat table-extended to preserve them."
43181
+ );
43182
+ }
43183
+ tableData = convertExtendedToBasicTableFormat(tableExtended);
43184
+ }
43185
+ const bit = {
43186
+ type: tableFormat,
43187
+ format: "bitmark++",
43188
+ table: tableData
43189
+ };
43190
+ if (caption) bit.caption = caption;
43191
+ return bit;
43192
+ }
43193
+ rowToCells(tr) {
43194
+ const cells = [];
43195
+ for (const cell of childElements(tr, CELL_TAGS)) {
43196
+ cells.push(this.cellToJson(cell));
43197
+ }
43198
+ return { cells };
43199
+ }
43200
+ cellToJson(cell) {
43201
+ const json = {
43202
+ content: this.blockNodes(cell.children)
43203
+ };
43204
+ if (cell.name === "th") json.title = true;
43205
+ const rowspan = intAttr(cell.attrs.rowspan);
43206
+ if (rowspan != null && rowspan >= 2) json.rowspan = rowspan;
43207
+ const colspan = intAttr(cell.attrs.colspan);
43208
+ if (colspan != null && colspan >= 2) json.colspan = colspan;
43209
+ const scope = cell.attrs.scope?.toLowerCase();
43210
+ if (scope && VALID_SCOPES.has(scope)) {
43211
+ json.scope = scope;
43212
+ }
43213
+ const width = this.widthFromCell(cell);
43214
+ if (width != null) json.colwidth = width;
43215
+ return json;
43216
+ }
43217
+ widthFromCell(cell) {
43218
+ const attrWidth = intAttr(cell.attrs.width);
43219
+ if (attrWidth != null && attrWidth > 0) return attrWidth;
43220
+ const style = cell.attrs.style;
43221
+ if (style) {
43222
+ const m = style.match(/width\s*:\s*(\d+)\s*px/i);
43223
+ if (m) {
43224
+ const n = Number.parseInt(m[1], 10);
43225
+ if (Number.isInteger(n) && n > 0) return n;
43226
+ }
43227
+ }
43228
+ return void 0;
43229
+ }
43230
+ isLossyAsBasic(table) {
43231
+ if (table.footer) return true;
43232
+ if (table.header && table.header.rows.length > 1) return true;
43233
+ const sections = [table.header, table.body, table.footer];
43234
+ for (const section of sections) {
43235
+ if (!section) continue;
43236
+ for (const row of section.rows) {
43237
+ for (const cell of row.cells) {
43238
+ if (cell.rowspan || cell.colspan || cell.scope) return true;
43239
+ }
43240
+ }
43241
+ }
43242
+ return false;
43243
+ }
43244
+ // --- content conversion (HTML -> TextAst) ---------------------------------
43245
+ /**
43246
+ * Convert a cell/li's child nodes into block-level TextAst nodes (paragraphs, lists, images).
43247
+ */
43248
+ blockNodes(nodes) {
43249
+ const blocks = [];
43250
+ let inline = [];
43251
+ const flush = () => {
43252
+ const trimmed = this.trimInline(inline);
43253
+ inline = [];
43254
+ if (trimmed.length === 0) return;
43255
+ if (trimmed.length === 1 && trimmed[0].type === "imageInline") {
43256
+ blocks.push({ ...trimmed[0], type: "image" });
43257
+ } else {
43258
+ blocks.push({ type: "paragraph", content: trimmed, attrs: {} });
43259
+ }
43260
+ };
43261
+ for (const node of nodes) {
43262
+ if (!isElement(node)) {
43263
+ inline.push(...this.inlineText(node.value, []));
43264
+ continue;
43265
+ }
43266
+ const name = node.name;
43267
+ if (name === "p") {
43268
+ flush();
43269
+ const content = this.trimInline(this.inlineNodes(node.children, []));
43270
+ if (content.length > 0) {
43271
+ blocks.push({ type: "paragraph", content, attrs: {} });
43272
+ }
43273
+ } else if (this.isListTag(name)) {
43274
+ flush();
43275
+ const list = this.listNode(node);
43276
+ if (list) blocks.push(list);
43277
+ } else {
43278
+ inline.push(...this.inlineFromElement(node, []));
43279
+ }
43280
+ }
43281
+ flush();
43282
+ return blocks;
43283
+ }
43284
+ /** Convert child nodes to a flat list of inline TextNodes, applying the given marks. */
43285
+ inlineNodes(nodes, marks) {
43286
+ const out = [];
43287
+ for (const node of nodes) {
43288
+ if (!isElement(node)) {
43289
+ out.push(...this.inlineText(node.value, marks));
43290
+ } else {
43291
+ out.push(...this.inlineFromElement(node, marks));
43292
+ }
43293
+ }
43294
+ return out;
43295
+ }
43296
+ inlineFromElement(el, marks) {
43297
+ const name = el.name;
43298
+ if (name === "br") {
43299
+ return [{ type: "hardBreak" }];
43300
+ }
43301
+ if (name === "img") {
43302
+ return [this.imageNode(el, true)];
43303
+ }
43304
+ if (this.isMathTag(name)) {
43305
+ return [this.latexNode(el)];
43306
+ }
43307
+ const markType = MARK_TAGS[name];
43308
+ if (markType) {
43309
+ return this.inlineNodes(el.children, [...marks, { type: markType }]);
43310
+ }
43311
+ if (name === "a") {
43312
+ const linkMark = {
43313
+ type: "link",
43314
+ attrs: {
43315
+ href: el.attrs.href ?? "",
43316
+ ...el.attrs.target ? { target: el.attrs.target } : {}
43317
+ }
43318
+ };
43319
+ return this.inlineNodes(el.children, [...marks, linkMark]);
43320
+ }
43321
+ if (this.keepUnknownTags) {
43322
+ const open = this.serializeOpenTag(el);
43323
+ const close = `</${name}>`;
43324
+ return [
43325
+ ...this.inlineText(open, marks),
43326
+ ...this.inlineNodes(el.children, marks),
43327
+ ...this.inlineText(close, marks)
43328
+ ];
43329
+ }
43330
+ return this.inlineNodes(el.children, marks);
43331
+ }
43332
+ inlineText(raw, marks) {
43333
+ const text = decodeEntities(raw).replace(/\s+/g, " ");
43334
+ if (text === "") return [];
43335
+ const node = { text, type: "text" };
43336
+ if (marks.length > 0) node.marks = marks.map((m) => ({ ...m }));
43337
+ return [node];
43338
+ }
43339
+ serializeOpenTag(el) {
43340
+ const attrs = Object.entries(el.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
43341
+ return `<${el.name}${attrs}>`;
43342
+ }
43343
+ // --- lists ----------------------------------------------------------------
43344
+ isListTag(name) {
43345
+ return name === "ul" || name === "ol";
43346
+ }
43347
+ listNode(el) {
43348
+ const items = childElements(el, LI_TAGS);
43349
+ if (items.length === 0) return void 0;
43350
+ const isTask = el.name === "ul" && items.some((li) => {
43351
+ const firstEl = li.children.find(isElement);
43352
+ return firstEl?.name === "input" && firstEl.attrs.type?.toLowerCase() === "checkbox";
43353
+ });
43354
+ if (isTask) {
43355
+ return {
43356
+ type: "taskList",
43357
+ content: items.map((li) => this.taskItemNode(li))
43358
+ };
43359
+ }
43360
+ const type = this.listType(el);
43361
+ const node = {
43362
+ type,
43363
+ content: items.map((li) => this.listItemNode(li))
43364
+ };
43365
+ if (el.name === "ol") {
43366
+ const start = intAttr(el.attrs.start);
43367
+ if (start != null) {
43368
+ node.attrs = { start };
43369
+ }
43370
+ }
43371
+ return node;
43372
+ }
43373
+ listType(el) {
43374
+ if (el.name === "ol") {
43375
+ switch (el.attrs.type) {
43376
+ case "i":
43377
+ return "orderedListRomanLower";
43378
+ case "I":
43379
+ return "orderedListRoman";
43380
+ case "a":
43381
+ return "letteredListLower";
43382
+ case "A":
43383
+ return "letteredList";
43384
+ default:
43385
+ return "orderedList";
43386
+ }
43387
+ }
43388
+ const style = el.attrs.style ?? "";
43389
+ if (/list-style(-type)?\s*:\s*none/i.test(style)) return "noBulletList";
43390
+ return "bulletList";
43391
+ }
43392
+ listItemNode(li) {
43393
+ let content = this.blockNodes(li.children);
43394
+ if (content.length === 0) {
43395
+ content = [{ type: "paragraph", content: [], attrs: {} }];
43396
+ }
43397
+ return { type: "listItem", content };
43398
+ }
43399
+ taskItemNode(li) {
43400
+ const checkbox = li.children.find(
43401
+ (c) => isElement(c) && c.name === "input" && c.attrs.type?.toLowerCase() === "checkbox"
43402
+ );
43403
+ const checked = checkbox ? "checked" in checkbox.attrs : false;
43404
+ const rest = li.children.filter((c) => c !== checkbox);
43405
+ let content = this.blockNodes(rest);
43406
+ if (content.length === 0) {
43407
+ content = [{ type: "paragraph", content: [], attrs: {} }];
43408
+ }
43409
+ return { type: "taskItem", attrs: { checked }, content };
43410
+ }
43411
+ // --- images ---------------------------------------------------------------
43412
+ imageNode(el, inline) {
43413
+ const attrs = { src: el.attrs.src ?? "" };
43414
+ attrs.alt = el.attrs.alt ?? null;
43415
+ attrs.title = el.attrs.title ?? null;
43416
+ const width = intAttr(el.attrs.width);
43417
+ if (width != null) attrs.width = width;
43418
+ const height = intAttr(el.attrs.height);
43419
+ if (height != null) attrs.height = height;
43420
+ if (el.attrs.class) attrs.class = el.attrs.class;
43421
+ return { type: inline ? "imageInline" : "image", attrs };
43422
+ }
43423
+ // --- formulas -------------------------------------------------------------
43424
+ isMathTag(name) {
43425
+ return this.localName(name) === "math";
43426
+ }
43427
+ latexNode(el) {
43428
+ return {
43429
+ type: "latex",
43430
+ attrs: {
43431
+ formula: this.latexFromMath(el)
43432
+ }
43433
+ };
43434
+ }
43435
+ latexFromMath(el) {
43436
+ const alt = el.attrs.alttext ?? el.attrs.alt ?? el.attrs.title;
43437
+ if (alt != null) return decodeEntities(alt);
43438
+ const annotation = this.findLatexAnnotation(el);
43439
+ if (annotation) return this.textContent(annotation).trim();
43440
+ return this.textContent(el).trim();
43441
+ }
43442
+ findLatexAnnotation(el) {
43443
+ for (const child of el.children) {
43444
+ if (!isElement(child)) continue;
43445
+ const lowerEncoding = (child.attrs.encoding ?? "").toLowerCase();
43446
+ if (this.localName(child.name) === "annotation" && (/\btex\b/.test(lowerEncoding) || /\blatex\b/.test(lowerEncoding))) {
43447
+ return child;
43448
+ }
43449
+ const nested = this.findLatexAnnotation(child);
43450
+ if (nested) return nested;
43451
+ }
43452
+ return void 0;
43453
+ }
43454
+ textContent(el) {
43455
+ return el.children.map((child) => isElement(child) ? this.textContent(child) : decodeEntities(child.value)).join("").replace(/\s+/g, " ");
43456
+ }
43457
+ localName(name) {
43458
+ return name.includes(":") ? name.slice(name.indexOf(":") + 1) : name;
43459
+ }
43460
+ // --- whitespace -----------------------------------------------------------
43461
+ /** Trim leading/trailing whitespace from inline content (edges only). */
43462
+ trimInline(nodes) {
43463
+ const out = nodes.slice();
43464
+ const isWs = (n) => n.type === "text" && /^\s*$/.test(n.text ?? "");
43465
+ while (out.length > 0 && isWs(out[0])) out.shift();
43466
+ if (out.length > 0 && out[0].type === "text") {
43467
+ out[0] = { ...out[0], text: (out[0].text ?? "").replace(/^\s+/, "") };
43468
+ }
43469
+ while (out.length > 0 && isWs(out[out.length - 1])) out.pop();
43470
+ if (out.length > 0 && out[out.length - 1].type === "text") {
43471
+ const last = out.length - 1;
43472
+ out[last] = { ...out[last], text: (out[last].text ?? "").replace(/\s+$/, "") };
43473
+ }
43474
+ return out.filter((n) => !(n.type === "text" && n.text === ""));
43475
+ }
43476
+ };
43477
+
42383
43478
  // src/BitmarkParserGenerator.ts
42384
43479
  var import_node_path4 = __toESM(require("path"), 1);
42385
43480
  var import_fs_extra4 = __toESM(require("fs-extra"), 1);
@@ -42673,235 +43768,6 @@ var BitmarkFileGenerator = class {
42673
43768
  }
42674
43769
  };
42675
43770
 
42676
- // src/generator/html/HtmlTableGenerator.ts
42677
- var MARK_TO_TAG = {
42678
- bold: "b",
42679
- italic: "i",
42680
- superscript: "sup",
42681
- subscript: "sub",
42682
- strike: "s",
42683
- del: "del",
42684
- ins: "ins",
42685
- underline: "u",
42686
- highlight: "mark",
42687
- code: "code"
42688
- };
42689
- var HtmlTableGenerator = class {
42690
- constructor() {
42691
- __publicField(this, "indentUnit", " ");
42692
- }
42693
- /**
42694
- * Generate HTML table fragments from a string or JSON value.
42695
- * @returns the concatenated `<table>` fragments, blank-line separated (empty string if none).
42696
- */
42697
- generate(input) {
42698
- let data = input;
42699
- if (typeof data === "string") {
42700
- try {
42701
- data = JSON.parse(data);
42702
- } catch {
42703
- return "";
42704
- }
42705
- }
42706
- const bits = this.collectBits(data);
42707
- const fragments = [];
42708
- for (const bit of bits) {
42709
- const html = this.renderTableBit(bit);
42710
- if (html) fragments.push(html);
42711
- }
42712
- return fragments.join("\n\n");
42713
- }
42714
- // --- bit collection -------------------------------------------------------
42715
- collectBits(data) {
42716
- const out = [];
42717
- const visit = (value) => {
42718
- if (value == null || typeof value !== "object") return;
42719
- if (Array.isArray(value)) {
42720
- value.forEach(visit);
42721
- return;
42722
- }
42723
- const obj = value;
42724
- if ("bit" in obj && obj.bit && typeof obj.bit === "object") {
42725
- out.push(obj.bit);
42726
- } else if ("type" in obj) {
42727
- out.push(obj);
42728
- }
42729
- };
42730
- visit(data);
42731
- return out;
42732
- }
42733
- // --- table rendering ------------------------------------------------------
42734
- renderTableBit(bit) {
42735
- const table = bit.table;
42736
- if (!table) return "";
42737
- if (bit.type !== "table" && bit.type !== "table-extended") return "";
42738
- const extended = this.toExtended(table);
42739
- const lines = [];
42740
- lines.push("<table>");
42741
- if (bit.caption != null) {
42742
- const caption = this.cellContentToHtml(this.asTextAst(bit.caption));
42743
- if (caption) lines.push(`${this.indentUnit}<caption>${caption}</caption>`);
42744
- }
42745
- this.renderSection(lines, "thead", extended.header, "header");
42746
- this.renderSection(lines, "tbody", extended.body, "body");
42747
- this.renderSection(lines, "tfoot", extended.footer, "footer");
42748
- lines.push("</table>");
42749
- return lines.join("\n");
42750
- }
42751
- toExtended(table) {
42752
- const t = table;
42753
- if (t.header || t.body || t.footer) return table;
42754
- return convertBasicToExtendedTableFormat(table);
42755
- }
42756
- renderSection(lines, tag, section, key) {
42757
- if (!section || !section.rows || section.rows.length === 0) return;
42758
- lines.push(`${this.indentUnit}<${tag}>`);
42759
- for (const row of section.rows) {
42760
- lines.push(`${this.indentUnit.repeat(2)}<tr>`);
42761
- for (const cell of row.cells ?? []) {
42762
- lines.push(`${this.indentUnit.repeat(3)}${this.renderCell(cell, key)}`);
42763
- }
42764
- lines.push(`${this.indentUnit.repeat(2)}</tr>`);
42765
- }
42766
- lines.push(`${this.indentUnit}</${tag}>`);
42767
- }
42768
- renderCell(cell, _key) {
42769
- const tag = cell.title === true ? "th" : "td";
42770
- const attrs = [];
42771
- if (cell.rowspan && cell.rowspan > 1) attrs.push(`rowspan="${cell.rowspan}"`);
42772
- if (cell.colspan && cell.colspan > 1) attrs.push(`colspan="${cell.colspan}"`);
42773
- if (cell.scope) attrs.push(`scope="${cell.scope}"`);
42774
- if (cell.colwidth && cell.colwidth > 0) attrs.push(`width="${cell.colwidth}"`);
42775
- const open = attrs.length > 0 ? `<${tag} ${attrs.join(" ")}>` : `<${tag}>`;
42776
- return `${open}${this.cellContentToHtml(this.asTextAst(cell.content))}</${tag}>`;
42777
- }
42778
- // --- cell content (TextAst -> HTML) ---------------------------------------
42779
- asTextAst(content) {
42780
- if (content == null) return [];
42781
- if (typeof content === "string") return [{ type: "text", text: content }];
42782
- return content;
42783
- }
42784
- cellContentToHtml(blocks) {
42785
- if (blocks.length === 0) return "";
42786
- if (blocks.length === 1 && blocks[0].type === "paragraph") {
42787
- return this.inlineToHtml(blocks[0].content ?? []);
42788
- }
42789
- return blocks.map((b) => this.blockToHtml(b)).join("");
42790
- }
42791
- blockToHtml(node) {
42792
- switch (node.type) {
42793
- case "paragraph":
42794
- return `<p>${this.inlineToHtml(node.content ?? [])}</p>`;
42795
- case "bulletList":
42796
- case "noBulletList":
42797
- case "orderedList":
42798
- case "orderedListRoman":
42799
- case "orderedListRomanLower":
42800
- case "letteredList":
42801
- case "letteredListLower":
42802
- case "taskList":
42803
- return this.listToHtml(node);
42804
- case "image":
42805
- case "imageInline":
42806
- return this.imageToHtml(node);
42807
- default:
42808
- return this.inlineToHtml([node]);
42809
- }
42810
- }
42811
- inlineToHtml(nodes) {
42812
- return nodes.map((n) => this.inlineNodeToHtml(n)).join("");
42813
- }
42814
- inlineNodeToHtml(node) {
42815
- if (node.type === "hardBreak") return "<br>";
42816
- if (node.type === "image" || node.type === "imageInline") return this.imageToHtml(node);
42817
- if (node.type === "text") {
42818
- return this.wrapMarks(this.escapeText(node.text ?? ""), node.marks);
42819
- }
42820
- if (node.content) return this.inlineToHtml(node.content);
42821
- return this.escapeText(node.text ?? "");
42822
- }
42823
- wrapMarks(text, marks) {
42824
- if (!marks || marks.length === 0) return text;
42825
- let open = "";
42826
- let close = "";
42827
- for (const mark of marks) {
42828
- if (mark.type === "link") {
42829
- const href = mark.attrs?.href ?? "";
42830
- open += `<a href="${this.escapeAttr(href)}">`;
42831
- close = `</a>${close}`;
42832
- } else {
42833
- const tag = MARK_TO_TAG[mark.type];
42834
- if (tag) {
42835
- open += `<${tag}>`;
42836
- close = `</${tag}>${close}`;
42837
- }
42838
- }
42839
- }
42840
- return `${open}${text}${close}`;
42841
- }
42842
- // --- lists ----------------------------------------------------------------
42843
- listToHtml(node) {
42844
- const { tag, attrs } = this.listTag(node);
42845
- const items = (node.content ?? []).map((item) => this.listItemToHtml(item, node.type === "taskList")).join("");
42846
- return `<${tag}${attrs}>${items}</${tag}>`;
42847
- }
42848
- listTag(node) {
42849
- const start = node.attrs?.start;
42850
- const startAttr = typeof start === "number" && start !== 1 ? ` start="${start}"` : "";
42851
- switch (node.type) {
42852
- case "orderedList":
42853
- return { tag: "ol", attrs: startAttr };
42854
- case "orderedListRoman":
42855
- return { tag: "ol", attrs: ` type="I"${startAttr}` };
42856
- case "orderedListRomanLower":
42857
- return { tag: "ol", attrs: ` type="i"${startAttr}` };
42858
- case "letteredList":
42859
- return { tag: "ol", attrs: ` type="A"${startAttr}` };
42860
- case "letteredListLower":
42861
- return { tag: "ol", attrs: ` type="a"${startAttr}` };
42862
- case "noBulletList":
42863
- return { tag: "ul", attrs: ' style="list-style:none"' };
42864
- case "taskList":
42865
- case "bulletList":
42866
- default:
42867
- return { tag: "ul", attrs: "" };
42868
- }
42869
- }
42870
- listItemToHtml(item, task) {
42871
- const inner = (item.content ?? []).map((b) => {
42872
- if (b.type === "paragraph") return this.inlineToHtml(b.content ?? []);
42873
- return this.blockToHtml(b);
42874
- }).join("");
42875
- if (task) {
42876
- const checked = item.attrs?.checked ? " checked" : "";
42877
- return `<li><input type="checkbox"${checked}>${inner}</li>`;
42878
- }
42879
- return `<li>${inner}</li>`;
42880
- }
42881
- // --- images ---------------------------------------------------------------
42882
- imageToHtml(node) {
42883
- const attrs = node.attrs ?? {};
42884
- const parts = [];
42885
- const src = attrs.src;
42886
- parts.push(`src="${this.escapeAttr(typeof src === "string" ? src : "")}"`);
42887
- if (typeof attrs.alt === "string") parts.push(`alt="${this.escapeAttr(attrs.alt)}"`);
42888
- if (typeof attrs.title === "string") parts.push(`title="${this.escapeAttr(attrs.title)}"`);
42889
- if (attrs.width != null) parts.push(`width="${attrs.width}"`);
42890
- if (attrs.height != null) parts.push(`height="${attrs.height}"`);
42891
- if (typeof attrs.class === "string" && attrs.class) {
42892
- parts.push(`class="${this.escapeAttr(attrs.class)}"`);
42893
- }
42894
- return `<img ${parts.join(" ")}>`;
42895
- }
42896
- // --- escaping -------------------------------------------------------------
42897
- escapeText(text) {
42898
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
42899
- }
42900
- escapeAttr(text) {
42901
- return this.escapeText(text).replace(/"/g, "&quot;");
42902
- }
42903
- };
42904
-
42905
43771
  // src/generator/json/JsonFileGenerator.ts
42906
43772
  var import_node_fs4 = require("fs");
42907
43773
  var JsonFileGenerator = class {
@@ -43325,6 +44191,7 @@ var ConfigBuilder = class {
43325
44191
  k = stringUtils.camelToKebab(k);
43326
44192
  const resolvedGroups = resolveGroupReferences(k);
43327
44193
  const hasOuterExport = Object.prototype.hasOwnProperty.call(tag, "exportJsonKey");
44194
+ const hasOuterHtml = Object.prototype.hasOwnProperty.call(tag, "htmlKey");
43328
44195
  if (!hasOuterExport && tag.jsonKey != null) {
43329
44196
  const headingPath = [...pathStack, `tags.${tag.key}`].join(" / ");
43330
44197
  exportJsonKeyErrors.push(
@@ -43334,6 +44201,8 @@ var ConfigBuilder = class {
43334
44201
  for (const ref of resolvedGroups) {
43335
44202
  const exportJsonKeyChosen = hasOuterExport ? tag.exportJsonKey : ref.exportJsonKey;
43336
44203
  const hasExportChosen = hasOuterExport || !!ref.hasExportJsonKey;
44204
+ const htmlKeyChosen = hasOuterHtml ? tag.htmlKey : ref.htmlKey;
44205
+ const hasHtmlChosen = hasOuterHtml || !!ref.hasHtmlKey;
43337
44206
  const min = tag.minCount != null ? tag.minCount : ref.min;
43338
44207
  const max = tag.maxCount != null ? tag.maxCount : ref.max;
43339
44208
  const description = tag.description ? tag.description : ref.description;
@@ -43341,6 +44210,7 @@ var ConfigBuilder = class {
43341
44210
  type: "group",
43342
44211
  key: ref.key,
43343
44212
  ...hasExportChosen ? { jsonKey: exportJsonKeyChosen } : {},
44213
+ ...hasHtmlChosen ? { htmlKey: htmlKeyChosen } : {},
43344
44214
  ...min != null ? { min } : {},
43345
44215
  ...max != null ? { max } : {},
43346
44216
  ...description ? { description } : {}
@@ -43369,10 +44239,13 @@ var ConfigBuilder = class {
43369
44239
  );
43370
44240
  exportJsonKeyField = {};
43371
44241
  }
44242
+ const hasHtml = Object.prototype.hasOwnProperty.call(tag, "htmlKey");
44243
+ const htmlKeyField = hasHtml ? { htmlKey: tag.htmlKey } : {};
43372
44244
  const t = {
43373
44245
  type: "tag",
43374
44246
  key: tagName,
43375
44247
  ...exportJsonKeyField,
44248
+ ...htmlKeyField,
43376
44249
  format,
43377
44250
  default: tag.defaultValue ?? null,
43378
44251
  ...tag.nullable ? { nullable: true } : {},
@@ -43395,6 +44268,13 @@ var ConfigBuilder = class {
43395
44268
  );
43396
44269
  return {};
43397
44270
  };
44271
+ const cardHtmlKeyField = (container, htmlExportKey) => {
44272
+ const c = container;
44273
+ if (Object.prototype.hasOwnProperty.call(c, htmlExportKey)) {
44274
+ return { htmlKey: c[htmlExportKey] };
44275
+ }
44276
+ return {};
44277
+ };
43398
44278
  const serializeCardSet = (cardSetKey) => {
43399
44279
  const variantBodyFormat = (variant) => variant.format ?? TextFormat.bitmarkText;
43400
44280
  const cardSetConfig = CARDSETS[cardSetKey];
@@ -43425,6 +44305,7 @@ var ConfigBuilder = class {
43425
44305
  }
43426
44306
  return {
43427
44307
  ...cardJsonKeyField(variant, "jsonKey", "exportJsonKey", variantPath),
44308
+ ...cardHtmlKeyField(variant, "htmlKey"),
43428
44309
  format: variantBodyFormat(variant),
43429
44310
  tags: variantTags,
43430
44311
  repeatCount: variant.repeatCount ?? 1,
@@ -43436,6 +44317,7 @@ var ConfigBuilder = class {
43436
44317
  name: side.name,
43437
44318
  ...side.repeat ? { repeat: side.repeat } : {},
43438
44319
  ...cardJsonKeyField(side, "jsonKey", "exportJsonKey", sidePath),
44320
+ ...cardHtmlKeyField(side, "htmlKey"),
43439
44321
  variants
43440
44322
  };
43441
44323
  });
@@ -43443,20 +44325,23 @@ var ConfigBuilder = class {
43443
44325
  const cards = Object.entries(cardSetConfig.sections).map(([sectionName, section]) => {
43444
44326
  const sectionPath = `cardSets.${normalizedKey} / cards.${sectionName}`;
43445
44327
  const hasSideExport = Object.prototype.hasOwnProperty.call(section, "sideExportJsonKey");
44328
+ const hasSideHtml = Object.prototype.hasOwnProperty.call(section, "sideHtmlKey");
43446
44329
  let cardSides = sides;
43447
- if (hasSideExport || section.sideJsonKey != null) {
44330
+ if (hasSideExport || section.sideJsonKey != null || hasSideHtml) {
43448
44331
  const sideOverride = cardJsonKeyField(
43449
44332
  section,
43450
44333
  "sideJsonKey",
43451
44334
  "sideExportJsonKey",
43452
44335
  `${sectionPath} / sides.*`
43453
44336
  );
43454
- cardSides = sides.map((s) => ({ ...s, ...sideOverride }));
44337
+ const sideHtmlOverride = cardHtmlKeyField(section, "sideHtmlKey");
44338
+ cardSides = sides.map((s) => ({ ...s, ...sideOverride, ...sideHtmlOverride }));
43455
44339
  }
43456
44340
  return {
43457
44341
  name: sectionName,
43458
44342
  ...section.isDefault ? { isDefault: true } : {},
43459
44343
  ...cardJsonKeyField(section, "jsonKey", "exportJsonKey", sectionPath),
44344
+ ...cardHtmlKeyField(section, "htmlKey"),
43460
44345
  // PLAN-085: per-section cardinality. Emit only when non-default
43461
44346
  // (treat `0` / undefined as "unbounded — omit").
43462
44347
  ...section.minCount != null && section.minCount !== 0 ? { min: section.minCount } : {},
@@ -43464,7 +44349,7 @@ var ConfigBuilder = class {
43464
44349
  sides: cardSides
43465
44350
  };
43466
44351
  });
43467
- return { name: normalizedKey, cards };
44352
+ return { name: normalizedKey, ...cardHtmlKeyField(cardSetConfig, "htmlKey"), cards };
43468
44353
  }
43469
44354
  const cardSetPath = `cardSets.${normalizedKey} / cards.default`;
43470
44355
  return {
@@ -43474,6 +44359,7 @@ var ConfigBuilder = class {
43474
44359
  name: "default",
43475
44360
  isDefault: true,
43476
44361
  ...cardJsonKeyField(cardSetConfig, "jsonKey", "exportJsonKey", cardSetPath),
44362
+ ...cardHtmlKeyField(cardSetConfig, "htmlKey"),
43477
44363
  sides
43478
44364
  }
43479
44365
  ]
@@ -43513,6 +44399,10 @@ var ConfigBuilder = class {
43513
44399
  ref.exportJsonKey = t.jsonKey;
43514
44400
  ref.hasExportJsonKey = true;
43515
44401
  }
44402
+ if (Object.prototype.hasOwnProperty.call(t, "htmlKey")) {
44403
+ ref.htmlKey = t.htmlKey;
44404
+ ref.hasHtmlKey = true;
44405
+ }
43516
44406
  replacements.push(ref);
43517
44407
  }
43518
44408
  squashedGroups.set(groupKey, replacements);
@@ -43547,6 +44437,7 @@ var ConfigBuilder = class {
43547
44437
  type: "group",
43548
44438
  key: ref.key,
43549
44439
  ...ref.hasExportJsonKey ? { jsonKey: ref.exportJsonKey } : {},
44440
+ ...ref.hasHtmlKey ? { htmlKey: ref.htmlKey } : {},
43550
44441
  ...ref.min != null ? { min: ref.min } : {},
43551
44442
  ...ref.max != null ? { max: ref.max } : {},
43552
44443
  ...ref.description ? { description: ref.description } : {}
@@ -43560,6 +44451,7 @@ var ConfigBuilder = class {
43560
44451
  type: "group",
43561
44452
  key: ref.key,
43562
44453
  ...ref.hasExportJsonKey ? { jsonKey: ref.exportJsonKey } : {},
44454
+ ...ref.hasHtmlKey ? { htmlKey: ref.htmlKey } : {},
43563
44455
  ...ref.min != null ? { min: ref.min } : {},
43564
44456
  ...ref.max != null ? { max: ref.max } : {},
43565
44457
  ...ref.description ? { description: ref.description } : {}
@@ -43689,6 +44581,7 @@ var ConfigBuilder = class {
43689
44581
  type: "group",
43690
44582
  key: ref.key,
43691
44583
  ...ref.hasExportJsonKey ? { jsonKey: ref.exportJsonKey } : {},
44584
+ ...ref.hasHtmlKey ? { htmlKey: ref.htmlKey } : {},
43692
44585
  ...ref.min != null ? { min: ref.min } : {},
43693
44586
  ...ref.max != null ? { max: ref.max } : {},
43694
44587
  ...ref.description ? { description: ref.description } : {}
@@ -43878,499 +44771,6 @@ var ConfigBuilder = class {
43878
44771
  }
43879
44772
  };
43880
44773
 
43881
- // src/parser/html/HtmlDom.ts
43882
- var VOID_ELEMENTS = /* @__PURE__ */ new Set([
43883
- "area",
43884
- "base",
43885
- "br",
43886
- "col",
43887
- "embed",
43888
- "hr",
43889
- "img",
43890
- "input",
43891
- "link",
43892
- "meta",
43893
- "param",
43894
- "source",
43895
- "track",
43896
- "wbr"
43897
- ]);
43898
- var IMPLIED_CLOSE = {
43899
- li: /* @__PURE__ */ new Set(["li"]),
43900
- td: /* @__PURE__ */ new Set(["td", "th"]),
43901
- th: /* @__PURE__ */ new Set(["td", "th"]),
43902
- tr: /* @__PURE__ */ new Set(["td", "th", "tr"]),
43903
- thead: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43904
- tbody: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43905
- tfoot: /* @__PURE__ */ new Set(["td", "th", "tr", "thead", "tbody", "tfoot"]),
43906
- p: /* @__PURE__ */ new Set(["p"]),
43907
- option: /* @__PURE__ */ new Set(["option"])
43908
- };
43909
- var TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9-]*)((?:\s+(?:"[^"]*"|'[^']*'|[^<>"'])*)?)(\/)?>/g;
43910
- var ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9:._-]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
43911
- function parseAttrs(raw) {
43912
- const attrs = {};
43913
- if (!raw) return attrs;
43914
- ATTR_RE.lastIndex = 0;
43915
- let m;
43916
- while ((m = ATTR_RE.exec(raw)) !== null) {
43917
- const name = m[1].toLowerCase();
43918
- let value = m[2] ?? "";
43919
- if (value.length >= 2 && (value[0] === '"' || value[0] === "'")) {
43920
- value = value.slice(1, -1);
43921
- }
43922
- attrs[name] = value;
43923
- }
43924
- return attrs;
43925
- }
43926
- function stripNonContent(html) {
43927
- 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, "");
43928
- }
43929
- function tokenize(html) {
43930
- const tokens = [];
43931
- const clean = stripNonContent(html);
43932
- TAG_RE.lastIndex = 0;
43933
- let lastIndex = 0;
43934
- let m;
43935
- while ((m = TAG_RE.exec(clean)) !== null) {
43936
- if (m.index > lastIndex) {
43937
- tokens.push({ kind: "text", text: clean.slice(lastIndex, m.index) });
43938
- }
43939
- lastIndex = TAG_RE.lastIndex;
43940
- const isClose = m[1] === "/";
43941
- const name = m[2].toLowerCase();
43942
- if (isClose) {
43943
- tokens.push({ kind: "close", name });
43944
- } else {
43945
- tokens.push({
43946
- kind: "open",
43947
- name,
43948
- attrs: parseAttrs(m[3]),
43949
- selfClose: m[4] === "/"
43950
- });
43951
- }
43952
- }
43953
- if (lastIndex < clean.length) {
43954
- tokens.push({ kind: "text", text: clean.slice(lastIndex) });
43955
- }
43956
- return tokens;
43957
- }
43958
- function parseHtml(html) {
43959
- const root = { type: "element", name: "#root", attrs: {}, children: [] };
43960
- const stack = [root];
43961
- const top = () => stack[stack.length - 1];
43962
- for (const token of tokenize(html)) {
43963
- if (token.kind === "text") {
43964
- if (token.text) top().children.push({ type: "text", value: token.text });
43965
- continue;
43966
- }
43967
- if (token.kind === "open") {
43968
- const name = token.name;
43969
- const closes = IMPLIED_CLOSE[name];
43970
- if (closes) {
43971
- while (stack.length > 1 && closes.has(top().name)) stack.pop();
43972
- }
43973
- const el = {
43974
- type: "element",
43975
- name,
43976
- attrs: token.attrs ?? {},
43977
- children: []
43978
- };
43979
- top().children.push(el);
43980
- if (!token.selfClose && !VOID_ELEMENTS.has(name)) {
43981
- stack.push(el);
43982
- }
43983
- continue;
43984
- }
43985
- for (let i = stack.length - 1; i > 0; i--) {
43986
- if (stack[i].name === token.name) {
43987
- stack.length = i;
43988
- break;
43989
- }
43990
- }
43991
- }
43992
- return root.children;
43993
- }
43994
- function childElements(el, names) {
43995
- return el.children.filter((c) => c.type === "element" && names.has(c.name));
43996
- }
43997
-
43998
- // src/parser/html/HtmlTableParser.ts
43999
- var MARK_TAGS = {
44000
- b: "bold",
44001
- strong: "bold",
44002
- i: "italic",
44003
- em: "italic",
44004
- sup: "superscript",
44005
- sub: "subscript",
44006
- s: "strike",
44007
- strike: "strike",
44008
- del: "del",
44009
- ins: "ins",
44010
- u: "underline",
44011
- mark: "highlight",
44012
- code: "code"
44013
- };
44014
- var SECTION_TAGS = /* @__PURE__ */ new Set(["thead", "tbody", "tfoot"]);
44015
- var ROW_TAGS = /* @__PURE__ */ new Set(["tr"]);
44016
- var CELL_TAGS = /* @__PURE__ */ new Set(["td", "th"]);
44017
- var LI_TAGS = /* @__PURE__ */ new Set(["li"]);
44018
- var VALID_SCOPES = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
44019
- var NAMED_ENTITIES = {
44020
- amp: "&",
44021
- lt: "<",
44022
- gt: ">",
44023
- quot: '"',
44024
- apos: "'",
44025
- nbsp: "\xA0"
44026
- };
44027
- function decodeEntities(text) {
44028
- return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);/g, (whole, body) => {
44029
- if (body[0] === "#") {
44030
- const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
44031
- if (Number.isFinite(code) && code >= 0 && code <= 1114111) {
44032
- try {
44033
- return String.fromCodePoint(code);
44034
- } catch {
44035
- return whole;
44036
- }
44037
- }
44038
- return whole;
44039
- }
44040
- return NAMED_ENTITIES[body] ?? whole;
44041
- });
44042
- }
44043
- function intAttr(value) {
44044
- if (value == null) return void 0;
44045
- const n = Number.parseInt(value, 10);
44046
- return Number.isInteger(n) ? n : void 0;
44047
- }
44048
- function isElement(node) {
44049
- return node.type === "element";
44050
- }
44051
- var HtmlTableParser = class {
44052
- constructor() {
44053
- __publicField(this, "keepUnknownTags", false);
44054
- }
44055
- /**
44056
- * Parse an HTML string and return one table bit per top-level `<table>`.
44057
- */
44058
- parse(html, options) {
44059
- this.keepUnknownTags = options?.keepUnknownTags ?? false;
44060
- const tableFormat = options?.tableFormat ?? "table-extended";
44061
- const onWarning = options?.onWarning;
44062
- const tables = this.collectTopLevelTables(parseHtml(html));
44063
- const bits = [];
44064
- for (const table of tables) {
44065
- const bit = this.tableToBit(table, tableFormat, onWarning);
44066
- if (bit) bits.push(bit);
44067
- }
44068
- return bits;
44069
- }
44070
- // --- table extraction -----------------------------------------------------
44071
- /** Collect every `<table>` element, NOT descending into nested tables. */
44072
- collectTopLevelTables(nodes) {
44073
- const tables = [];
44074
- const visit = (node) => {
44075
- if (!isElement(node)) return;
44076
- if (node.name === "table") {
44077
- tables.push(node);
44078
- return;
44079
- }
44080
- node.children.forEach(visit);
44081
- };
44082
- nodes.forEach(visit);
44083
- return tables;
44084
- }
44085
- tableToBit(table, tableFormat, onWarning) {
44086
- const header = [];
44087
- const body = [];
44088
- const footer = [];
44089
- let caption;
44090
- for (const child of table.children) {
44091
- if (!isElement(child)) continue;
44092
- if (child.name === "caption") {
44093
- const capContent = this.inlineNodes(child.children, []);
44094
- const trimmed = this.trimInline(capContent);
44095
- if (trimmed.length > 0) caption = trimmed;
44096
- } else if (SECTION_TAGS.has(child.name)) {
44097
- const target = child.name === "thead" ? header : child.name === "tfoot" ? footer : body;
44098
- for (const tr of childElements(child, ROW_TAGS)) {
44099
- target.push(this.rowToCells(tr));
44100
- }
44101
- } else if (ROW_TAGS.has(child.name)) {
44102
- body.push(this.rowToCells(child));
44103
- }
44104
- }
44105
- if (header.length === 0 && body.length === 0 && footer.length === 0) {
44106
- return void 0;
44107
- }
44108
- const tableExtended = {};
44109
- if (header.length > 0) tableExtended.header = { rows: header };
44110
- if (body.length > 0) tableExtended.body = { rows: body };
44111
- if (footer.length > 0) tableExtended.footer = { rows: footer };
44112
- let tableData = tableExtended;
44113
- if (tableFormat === "table") {
44114
- if (this.isLossyAsBasic(tableExtended)) {
44115
- onWarning?.(
44116
- "Converting an HTML table to [.table] loses rowspan/colspan/scope and multi-section structure; use --tableFormat table-extended to preserve them."
44117
- );
44118
- }
44119
- tableData = convertExtendedToBasicTableFormat(tableExtended);
44120
- }
44121
- const bit = {
44122
- type: tableFormat,
44123
- format: "bitmark++",
44124
- table: tableData
44125
- };
44126
- if (caption) bit.caption = caption;
44127
- return bit;
44128
- }
44129
- rowToCells(tr) {
44130
- const cells = [];
44131
- for (const cell of childElements(tr, CELL_TAGS)) {
44132
- cells.push(this.cellToJson(cell));
44133
- }
44134
- return { cells };
44135
- }
44136
- cellToJson(cell) {
44137
- const json = {
44138
- content: this.blockNodes(cell.children)
44139
- };
44140
- if (cell.name === "th") json.title = true;
44141
- const rowspan = intAttr(cell.attrs.rowspan);
44142
- if (rowspan != null && rowspan >= 2) json.rowspan = rowspan;
44143
- const colspan = intAttr(cell.attrs.colspan);
44144
- if (colspan != null && colspan >= 2) json.colspan = colspan;
44145
- const scope = cell.attrs.scope?.toLowerCase();
44146
- if (scope && VALID_SCOPES.has(scope)) {
44147
- json.scope = scope;
44148
- }
44149
- const width = this.widthFromCell(cell);
44150
- if (width != null) json.colwidth = width;
44151
- return json;
44152
- }
44153
- widthFromCell(cell) {
44154
- const attrWidth = intAttr(cell.attrs.width);
44155
- if (attrWidth != null && attrWidth > 0) return attrWidth;
44156
- const style = cell.attrs.style;
44157
- if (style) {
44158
- const m = style.match(/width\s*:\s*(\d+)\s*px/i);
44159
- if (m) {
44160
- const n = Number.parseInt(m[1], 10);
44161
- if (Number.isInteger(n) && n > 0) return n;
44162
- }
44163
- }
44164
- return void 0;
44165
- }
44166
- isLossyAsBasic(table) {
44167
- if (table.footer) return true;
44168
- if (table.header && table.header.rows.length > 1) return true;
44169
- const sections = [table.header, table.body, table.footer];
44170
- for (const section of sections) {
44171
- if (!section) continue;
44172
- for (const row of section.rows) {
44173
- for (const cell of row.cells) {
44174
- if (cell.rowspan || cell.colspan || cell.scope) return true;
44175
- }
44176
- }
44177
- }
44178
- return false;
44179
- }
44180
- // --- content conversion (HTML -> TextAst) ---------------------------------
44181
- /**
44182
- * Convert a cell/li's child nodes into block-level TextAst nodes (paragraphs, lists, images).
44183
- */
44184
- blockNodes(nodes) {
44185
- const blocks = [];
44186
- let inline = [];
44187
- const flush = () => {
44188
- const trimmed = this.trimInline(inline);
44189
- inline = [];
44190
- if (trimmed.length === 0) return;
44191
- if (trimmed.length === 1 && trimmed[0].type === "imageInline") {
44192
- blocks.push({ ...trimmed[0], type: "image" });
44193
- } else {
44194
- blocks.push({ type: "paragraph", content: trimmed, attrs: {} });
44195
- }
44196
- };
44197
- for (const node of nodes) {
44198
- if (!isElement(node)) {
44199
- inline.push(...this.inlineText(node.value, []));
44200
- continue;
44201
- }
44202
- const name = node.name;
44203
- if (name === "p") {
44204
- flush();
44205
- const content = this.trimInline(this.inlineNodes(node.children, []));
44206
- if (content.length > 0) {
44207
- blocks.push({ type: "paragraph", content, attrs: {} });
44208
- }
44209
- } else if (this.isListTag(name)) {
44210
- flush();
44211
- const list = this.listNode(node);
44212
- if (list) blocks.push(list);
44213
- } else {
44214
- inline.push(...this.inlineFromElement(node, []));
44215
- }
44216
- }
44217
- flush();
44218
- return blocks;
44219
- }
44220
- /** Convert child nodes to a flat list of inline TextNodes, applying the given marks. */
44221
- inlineNodes(nodes, marks) {
44222
- const out = [];
44223
- for (const node of nodes) {
44224
- if (!isElement(node)) {
44225
- out.push(...this.inlineText(node.value, marks));
44226
- } else {
44227
- out.push(...this.inlineFromElement(node, marks));
44228
- }
44229
- }
44230
- return out;
44231
- }
44232
- inlineFromElement(el, marks) {
44233
- const name = el.name;
44234
- if (name === "br") {
44235
- return [{ type: "hardBreak" }];
44236
- }
44237
- if (name === "img") {
44238
- return [this.imageNode(el, true)];
44239
- }
44240
- const markType = MARK_TAGS[name];
44241
- if (markType) {
44242
- return this.inlineNodes(el.children, [...marks, { type: markType }]);
44243
- }
44244
- if (name === "a") {
44245
- const linkMark = {
44246
- type: "link",
44247
- attrs: {
44248
- href: el.attrs.href ?? "",
44249
- ...el.attrs.target ? { target: el.attrs.target } : {}
44250
- }
44251
- };
44252
- return this.inlineNodes(el.children, [...marks, linkMark]);
44253
- }
44254
- if (this.keepUnknownTags) {
44255
- const open = this.serializeOpenTag(el);
44256
- const close = `</${name}>`;
44257
- return [
44258
- ...this.inlineText(open, marks),
44259
- ...this.inlineNodes(el.children, marks),
44260
- ...this.inlineText(close, marks)
44261
- ];
44262
- }
44263
- return this.inlineNodes(el.children, marks);
44264
- }
44265
- inlineText(raw, marks) {
44266
- const text = decodeEntities(raw).replace(/\s+/g, " ");
44267
- if (text === "") return [];
44268
- const node = { text, type: "text" };
44269
- if (marks.length > 0) node.marks = marks.map((m) => ({ ...m }));
44270
- return [node];
44271
- }
44272
- serializeOpenTag(el) {
44273
- const attrs = Object.entries(el.attrs).map(([k, v]) => v === "" ? ` ${k}` : ` ${k}="${v}"`).join("");
44274
- return `<${el.name}${attrs}>`;
44275
- }
44276
- // --- lists ----------------------------------------------------------------
44277
- isListTag(name) {
44278
- return name === "ul" || name === "ol";
44279
- }
44280
- listNode(el) {
44281
- const items = childElements(el, LI_TAGS);
44282
- if (items.length === 0) return void 0;
44283
- const isTask = el.name === "ul" && items.some((li) => {
44284
- const firstEl = li.children.find(isElement);
44285
- return firstEl?.name === "input" && firstEl.attrs.type?.toLowerCase() === "checkbox";
44286
- });
44287
- if (isTask) {
44288
- return {
44289
- type: "taskList",
44290
- content: items.map((li) => this.taskItemNode(li))
44291
- };
44292
- }
44293
- const type = this.listType(el);
44294
- const node = {
44295
- type,
44296
- content: items.map((li) => this.listItemNode(li))
44297
- };
44298
- if (el.name === "ol") {
44299
- const start = intAttr(el.attrs.start);
44300
- if (start != null) {
44301
- node.attrs = { start };
44302
- }
44303
- }
44304
- return node;
44305
- }
44306
- listType(el) {
44307
- if (el.name === "ol") {
44308
- switch (el.attrs.type) {
44309
- case "i":
44310
- return "orderedListRomanLower";
44311
- case "I":
44312
- return "orderedListRoman";
44313
- case "a":
44314
- return "letteredListLower";
44315
- case "A":
44316
- return "letteredList";
44317
- default:
44318
- return "orderedList";
44319
- }
44320
- }
44321
- const style = el.attrs.style ?? "";
44322
- if (/list-style(-type)?\s*:\s*none/i.test(style)) return "noBulletList";
44323
- return "bulletList";
44324
- }
44325
- listItemNode(li) {
44326
- let content = this.blockNodes(li.children);
44327
- if (content.length === 0) {
44328
- content = [{ type: "paragraph", content: [], attrs: {} }];
44329
- }
44330
- return { type: "listItem", content };
44331
- }
44332
- taskItemNode(li) {
44333
- const checkbox = li.children.find(
44334
- (c) => isElement(c) && c.name === "input" && c.attrs.type?.toLowerCase() === "checkbox"
44335
- );
44336
- const checked = checkbox ? "checked" in checkbox.attrs : false;
44337
- const rest = li.children.filter((c) => c !== checkbox);
44338
- let content = this.blockNodes(rest);
44339
- if (content.length === 0) {
44340
- content = [{ type: "paragraph", content: [], attrs: {} }];
44341
- }
44342
- return { type: "taskItem", attrs: { checked }, content };
44343
- }
44344
- // --- images ---------------------------------------------------------------
44345
- imageNode(el, inline) {
44346
- const attrs = { src: el.attrs.src ?? "" };
44347
- attrs.alt = el.attrs.alt ?? null;
44348
- attrs.title = el.attrs.title ?? null;
44349
- const width = intAttr(el.attrs.width);
44350
- if (width != null) attrs.width = width;
44351
- const height = intAttr(el.attrs.height);
44352
- if (height != null) attrs.height = height;
44353
- if (el.attrs.class) attrs.class = el.attrs.class;
44354
- return { type: inline ? "imageInline" : "image", attrs };
44355
- }
44356
- // --- whitespace -----------------------------------------------------------
44357
- /** Trim leading/trailing whitespace from inline content (edges only). */
44358
- trimInline(nodes) {
44359
- const out = nodes.slice();
44360
- const isWs = (n) => n.type === "text" && /^\s*$/.test(n.text ?? "");
44361
- while (out.length > 0 && isWs(out[0])) out.shift();
44362
- if (out.length > 0 && out[0].type === "text") {
44363
- out[0] = { ...out[0], text: (out[0].text ?? "").replace(/^\s+/, "") };
44364
- }
44365
- while (out.length > 0 && isWs(out[out.length - 1])) out.pop();
44366
- if (out.length > 0 && out[out.length - 1].type === "text") {
44367
- const last = out.length - 1;
44368
- out[last] = { ...out[last], text: (out[last].text ?? "").replace(/\s+$/, "") };
44369
- }
44370
- return out.filter((n) => !(n.type === "text" && n.text === ""));
44371
- }
44372
- };
44373
-
44374
44774
  // src/BitmarkParserGenerator.ts
44375
44775
  var Input = {
44376
44776
  /**
@@ -45007,7 +45407,10 @@ var BitmarkParserGenerator = class {
45007
45407
  outputFile: opts.outputFile,
45008
45408
  fileOptions: opts.fileOptions,
45009
45409
  jsonOptions: opts.jsonOptions,
45010
- bitmarkOptions: opts.bitmarkOptions
45410
+ bitmarkOptions: {
45411
+ ...opts.bitmarkOptions,
45412
+ ...opts.noBreakscaping ? { noBreakscaping: true } : {}
45413
+ }
45011
45414
  });
45012
45415
  }
45013
45416
  const outputFormat = opts.outputFormat ?? "html";