@pubid/pubid 0.1.3 → 0.1.4

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.
Files changed (183) hide show
  1. package/artifact/corpus.json.gz +0 -0
  2. package/dist/conformance/cli.js +5 -1
  3. package/dist/flavors/amca/grammar.d.ts +4 -0
  4. package/dist/flavors/amca/grammar.js +87 -0
  5. package/dist/flavors/amca/index.d.ts +4 -0
  6. package/dist/flavors/amca/index.js +76 -0
  7. package/dist/flavors/amca/model.d.ts +29 -0
  8. package/dist/flavors/amca/model.js +146 -0
  9. package/dist/flavors/ansi/index.d.ts +24 -0
  10. package/dist/flavors/ansi/index.js +147 -0
  11. package/dist/flavors/api/grammar.d.ts +4 -0
  12. package/dist/flavors/api/grammar.js +79 -0
  13. package/dist/flavors/api/index.d.ts +4 -0
  14. package/dist/flavors/api/index.js +71 -0
  15. package/dist/flavors/api/model.d.ts +27 -0
  16. package/dist/flavors/api/model.js +114 -0
  17. package/dist/flavors/ashrae/grammar.d.ts +2 -0
  18. package/dist/flavors/ashrae/grammar.js +667 -0
  19. package/dist/flavors/ashrae/index.d.ts +4 -0
  20. package/dist/flavors/ashrae/index.js +306 -0
  21. package/dist/flavors/ashrae/model.d.ts +87 -0
  22. package/dist/flavors/ashrae/model.js +335 -0
  23. package/dist/flavors/ashrae/preprocessor.d.ts +7 -0
  24. package/dist/flavors/ashrae/preprocessor.js +44 -0
  25. package/dist/flavors/asme/grammar.d.ts +2 -0
  26. package/dist/flavors/asme/grammar.js +200 -0
  27. package/dist/flavors/asme/index.d.ts +4 -0
  28. package/dist/flavors/asme/index.js +138 -0
  29. package/dist/flavors/asme/model.d.ts +34 -0
  30. package/dist/flavors/asme/model.js +123 -0
  31. package/dist/flavors/astm/grammar.d.ts +2 -0
  32. package/dist/flavors/astm/grammar.js +110 -0
  33. package/dist/flavors/astm/index.d.ts +2 -0
  34. package/dist/flavors/astm/index.js +127 -0
  35. package/dist/flavors/astm/model.d.ts +42 -0
  36. package/dist/flavors/astm/model.js +263 -0
  37. package/dist/flavors/bipm/index.d.ts +30 -0
  38. package/dist/flavors/bipm/index.js +434 -0
  39. package/dist/flavors/bsi/grammar.d.ts +4 -0
  40. package/dist/flavors/bsi/grammar.js +535 -0
  41. package/dist/flavors/bsi/index.d.ts +4 -0
  42. package/dist/flavors/bsi/index.js +941 -0
  43. package/dist/flavors/bsi/model.d.ts +127 -0
  44. package/dist/flavors/bsi/model.js +863 -0
  45. package/dist/flavors/calconnect/index.d.ts +21 -0
  46. package/dist/flavors/calconnect/index.js +113 -0
  47. package/dist/flavors/cen_cenelec/grammar.d.ts +4 -0
  48. package/dist/flavors/cen_cenelec/grammar.js +107 -0
  49. package/dist/flavors/cen_cenelec/index.d.ts +5 -0
  50. package/dist/flavors/cen_cenelec/index.js +263 -0
  51. package/dist/flavors/cen_cenelec/model.d.ts +45 -0
  52. package/dist/flavors/cen_cenelec/model.js +490 -0
  53. package/dist/flavors/cie/grammar.d.ts +5 -0
  54. package/dist/flavors/cie/grammar.js +127 -0
  55. package/dist/flavors/cie/index.d.ts +2 -0
  56. package/dist/flavors/cie/index.js +311 -0
  57. package/dist/flavors/cie/model.d.ts +122 -0
  58. package/dist/flavors/cie/model.js +464 -0
  59. package/dist/flavors/csa/grammar.d.ts +6 -0
  60. package/dist/flavors/csa/grammar.js +239 -0
  61. package/dist/flavors/csa/index.d.ts +2 -0
  62. package/dist/flavors/csa/index.js +634 -0
  63. package/dist/flavors/csa/model.d.ts +33 -0
  64. package/dist/flavors/csa/model.js +505 -0
  65. package/dist/flavors/doi/index.d.ts +13 -0
  66. package/dist/flavors/doi/index.js +65 -0
  67. package/dist/flavors/easc/index.d.ts +4 -0
  68. package/dist/flavors/easc/index.js +107 -0
  69. package/dist/flavors/ecma/index.d.ts +4 -0
  70. package/dist/flavors/ecma/index.js +117 -0
  71. package/dist/flavors/etsi/index.d.ts +14 -0
  72. package/dist/flavors/etsi/index.js +307 -0
  73. package/dist/flavors/evs/grammar.d.ts +2 -0
  74. package/dist/flavors/evs/grammar.js +22 -0
  75. package/dist/flavors/evs/index.d.ts +2 -0
  76. package/dist/flavors/evs/index.js +24 -0
  77. package/dist/flavors/evs/model.d.ts +19 -0
  78. package/dist/flavors/evs/model.js +36 -0
  79. package/dist/flavors/gb/index.d.ts +24 -0
  80. package/dist/flavors/gb/index.js +111 -0
  81. package/dist/flavors/gost/grammar.d.ts +2 -0
  82. package/dist/flavors/gost/grammar.js +46 -0
  83. package/dist/flavors/gost/index.d.ts +2 -0
  84. package/dist/flavors/gost/index.js +165 -0
  85. package/dist/flavors/gost/model.d.ts +61 -0
  86. package/dist/flavors/gost/model.js +147 -0
  87. package/dist/flavors/gost/prefix_routes.d.ts +15 -0
  88. package/dist/flavors/gost/prefix_routes.js +192 -0
  89. package/dist/flavors/iala/index.d.ts +32 -0
  90. package/dist/flavors/iala/index.js +300 -0
  91. package/dist/flavors/iana/index.d.ts +19 -0
  92. package/dist/flavors/iana/index.js +71 -0
  93. package/dist/flavors/idf/grammar.d.ts +2 -0
  94. package/dist/flavors/idf/grammar.js +71 -0
  95. package/dist/flavors/idf/index.d.ts +2 -0
  96. package/dist/flavors/idf/index.js +100 -0
  97. package/dist/flavors/idf/model.d.ts +54 -0
  98. package/dist/flavors/idf/model.js +359 -0
  99. package/dist/flavors/iec/grammar.d.ts +3 -0
  100. package/dist/flavors/iec/grammar.js +187 -0
  101. package/dist/flavors/iec/index.d.ts +4 -0
  102. package/dist/flavors/iec/index.js +507 -0
  103. package/dist/flavors/iec/model.d.ts +54 -0
  104. package/dist/flavors/iec/model.js +437 -0
  105. package/dist/flavors/ieee/grammar.d.ts +2 -0
  106. package/dist/flavors/ieee/grammar.js +626 -0
  107. package/dist/flavors/ieee/index.d.ts +3 -0
  108. package/dist/flavors/ieee/index.js +1186 -0
  109. package/dist/flavors/ieee/model.d.ts +142 -0
  110. package/dist/flavors/ieee/model.js +1148 -0
  111. package/dist/flavors/ieee/preprocessor.d.ts +8 -0
  112. package/dist/flavors/ieee/preprocessor.js +410 -0
  113. package/dist/flavors/ieee/update_codes.d.ts +8 -0
  114. package/dist/flavors/ieee/update_codes.js +191 -0
  115. package/dist/flavors/ietf/index.d.ts +30 -0
  116. package/dist/flavors/ietf/index.js +151 -0
  117. package/dist/flavors/iho/index.d.ts +4 -0
  118. package/dist/flavors/iho/index.js +126 -0
  119. package/dist/flavors/index.d.ts +9 -0
  120. package/dist/flavors/index.js +136 -0
  121. package/dist/flavors/isbn/index.d.ts +13 -0
  122. package/dist/flavors/isbn/index.js +97 -0
  123. package/dist/flavors/iso/grammar.d.ts +2 -0
  124. package/dist/flavors/iso/grammar.js +207 -0
  125. package/dist/flavors/iso/index.d.ts +2 -0
  126. package/dist/flavors/iso/index.js +343 -0
  127. package/dist/flavors/iso/model.d.ts +159 -0
  128. package/dist/flavors/iso/model.js +802 -0
  129. package/dist/flavors/iso/stages.generated.d.ts +13 -0
  130. package/dist/flavors/iso/stages.generated.js +391 -0
  131. package/dist/flavors/itu/grammar.d.ts +4 -0
  132. package/dist/flavors/itu/grammar.js +205 -0
  133. package/dist/flavors/itu/index.d.ts +2 -0
  134. package/dist/flavors/itu/index.js +224 -0
  135. package/dist/flavors/itu/model.d.ts +184 -0
  136. package/dist/flavors/itu/model.js +599 -0
  137. package/dist/flavors/jcgm/index.d.ts +7 -0
  138. package/dist/flavors/jcgm/index.js +300 -0
  139. package/dist/flavors/jis/index.d.ts +4 -0
  140. package/dist/flavors/jis/index.js +267 -0
  141. package/dist/flavors/nist/grammar.d.ts +2 -0
  142. package/dist/flavors/nist/grammar.js +287 -0
  143. package/dist/flavors/nist/index.d.ts +2 -0
  144. package/dist/flavors/nist/index.js +1244 -0
  145. package/dist/flavors/nist/model.d.ts +155 -0
  146. package/dist/flavors/nist/model.js +823 -0
  147. package/dist/flavors/nist/preprocessor.d.ts +11 -0
  148. package/dist/flavors/nist/preprocessor.js +281 -0
  149. package/dist/flavors/oasis/index.d.ts +23 -0
  150. package/dist/flavors/oasis/index.js +110 -0
  151. package/dist/flavors/ogc/index.d.ts +20 -0
  152. package/dist/flavors/ogc/index.js +75 -0
  153. package/dist/flavors/oiml/grammar.d.ts +2 -0
  154. package/dist/flavors/oiml/grammar.js +180 -0
  155. package/dist/flavors/oiml/implementation.d.ts +9 -0
  156. package/dist/flavors/oiml/implementation.js +16 -0
  157. package/dist/flavors/oiml/model.d.ts +4 -0
  158. package/dist/flavors/oiml/model.js +462 -0
  159. package/dist/flavors/omg/index.d.ts +14 -0
  160. package/dist/flavors/omg/index.js +73 -0
  161. package/dist/flavors/plateau/index.d.ts +4 -0
  162. package/dist/flavors/plateau/index.js +97 -0
  163. package/dist/flavors/tgpp/index.d.ts +4 -0
  164. package/dist/flavors/tgpp/index.js +122 -0
  165. package/dist/flavors/un/index.d.ts +13 -0
  166. package/dist/flavors/un/index.js +90 -0
  167. package/dist/flavors/w3c/index.d.ts +14 -0
  168. package/dist/flavors/w3c/index.js +148 -0
  169. package/dist/flavors/xsf/index.d.ts +18 -0
  170. package/dist/flavors/xsf/index.js +65 -0
  171. package/dist/grammar/engine.d.ts +78 -0
  172. package/dist/grammar/engine.js +351 -0
  173. package/dist/model/attribute.d.ts +54 -0
  174. package/dist/model/attribute.js +53 -0
  175. package/dist/model/builder.d.ts +39 -0
  176. package/dist/model/builder.js +124 -0
  177. package/dist/model/component.d.ts +70 -0
  178. package/dist/model/component.js +170 -0
  179. package/dist/model/identifier.d.ts +83 -0
  180. package/dist/model/identifier.js +270 -0
  181. package/dist/model/urn-generator.d.ts +27 -0
  182. package/dist/model/urn-generator.js +91 -0
  183. package/package.json +1 -1
@@ -0,0 +1,941 @@
1
+ import { parseGrammar } from "../../grammar/engine.js";
2
+ import { grammarImplementation } from "../index.js";
3
+ import { bsiGrammar, preprocessBsi } from "./grammar.js";
4
+ import { PubidDate, Publisher } from "../../model/component.js";
5
+ import { AdoptedEuropeanNormClass as CenAdoptedEuropeanNormClass } from "../cen_cenelec/model.js";
6
+ import { AddendumDocumentClass, AdoptedEuropeanNormClass, AdoptedInternationalStandardClass, AerospaceStandardClass, Amendment, BsiCode, BsiIdentifier, BundledIdentifierClass, BritishStandardClass, CommitteeDocumentClass, ConsolidatedIdentifierClass, Corrigendum, DetailedSpecificationClass, DiscClass, ExpertCommentaryClass, ExplanatorySupplementClass, FlexClass, IndexClass, MethodClass, NationalAnnexClass, SectionClass, SetClass, StandaloneAmendmentClass, SupplementDocumentClass, SupplementaryIndexClass, TestMethodClass, ValueAddedPublicationClass, locateStage, DEFAULT_TYPED_STAGE, TYPE_CLASSES, } from "./model.js";
7
+ /**
8
+ * Port of lib/pubid/bsi/builder.rb — dispatch, attribute extraction,
9
+ * the specialized build paths, the adopted-identifier routing, and the
10
+ * supplement/consolidated wrapping.
11
+ */
12
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13
+ // Parse-tree numbers arrive as {number: "1000"} (the rule's own .as).
14
+ const numv = (v) => {
15
+ if (isObj(v))
16
+ return strv(v["number"]);
17
+ return strv(v);
18
+ };
19
+ const strv = (v) => {
20
+ if (v === undefined || v === null)
21
+ return undefined;
22
+ if (Array.isArray(v)) {
23
+ if (v.length === 0)
24
+ return undefined;
25
+ const joined = v.flat(Infinity).map(String).join("");
26
+ return joined.length > 0 ? joined : undefined;
27
+ }
28
+ const s = String(v).trim();
29
+ return s.length > 0 ? s : undefined;
30
+ };
31
+ function flatten(data) {
32
+ return Array.isArray(data) ? Object.assign({}, ...data) : data;
33
+ }
34
+ const ctor = (klass) => klass;
35
+ function pub(body) {
36
+ return new Publisher({ body });
37
+ }
38
+ // The adopted routing mirrors the gem: a routed branch that fails to
39
+ // parse RAISES through (build_adopted_identifier calls the foreign
40
+ // parser directly), which is what rejects e.g. "BS HD 629.1 S3:2019".
41
+ function tryForeign(flavor, raw) {
42
+ const impl = grammarImplementation(flavor);
43
+ if (impl === undefined)
44
+ return undefined;
45
+ return impl.parse(raw);
46
+ }
47
+ function extractSupplementsFromArray(supps) {
48
+ return supps.map((s) => {
49
+ const supp = isObj(s) && s["supplement"] !== undefined ? s["supplement"] : (isObj(s) ? s : {});
50
+ const separator = supp["amd_sep_slash"] !== undefined ? "/" : "+";
51
+ return {
52
+ type: supp["amd_number"] !== undefined ? "amendment" : "corrigendum",
53
+ number: strv(supp["amd_number"] ?? supp["cor_number"]),
54
+ year: strv(supp["amd_year"] ?? supp["cor_year"]),
55
+ separator,
56
+ amd_suffix_form: supp["amd_sep_plus"] === undefined && supp["amd_sep_slash"] === undefined,
57
+ };
58
+ });
59
+ }
60
+ function extractSupplements(data) {
61
+ const raw = data["supplements"];
62
+ if (raw === undefined)
63
+ return [];
64
+ const arr = (Array.isArray(raw) ? raw.flat(Infinity) : [raw])
65
+ .filter((x) => isObj(x) || (Array.isArray(x) && x.length > 0));
66
+ if (arr.length === 0)
67
+ return [];
68
+ return extractSupplementsFromArray(arr);
69
+ }
70
+ function expandYear(yearVal) {
71
+ if (yearVal === undefined)
72
+ return undefined;
73
+ if (yearVal.length === 2)
74
+ return String(2000 + Number(yearVal));
75
+ return yearVal;
76
+ }
77
+ function wrapWithConsolidated(base, supplementsData) {
78
+ const supplementIds = supplementsData.map((supp) => {
79
+ const yearVal = expandYear(supp.year);
80
+ if (supp.type === "amendment") {
81
+ return new Amendment({
82
+ base,
83
+ number: supp.number,
84
+ year: yearVal,
85
+ separator: supp.separator,
86
+ amd_suffix_form: supp.amd_suffix_form ? true : false,
87
+ });
88
+ }
89
+ return new Corrigendum({
90
+ base,
91
+ number: supp.number,
92
+ year: yearVal,
93
+ separator: supp.separator,
94
+ });
95
+ });
96
+ const wrapper = new (ctor(ConsolidatedIdentifierClass))({
97
+ identifiers: [base, ...supplementIds],
98
+ });
99
+ // The gem serializes the FIRST member's typed stage onto the
100
+ // consolidated wrapper itself.
101
+ wrapper["typedStage"] =
102
+ base["typedStage"];
103
+ return wrapper;
104
+ }
105
+ function wrapWithExpertCommentary(baseId, original) {
106
+ // The grammar nests the topic/full flags under an "expert_commentary"
107
+ // subtree.
108
+ const ec = isObj(original["expert_commentary"]) ? original["expert_commentary"] : {};
109
+ const flags = { ...ec, ...original };
110
+ let format = "abbr";
111
+ if (flags["expert_commentary_full"] !== undefined)
112
+ format = "full";
113
+ else if (flags["expert_commentary_topic"] !== undefined)
114
+ format = "abbr_with_topic";
115
+ const topic = strv(flags["expert_commentary_topic"]);
116
+ return new (ctor(ExpertCommentaryClass))({ base: baseId, format, topic });
117
+ }
118
+ // --- Attribute extraction ------------------------------------------------------------
119
+ function partsToAttrs(partsVal) {
120
+ const arr = Array.isArray(partsVal) ? partsVal.flat(Infinity) : partsVal !== undefined ? [partsVal] : [];
121
+ if (arr.length === 0)
122
+ return {};
123
+ const first = isObj(arr[0]) ? arr[0] : undefined;
124
+ if (first === undefined)
125
+ return {};
126
+ const partStr = strv(first["part"]) ?? "";
127
+ if (first["subpart"] !== undefined) {
128
+ return { part: partStr, subpart: strv(first["subpart"]) };
129
+ }
130
+ const components = partStr.split("-");
131
+ if (components.length > 1) {
132
+ return { part: components[0], subpart: components[1] };
133
+ }
134
+ return { part: partStr };
135
+ }
136
+ function locateKlass(parsedHash) {
137
+ if (parsedHash["flex_type"] !== undefined)
138
+ return FlexClass;
139
+ if (parsedHash["na_prefix"] !== undefined)
140
+ return NationalAnnexClass;
141
+ if (parsedHash["prefix"] !== undefined)
142
+ return AerospaceStandardClass;
143
+ const adopted = strv(parsedHash["adopted_string"]);
144
+ if (adopted !== undefined && /^EN\s+\d/.test(adopted))
145
+ return AdoptedEuropeanNormClass;
146
+ if (adopted !== undefined)
147
+ return AdoptedInternationalStandardClass;
148
+ const typeStr = strv(parsedHash["type"]) ?? strv(parsedHash["stage"]) ?? "";
149
+ // Unknown abbreviations fall back to the DEFAULT_TYPED_STAGE (a
150
+ // published British Standard).
151
+ const stage = locateStage(typeStr.toUpperCase()) ?? DEFAULT_TYPED_STAGE;
152
+ return TYPE_CLASSES[stage.typeCode] ?? BritishStandardClass;
153
+ }
154
+ /** The registry entry the parse selected (stashed on the instance;
155
+ * the wire serializes it for non-default typed stages). */
156
+ function selectedStage(parsedHash) {
157
+ const typeStr = strv(parsedHash["type"]) ?? strv(parsedHash["stage"]) ?? "";
158
+ return locateStage(typeStr.toUpperCase()) ?? DEFAULT_TYPED_STAGE;
159
+ }
160
+ function assignAttributes(data) {
161
+ const attrs = {};
162
+ if (data["publisher"] !== undefined)
163
+ attrs["publisher"] = pub(strv(data["publisher"]) ?? "BS");
164
+ if (data["prefix"] !== undefined)
165
+ attrs["prefix"] = strv(data["prefix"]);
166
+ if (data["flex_prefix"] !== undefined)
167
+ attrs["flex_prefix"] = strv(data["flex_prefix"]);
168
+ if (data["number"] !== undefined)
169
+ attrs["number"] = strv(data["number"]);
170
+ if (data["iteration"] !== undefined) {
171
+ const iterationObj = isObj(data["iteration"]) ? data["iteration"] : undefined;
172
+ const iterationVal = iterationObj !== undefined ? strv(iterationObj["iteration"]) : strv(data["iteration"]);
173
+ if (iterationVal !== undefined && iterationVal !== "")
174
+ attrs["iteration"] = iterationVal;
175
+ }
176
+ const partsAttr = partsToAttrs(data["parts"]);
177
+ if (partsAttr.part !== undefined)
178
+ attrs["part"] = partsAttr.part;
179
+ if (partsAttr.subpart !== undefined)
180
+ attrs["subpart"] = partsAttr.subpart;
181
+ if (data["year"] !== undefined) {
182
+ attrs["date"] = new PubidDate({ year: String(Number(strv(data["year"]))) });
183
+ }
184
+ if (data["month"] !== undefined)
185
+ attrs["month"] = Number(strv(data["month"]));
186
+ if (data["edition"] !== undefined)
187
+ attrs["edition"] = strv(data["edition"]);
188
+ if (data["second_number"] !== undefined) {
189
+ attrs["second_number"] = new BsiCode({ value: strv(data["second_number"]) ?? "" });
190
+ }
191
+ if (data["translation_lang"] !== undefined) {
192
+ const lang = strv(data["translation_lang"]) ?? "";
193
+ attrs["translation_lang"] = lang.charAt(0).toUpperCase() + lang.slice(1).toLowerCase();
194
+ }
195
+ if (data["translation_upper"] !== undefined) {
196
+ const lang = strv(data["translation_upper"]) ?? "";
197
+ attrs["translation_upper"] = lang.charAt(0).toUpperCase() + lang.slice(1).toLowerCase();
198
+ }
199
+ return attrs;
200
+ }
201
+ // --- The builder ---------------------------------------------------------------------
202
+ export function buildBsiIdentifier(tree) {
203
+ return buildTree(flatten(tree));
204
+ }
205
+ function buildTree(data) {
206
+ const original = { ...data };
207
+ if (data["standalone_amendment"] !== undefined || data["parenthesized_amd"] !== undefined) {
208
+ return buildStandaloneAmendment(data);
209
+ }
210
+ if (data["committee_document"] !== undefined) {
211
+ return buildCommitteeDocument(flatten(data["committee_document"]));
212
+ }
213
+ if (data["index_identifier"] !== undefined) {
214
+ return buildIndex(flatten(data["index_identifier"]));
215
+ }
216
+ if (data["supplementary_index_identifier"] !== undefined) {
217
+ return buildSupplementaryIndex(flatten(data["supplementary_index_identifier"]));
218
+ }
219
+ if (data["explanatory_supplement_identifier"] !== undefined) {
220
+ return buildExplanatorySupplement(flatten(data["explanatory_supplement_identifier"]));
221
+ }
222
+ if (data["method_identifier"] !== undefined) {
223
+ return buildMethod(flatten(data["method_identifier"]));
224
+ }
225
+ if (data["test_method_identifier"] !== undefined) {
226
+ return buildTestMethod(flatten(data["test_method_identifier"]));
227
+ }
228
+ if (data["section_identifier"] !== undefined) {
229
+ return buildSection(flatten(data["section_identifier"]));
230
+ }
231
+ if (data["detailed_specification"] !== undefined) {
232
+ return buildDetailedSpecification(flatten(data["detailed_specification"]));
233
+ }
234
+ if (data["disc_identifier"] !== undefined) {
235
+ return buildDisc(flatten(data["disc_identifier"]));
236
+ }
237
+ if (data["aerospace_identifier"] !== undefined) {
238
+ return buildAerospaceIdentifier(flatten(data["aerospace_identifier"]));
239
+ }
240
+ if (data["supplement_document"] !== undefined) {
241
+ return buildSupplementDocument(flatten(data["supplement_document"]));
242
+ }
243
+ if (data["addendum_document"] !== undefined) {
244
+ return buildAddendumDocument(flatten(data["addendum_document"]));
245
+ }
246
+ if (data["bundled_parts"] !== undefined || data["bundled_list"] !== undefined) {
247
+ return buildBundledIdentifier(data);
248
+ }
249
+ if (data["set"] !== undefined) {
250
+ return buildSetData(data["set"]);
251
+ }
252
+ const supplementsData = extractSupplements(data);
253
+ if (data["pdf_format"] !== undefined || data["tc_format"] !== undefined || data["book_format"] !== undefined) {
254
+ return buildValueAddedPublication(data, supplementsData);
255
+ }
256
+ if (data["na_prefix"] !== undefined) {
257
+ return buildNationalAnnex(data, supplementsData);
258
+ }
259
+ if (data["adopted_string"] !== undefined) {
260
+ const adopted = buildAdoptedIdentifier(data, original);
261
+ if (adopted !== undefined)
262
+ return adopted;
263
+ }
264
+ const stageEntry = selectedStage(data);
265
+ let identifier = new (ctor(locateKlass(data)))(assignAttributes(data));
266
+ identifier["typedStage"] = stageEntry;
267
+ void identifier;
268
+ if (supplementsData.length > 0) {
269
+ identifier = wrapWithConsolidated(identifier, supplementsData);
270
+ }
271
+ if (data["expert_commentary"] !== undefined || data["expert_commentary_full"] !== undefined) {
272
+ identifier = wrapWithExpertCommentary(identifier, original);
273
+ }
274
+ return identifier;
275
+ }
276
+ // --- Specialized builders ------------------------------------------------------------
277
+ function yearDate(v) {
278
+ const s = strv(v);
279
+ if (s === undefined)
280
+ return undefined;
281
+ return new PubidDate({ year: String(Number(s)) });
282
+ }
283
+ function buildIndex(data) {
284
+ const attrs = {
285
+ number: strv(data["number"]),
286
+ };
287
+ const suffix = isObj(data["index_suffix"]) ? data["index_suffix"] : {};
288
+ let format = "space";
289
+ let issueNumber;
290
+ if (suffix["colon_sep"] !== undefined)
291
+ format = "colon";
292
+ else if (suffix["issue_number"] !== undefined)
293
+ issueNumber = strv(suffix["issue_number"]);
294
+ attrs["issue_number"] = issueNumber;
295
+ attrs["index_format"] = format;
296
+ const date = yearDate(data["year"]);
297
+ if (date !== undefined)
298
+ attrs["date"] = date;
299
+ return new (ctor(IndexClass))(attrs);
300
+ }
301
+ function buildSupplementaryIndex(data) {
302
+ const attrs = { number: strv(data["number"]) };
303
+ const date = yearDate(data["year"]);
304
+ if (date !== undefined)
305
+ attrs["date"] = date;
306
+ return new (ctor(SupplementaryIndexClass))(attrs);
307
+ }
308
+ function buildExplanatorySupplement(data) {
309
+ const attrs = { number: strv(data["number"]) };
310
+ const parts = partsToAttrs(data["parts"]);
311
+ if (parts.part !== undefined)
312
+ attrs["part"] = parts.part;
313
+ const date = yearDate(data["year"]);
314
+ if (date !== undefined)
315
+ attrs["date"] = date;
316
+ return new (ctor(ExplanatorySupplementClass))(attrs);
317
+ }
318
+ function buildMethod(data) {
319
+ const suffix = isObj(data["method_suffix"]) ? data["method_suffix"] : {};
320
+ const methodTo = strv(suffix["method_to"]);
321
+ const methodAnd = strv(suffix["method_and"]);
322
+ const attrs = {
323
+ number: strv(data["number"]),
324
+ method_code: strv(suffix["method_code"]),
325
+ method_to: methodTo,
326
+ method_and: methodAnd,
327
+ is_plural: methodTo !== undefined || methodAnd !== undefined,
328
+ };
329
+ const parts = partsToAttrs(data["parts"]);
330
+ if (parts.part !== undefined)
331
+ attrs["part"] = parts.part;
332
+ const date = yearDate(data["year"]);
333
+ if (date !== undefined)
334
+ attrs["date"] = date;
335
+ return new (ctor(MethodClass))(attrs);
336
+ }
337
+ function buildTestMethod(data) {
338
+ const suffix = isObj(data["test_method_suffix"]) ? data["test_method_suffix"] : {};
339
+ const attrs = {
340
+ number: strv(data["number"]),
341
+ test_series: strv(suffix["test_series"]),
342
+ test_id: strv(suffix["test_id"]),
343
+ };
344
+ if (data["publisher"] !== undefined)
345
+ attrs["publisher"] = pub(strv(data["publisher"]) ?? "BS");
346
+ const date = yearDate(data["year"]);
347
+ if (date !== undefined)
348
+ attrs["date"] = date;
349
+ return new (ctor(TestMethodClass))(attrs);
350
+ }
351
+ function buildSection(data) {
352
+ const suffix = isObj(data["section_suffix"]) ? data["section_suffix"] : {};
353
+ const publisherVal = strv(data["publisher"]) ?? strv(data["type"]);
354
+ const attrs = {
355
+ number: strv(data["number"]),
356
+ section_id: strv(suffix["section_id"]),
357
+ section_format: suffix["colon_sep"] !== undefined ? "colon" : "space",
358
+ };
359
+ if (publisherVal !== undefined)
360
+ attrs["publisher"] = pub(publisherVal);
361
+ const date = yearDate(data["year"]);
362
+ if (date !== undefined)
363
+ attrs["date"] = date;
364
+ return new (ctor(SectionClass))(attrs);
365
+ }
366
+ function buildDetailedSpecification(data) {
367
+ const suffix = isObj(data["detailed_spec_suffix"]) ? data["detailed_spec_suffix"] : {};
368
+ const attrs = { number: strv(data["number"]) };
369
+ const specCode = strv(suffix["spec_code"]);
370
+ if (specCode !== undefined)
371
+ attrs["spec_code"] = new BsiCode({ value: specCode });
372
+ if (data["publisher"] !== undefined)
373
+ attrs["publisher"] = pub(strv(data["publisher"]) ?? "BS");
374
+ const date = yearDate(data["year"]);
375
+ if (date !== undefined)
376
+ attrs["date"] = date;
377
+ return new (ctor(DetailedSpecificationClass))(attrs);
378
+ }
379
+ function buildDisc(data) {
380
+ const attrs = { number: strv(data["number"]) };
381
+ const parts = partsToAttrs(data["parts"]);
382
+ if (parts.part !== undefined)
383
+ attrs["part"] = parts.part;
384
+ attrs["publisher"] = pub("DISC");
385
+ const date = yearDate(data["year"]);
386
+ if (date !== undefined)
387
+ attrs["date"] = date;
388
+ return new (ctor(DiscClass))(attrs);
389
+ }
390
+ function buildAerospaceIdentifier(data) {
391
+ const prefixRaw = data["prefix"];
392
+ const prefixVal = isObj(prefixRaw) ? strv(prefixRaw["prefix"]) : strv(prefixRaw);
393
+ const attrs = {
394
+ prefix: prefixVal,
395
+ number: numv(data["number"]),
396
+ publisher: pub("BS"),
397
+ };
398
+ // The part captures land flat at the aerospace_identifier level when
399
+ // the engine flattens embedded subrule labels (data[:part], with an
400
+ // optional sibling letter_edition).
401
+ if (data["part"] !== undefined) {
402
+ const partDirect = isObj(data["part"])
403
+ ? strv(data["part"]["part"])
404
+ : strv(data["part"]);
405
+ if (partDirect !== undefined)
406
+ attrs["part"] = partDirect;
407
+ const partLetter = isObj(data["part"])
408
+ ? strv(data["part"]["letter_edition"])
409
+ : undefined;
410
+ if (partLetter === undefined && data["letter_edition"] !== undefined) {
411
+ attrs["edition"] = isObj(data["letter_edition"])
412
+ ? strv(data["letter_edition"]["letter_edition"])
413
+ : strv(data["letter_edition"]);
414
+ }
415
+ if (partLetter !== undefined)
416
+ attrs["edition"] = partLetter;
417
+ }
418
+ const letterEditionPart = isObj(data["part_with_letter_edition"])
419
+ ? data["part_with_letter_edition"]
420
+ : undefined;
421
+ if (letterEditionPart !== undefined) {
422
+ const partVal = strv(letterEditionPart["part"]);
423
+ if (partVal !== undefined)
424
+ attrs["part"] = partVal;
425
+ const letterEdition = strv(letterEditionPart["letter_edition"]);
426
+ if (letterEdition !== undefined)
427
+ attrs["edition"] = letterEdition;
428
+ }
429
+ else {
430
+ const parts = partsToAttrs(data["parts"]);
431
+ if (parts.part !== undefined)
432
+ attrs["part"] = parts.part;
433
+ if (parts.subpart !== undefined)
434
+ attrs["subpart"] = parts.subpart;
435
+ }
436
+ if (attrs["edition"] === undefined && data["letter_edition"] !== undefined) {
437
+ const editionVal = isObj(data["letter_edition"])
438
+ ? strv(data["letter_edition"]["letter_edition"])
439
+ : strv(data["letter_edition"]);
440
+ if (editionVal !== undefined)
441
+ attrs["edition"] = editionVal;
442
+ }
443
+ const date = yearDate(data["year"]);
444
+ if (date !== undefined)
445
+ attrs["date"] = date;
446
+ return new (ctor(AerospaceStandardClass))(attrs);
447
+ }
448
+ function buildStandaloneAmendment(data) {
449
+ const amdRaw = data["standalone_amendment"] ?? data["parenthesized_amd"];
450
+ const amdData = flatten(amdRaw);
451
+ return new (ctor(StandaloneAmendmentClass))({
452
+ number: strv(amdData["amendment_number"]),
453
+ corrigendum: amdData["corrigendum"] !== undefined,
454
+ parenthesized: data["parenthesized_amd"] !== undefined,
455
+ });
456
+ }
457
+ function buildCommitteeDocument(data) {
458
+ const yearVal = strv(data["year"]);
459
+ const fullYear = yearVal !== undefined ? `20${yearVal}` : undefined;
460
+ const attrs = {
461
+ number: strv(data["document_number"]),
462
+ };
463
+ if (fullYear !== undefined)
464
+ attrs["date"] = new PubidDate({ year: String(Number(fullYear)) });
465
+ return new (ctor(CommitteeDocumentClass))(attrs);
466
+ }
467
+ function buildValueAddedPublication(data, _supplements) {
468
+ const format = data["pdf_format"] !== undefined
469
+ ? "PDF"
470
+ : data["tc_format"] !== undefined
471
+ ? "TC"
472
+ : data["book_format"] !== undefined
473
+ ? "BOOK"
474
+ : undefined;
475
+ const baseData = { ...data };
476
+ delete baseData["pdf_format"];
477
+ delete baseData["tc_format"];
478
+ delete baseData["book_format"];
479
+ const baseId = buildTree(baseData);
480
+ return new (ctor(ValueAddedPublicationClass))({
481
+ base: baseId,
482
+ format,
483
+ });
484
+ }
485
+ function buildNationalAnnex(data, _supplements) {
486
+ const baseData = { ...data };
487
+ delete baseData["na_prefix"];
488
+ delete baseData["na_supplements"];
489
+ const baseId = buildTree(baseData);
490
+ const naPrefix = isObj(data["na_prefix"]) ? data["na_prefix"] : undefined;
491
+ const naSuppRaw = naPrefix !== undefined && naPrefix["na_supplements"] !== undefined
492
+ ? naPrefix["na_supplements"]
493
+ : data["na_supplements"];
494
+ const naSuppData = naSuppRaw !== undefined
495
+ ? extractSupplementsFromArray(Array.isArray(naSuppRaw) ? naSuppRaw.flat(Infinity) : [naSuppRaw])
496
+ : [];
497
+ const naSupps = naSuppData.map((supp) => {
498
+ const yearVal = expandYear(supp.year);
499
+ if (supp.type === "amendment") {
500
+ return new Amendment({
501
+ number: supp.number,
502
+ year: yearVal,
503
+ separator: supp.separator,
504
+ });
505
+ }
506
+ return new Corrigendum({
507
+ number: supp.number,
508
+ year: yearVal,
509
+ separator: supp.separator,
510
+ });
511
+ });
512
+ return new (ctor(NationalAnnexClass))({
513
+ na_supplements: naSupps,
514
+ base: baseId,
515
+ });
516
+ }
517
+ function iterationOf(v) {
518
+ if (v === undefined)
519
+ return undefined;
520
+ const flat = (Array.isArray(v) ? v.flat(Infinity) : [v]);
521
+ for (const item of flat) {
522
+ if (isObj(item)) {
523
+ const raw = item["iteration"];
524
+ const inner = Array.isArray(raw)
525
+ ? iterationOf(raw)
526
+ : strv(raw);
527
+ if (inner !== undefined && inner !== "")
528
+ return inner;
529
+ }
530
+ }
531
+ return undefined;
532
+ }
533
+ function buildSupplementDocument(data) {
534
+ const publisherVal = strv(data["publisher"]);
535
+ // The grammar nests the parts under a "parts" key inside the parts
536
+ // subtree; unwrap before extracting part/subpart.
537
+ const suppPartsRaw = data["parts"];
538
+ const suppPartsVal = isObj(suppPartsRaw) && suppPartsRaw["parts"] !== undefined
539
+ ? suppPartsRaw["parts"]
540
+ : suppPartsRaw;
541
+ const baseData = {
542
+ ...(publisherVal !== undefined ? { publisher: publisherVal } : {}),
543
+ number: numv(data["number"]),
544
+ ...(iterationOf(data["iteration"]) !== undefined ? { iteration: iterationOf(data["iteration"]) } : {}),
545
+ ...(suppPartsVal !== undefined ? { parts: suppPartsVal } : {}),
546
+ ...(data["flex_prefix"] !== undefined ? { flex_prefix: strv(data["flex_prefix"]) } : {}),
547
+ };
548
+ const reverseFormat = data["supplement_number"] !== undefined
549
+ && data["supplement_year"] !== undefined
550
+ && data["publisher"] !== undefined
551
+ && data["number"] !== undefined
552
+ && data["base_year"] !== undefined;
553
+ if (reverseFormat) {
554
+ baseData["year"] = data["base_year"];
555
+ }
556
+ else if (data["year"] !== undefined) {
557
+ baseData["year"] = data["year"];
558
+ }
559
+ const baseId = buildTree(baseData);
560
+ const suppType = strv(data["supp_no_prefix"]) === "No."
561
+ ? "No."
562
+ : strv(data["supp_no_prefix"]) !== undefined
563
+ ? strv(data["supp_no_prefix"])
564
+ : "";
565
+ return new (ctor(SupplementDocumentClass))({
566
+ base: baseId,
567
+ supplement_number: strv(data["supplement_number"]),
568
+ supplement_year: data["supplement_year"] !== undefined ? Number(strv(data["supplement_year"])) : undefined,
569
+ supplement_type: suppType,
570
+ reverse_format: reverseFormat,
571
+ separator: strv(data["supp_sep"]) ?? ":",
572
+ });
573
+ }
574
+ function buildAddendumDocument(data) {
575
+ const publisherVal = strv(data["publisher"]);
576
+ // The grammar nests the parts under a "parts" key inside the
577
+ // "parts" subtree; unwrap before extracting part/subpart.
578
+ const partsRaw = data["parts"];
579
+ const partsVal = isObj(partsRaw) && partsRaw["parts"] !== undefined
580
+ ? partsRaw["parts"]
581
+ : partsRaw;
582
+ const baseData = {
583
+ ...(publisherVal !== undefined ? { publisher: publisherVal } : {}),
584
+ number: numv(data["number"]),
585
+ ...(iterationOf(data["iteration"]) !== undefined ? { iteration: iterationOf(data["iteration"]) } : {}),
586
+ ...(partsVal !== undefined ? { parts: partsVal } : {}),
587
+ ...(data["flex_prefix"] !== undefined ? { flex_prefix: strv(data["flex_prefix"]) } : {}),
588
+ };
589
+ if (data["base_year"] !== undefined)
590
+ baseData["year"] = data["base_year"];
591
+ const baseId = buildTree(baseData);
592
+ const addType = strv(data["add_no_prefix"]) === "No."
593
+ ? "No."
594
+ : strv(data["add_no_prefix"]) !== undefined
595
+ ? strv(data["add_no_prefix"])
596
+ : "";
597
+ // strv() trims, so a captured space separator must be read raw.
598
+ const sepRaw = data["add_sep"];
599
+ const separator = sepRaw === " " ? " " : strv(sepRaw) ?? ":";
600
+ return new (ctor(AddendumDocumentClass))({
601
+ base: baseId,
602
+ addendum_number: strv(data["addendum_number"]),
603
+ addendum_year: data["addendum_year"] !== undefined ? Number(strv(data["addendum_year"])) : undefined,
604
+ addendum_type: addType,
605
+ separator,
606
+ });
607
+ }
608
+ function buildBundleItem(itemData, defaultPublisher, defaultPrefix) {
609
+ if (isObj(itemData)) {
610
+ const d = itemData;
611
+ const hasExplicitPublisher = d["publisher"] !== undefined;
612
+ const hasExplicitPrefix = d["prefix"] !== undefined;
613
+ const publisherVal = strv(d["publisher"]) ?? defaultPublisher;
614
+ const prefixVal = strv(d["prefix"]) ?? defaultPrefix;
615
+ const id = new BsiIdentifier({
616
+ ...(publisherVal !== undefined ? { publisher: pub(publisherVal) } : {}),
617
+ ...(prefixVal !== undefined && prefixVal !== "" ? { prefix: prefixVal } : {}),
618
+ ...(strv(d["number"]) !== undefined ? { number: strv(d["number"]) } : {}),
619
+ });
620
+ const self = id;
621
+ self["explicit_prefix"] = hasExplicitPublisher || hasExplicitPrefix;
622
+ self["explicit_publisher"] = hasExplicitPublisher;
623
+ const partsVal = d["parts"];
624
+ if (isObj(partsVal) && Array.isArray(partsVal["parts"])) {
625
+ const partsArray = partsVal["parts"];
626
+ const partStr = strv(partsArray[0]?.["part"]);
627
+ if (partStr !== undefined)
628
+ self["part"] = partStr;
629
+ }
630
+ else if (d["part"] !== undefined) {
631
+ self["part"] = strv(d["part"]);
632
+ self["space_separated_part"] = true;
633
+ }
634
+ return id;
635
+ }
636
+ // Simple string (alphanumeric like "N001")
637
+ return new BsiIdentifier({ number: strv(itemData) });
638
+ }
639
+ function buildBundledIdentifier(data) {
640
+ if (data["bundled_parts"] !== undefined) {
641
+ const partsData = flatten(data["bundled_parts"]);
642
+ const baseNumber = strv(partsData["number"]) ?? "";
643
+ const bundleType = strv(partsData["bundle_type"]) ?? "";
644
+ const part1 = strv(partsData["part1"]) ?? "";
645
+ const part2 = strv(partsData["part2"]) ?? "";
646
+ const yearVal = strv(partsData["year"]);
647
+ const baseId = new BsiIdentifier({ publisher: pub("BS"), number: baseNumber });
648
+ const id1 = new BsiIdentifier({ publisher: pub("BS"), number: baseNumber, part: part1 });
649
+ const id2 = new BsiIdentifier({ publisher: pub("BS"), number: baseNumber, part: part2 });
650
+ return new (ctor(BundledIdentifierClass))({
651
+ identifiers: [baseId, id1, id2],
652
+ bundle_type: bundleType,
653
+ common_year: yearVal !== undefined ? new PubidDate({ year: String(Number(yearVal)) }) : undefined,
654
+ });
655
+ }
656
+ const listArray = (Array.isArray(data["bundled_list"]) ? data["bundled_list"] : [data["bundled_list"]]);
657
+ const firstElem = flatten(listArray[0]);
658
+ const publisherVal = strv(firstElem["publisher"]) ?? "BS";
659
+ const prefixVal = strv(firstElem["prefix"]);
660
+ const items = [];
661
+ const separators = [];
662
+ if (firstElem["bundle_item"] !== undefined) {
663
+ items.push(buildBundleItem(firstElem["bundle_item"], publisherVal, prefixVal));
664
+ }
665
+ for (const rawElem of listArray.slice(1)) {
666
+ const elem = flatten(rawElem);
667
+ if (elem["year"] !== undefined)
668
+ continue;
669
+ if (elem["bundle_item"] !== undefined) {
670
+ let sep = " and ";
671
+ if (elem["sep_and"] !== undefined)
672
+ sep = " and ";
673
+ else if (elem["sep_to"] !== undefined) {
674
+ const toCase = strv(elem["sep_to"]?.["to_case"]);
675
+ sep = toCase === "TO" ? " TO " : " to ";
676
+ }
677
+ else if (elem["sep_ampersand"] !== undefined)
678
+ sep = " & ";
679
+ else if (elem["sep_semicolon"] !== undefined)
680
+ sep = "; ";
681
+ else if (elem["sep_comma"] !== undefined)
682
+ sep = ",";
683
+ separators.push(sep);
684
+ items.push(buildBundleItem(elem["bundle_item"], publisherVal, prefixVal));
685
+ }
686
+ }
687
+ const yearElem = listArray.map((e) => flatten(e)).find((e) => e["year"] !== undefined);
688
+ const yearVal = yearElem !== undefined ? strv(yearElem["year"]) : undefined;
689
+ return new (ctor(BundledIdentifierClass))({
690
+ identifiers: items,
691
+ separators,
692
+ common_year: yearVal !== undefined ? new PubidDate({ year: String(Number(yearVal)) }) : undefined,
693
+ });
694
+ }
695
+ function buildSetData(setRaw) {
696
+ const itemsArray = Array.isArray(setRaw) ? setRaw.flat(Infinity) : [setRaw];
697
+ const identifiers = [];
698
+ for (const item of itemsArray) {
699
+ const itemData = isObj(item) && item["set_item"] !== undefined
700
+ ? (flatten(item["set_item"]))
701
+ : (isObj(item) ? item : {});
702
+ let idStr = "";
703
+ if (itemData["publisher"] !== undefined)
704
+ idStr += `${strv(itemData["publisher"])} `;
705
+ if (itemData["adopted_org"] !== undefined)
706
+ idStr += `${strv(itemData["adopted_org"])} `;
707
+ const numberVal = isObj(itemData["number"])
708
+ ? strv(itemData["number"]["number"])
709
+ : strv(itemData["number"]);
710
+ if (numberVal !== undefined)
711
+ idStr += numberVal;
712
+ if (isObj(itemData["parts"])) {
713
+ const partsInner = itemData["parts"]["parts"];
714
+ if (Array.isArray(partsInner) && partsInner.length > 0) {
715
+ const partVal = strv(partsInner[0]["part"]);
716
+ if (partVal !== undefined)
717
+ idStr += `-${partVal}`;
718
+ }
719
+ }
720
+ if (itemData["year"] !== undefined)
721
+ idStr += `:${strv(itemData["year"])}`;
722
+ if (idStr.trim() === "")
723
+ continue;
724
+ const parsed = parseBsi(idStr);
725
+ if (parsed !== undefined)
726
+ identifiers.push(parsed);
727
+ }
728
+ return new (ctor(SetClass))({
729
+ identifiers,
730
+ separators: Array.from({ length: Math.max(0, identifiers.length - 1) }, () => " + "),
731
+ });
732
+ }
733
+ // --- Adopted identifiers ---------------------------------------------------------------
734
+ function buildAdoptedIdentifier(dataIn, originalIn) {
735
+ const data = dataIn;
736
+ const original = originalIn;
737
+ const adoptedRaw = data["adopted_string"];
738
+ let adoptedStr = isObj(adoptedRaw)
739
+ ? strv(adoptedRaw["adopted_string"]) ?? strv(adoptedRaw["adopted_string_no_expert"])
740
+ : strv(adoptedRaw);
741
+ if (adoptedStr === undefined || adoptedStr === "")
742
+ return undefined;
743
+ adoptedStr = adoptedStr.trim();
744
+ const isBare = data["publisher"] === undefined && data["type"] === undefined
745
+ && data["na_prefix"] === undefined && data["flex_type"] === undefined && data["stage"] === undefined;
746
+ // Extract Expert Commentary suffixes (case-insensitive).
747
+ const setExComm = (topic) => {
748
+ data["expert_commentary_full"] = topic === undefined ? "Expert Commentary" : undefined;
749
+ if (topic === undefined && data["expert_commentary"] === undefined)
750
+ data["expert_commentary"] = true;
751
+ if (topic !== undefined) {
752
+ data["expert_commentary_topic"] = topic;
753
+ if (data["expert_commentary"] === undefined)
754
+ data["expert_commentary"] = true;
755
+ }
756
+ if (topic === undefined) {
757
+ original["expert_commentary_full"] = "Expert Commentary";
758
+ if (original["expert_commentary"] === undefined)
759
+ original["expert_commentary"] = true;
760
+ }
761
+ else {
762
+ original["expert_commentary_topic"] = topic;
763
+ if (original["expert_commentary"] === undefined)
764
+ original["expert_commentary"] = true;
765
+ }
766
+ };
767
+ if (/expert commentary$/i.test(adoptedStr)) {
768
+ adoptedStr = adoptedStr.replace(/expert commentary$/i, "");
769
+ setExComm();
770
+ }
771
+ else if (/excomm \($/i.test(adoptedStr)) {
772
+ adoptedStr = adoptedStr.replace(/excomm \(.*\)$/i, "");
773
+ setExComm();
774
+ }
775
+ else if (/excomm \(/i.test(adoptedStr)) {
776
+ const topicMatch = adoptedStr.match(/excomm\s*\(([^)]+)\)/i);
777
+ adoptedStr = adoptedStr.replace(/excomm\s*\(.*\)$/i, "");
778
+ setExComm(topicMatch?.[1]);
779
+ }
780
+ else if (/excomm\s*$/i.test(adoptedStr)) {
781
+ adoptedStr = adoptedStr.replace(/excomm\s*$/i, "");
782
+ if (data["expert_commentary"] === undefined)
783
+ data["expert_commentary"] = true;
784
+ if (original["expert_commentary"] === undefined)
785
+ original["expert_commentary"] = true;
786
+ }
787
+ // Extract translation suffixes.
788
+ const parenMatch = adoptedStr.match(/\s*\(([A-Za-z]+)(?:\s+(Translation|version))?\)\s*$/);
789
+ if (parenMatch !== null) {
790
+ data["translation_lang"] = parenMatch[1];
791
+ if (parenMatch[2] !== undefined)
792
+ data["translation_suffix_type"] = parenMatch[2];
793
+ adoptedStr = adoptedStr.replace(/\s*\([A-Za-z]+(?:\s+(?:Translation|version))?\)\s*$/, "");
794
+ }
795
+ else {
796
+ const upperMatch = adoptedStr.match(/\s+(SPANISH|FRENCH|GERMAN|ITALIAN)\s+TRANSLATION\s*$/);
797
+ if (upperMatch !== null) {
798
+ data["translation_upper"] = upperMatch[1];
799
+ data["translation_suffix_type"] = "Translation";
800
+ adoptedStr = adoptedStr.replace(/\s+(SPANISH|FRENCH|GERMAN|ITALIAN)\s+TRANSLATION\s*$/, "");
801
+ }
802
+ }
803
+ // Extract edition (only when NOT bare).
804
+ const editionMatch = isBare ? null : adoptedStr.match(/\s+ED(\d+)\s*$/);
805
+ const extractedEdition = editionMatch !== null ? editionMatch[1] : undefined;
806
+ if (editionMatch !== null) {
807
+ adoptedStr = adoptedStr.replace(/\s+ED\d+\s*$/, "");
808
+ }
809
+ const finalEdition = strv(data["edition"]) ?? extractedEdition;
810
+ // Extract reaffirmation "(R2004)".
811
+ const reaffirmationMatch = adoptedStr.match(/\s+\(R(\d{4})\)\s*$/);
812
+ if (reaffirmationMatch !== null) {
813
+ data["reaffirmation_year"] = reaffirmationMatch[1];
814
+ adoptedStr = adoptedStr.replace(/\s+\(R\d{4}\)\s*$/, "");
815
+ }
816
+ // Extract BSI-style supplements from the adopted string.
817
+ const extractedSupplements = [];
818
+ adoptedStr = adoptedStr.replace(/([+])(A(\d+)|AMD(\d+)|C(\d+)|COR(\d+))(?::(\d{4}))?(?:\s|$)/g, (match, sep, typeCode, _a, _amd, _c, _cor, year) => {
819
+ const suppType = typeCode.startsWith("A") ? "amendment" : "corrigendum";
820
+ const suppNumber = typeCode.startsWith("AMD")
821
+ ? typeCode.slice(3)
822
+ : typeCode.replace(/^[AC]/, "");
823
+ extractedSupplements.push({
824
+ type: suppType,
825
+ number: suppNumber,
826
+ year: year !== undefined ? year : undefined,
827
+ separator: sep,
828
+ amd_suffix_form: false,
829
+ });
830
+ return "";
831
+ }).trim();
832
+ adoptedStr = adoptedStr.replace(/\s+AMD\s*(AA|\d+)(?:\s|$)/gi, (match) => {
833
+ const amdNumber = match.trim().replace(/^AMD\s*/i, "");
834
+ extractedSupplements.push({
835
+ type: "amendment",
836
+ number: amdNumber,
837
+ year: undefined,
838
+ separator: undefined,
839
+ amd_suffix_form: true,
840
+ });
841
+ return "";
842
+ }).trim();
843
+ const bsiPrefix = strv(data["type"]) ?? strv(data["publisher"]) ?? "BS";
844
+ let adoptedId;
845
+ if (/^EN\s+(ISO\/IEC|IEC|ISO)/.test(adoptedStr)) {
846
+ const isoIecStr = adoptedStr.replace(/^EN\s+/, "");
847
+ let parsed;
848
+ if (isoIecStr.startsWith("ISO/IEC") || isoIecStr.includes("ISO/IEC")) {
849
+ parsed = tryForeign("iso", isoIecStr);
850
+ }
851
+ else if (isoIecStr.startsWith("ISO")) {
852
+ parsed = tryForeign("iso", isoIecStr);
853
+ }
854
+ else if (isoIecStr.startsWith("IEC")) {
855
+ parsed = tryForeign("iec", isoIecStr);
856
+ }
857
+ // Wrap the ISO/IEC identifier in a CEN EN adoption.
858
+ if (parsed !== undefined) {
859
+ adoptedId = new CenAdoptedEuropeanNormClass({
860
+ publisher: "EN",
861
+ adopted: parsed,
862
+ });
863
+ }
864
+ else {
865
+ adoptedId = tryForeign("cen_cenelec", adoptedStr);
866
+ }
867
+ }
868
+ else if (adoptedStr.startsWith("ISO/IEC") || adoptedStr.includes("ISO/IEC")) {
869
+ adoptedId = tryForeign("iso", adoptedStr);
870
+ }
871
+ else if (adoptedStr.startsWith("ISO")) {
872
+ adoptedId = tryForeign("iso", adoptedStr);
873
+ }
874
+ else if (adoptedStr.startsWith("IEC")) {
875
+ adoptedId = tryForeign("iec", adoptedStr);
876
+ }
877
+ else if (/^(EN|CEN|CLC|CR|ES|ENV|HD|CWA)/.test(adoptedStr)) {
878
+ adoptedId = tryForeign("cen_cenelec", adoptedStr);
879
+ }
880
+ else if (adoptedStr.startsWith("CISPR")) {
881
+ adoptedId = tryForeign("iec", adoptedStr);
882
+ }
883
+ if (isBare && adoptedId !== undefined)
884
+ return adoptedId;
885
+ if (adoptedId !== undefined) {
886
+ // AdoptedEuropeanNorm for CEN documents, else AdoptedInternational.
887
+ const adoptedWire = adoptedId.toHash();
888
+ const adoptedTypeStr = String(adoptedWire["_type"] ?? "");
889
+ const wrapperCtor = adoptedTypeStr.startsWith("pubid:cencenelec")
890
+ ? AdoptedEuropeanNormClass
891
+ : AdoptedInternationalStandardClass;
892
+ let identifier = new (ctor(wrapperCtor))({
893
+ publisher: pub(bsiPrefix),
894
+ base: adoptedId,
895
+ ...(finalEdition !== undefined ? { edition: finalEdition } : {}),
896
+ ...(strv(data["translation_lang"]) !== undefined ? { translation_lang: strv(data["translation_lang"]) } : {}),
897
+ ...(strv(data["translation_upper"]) !== undefined ? { translation_upper: strv(data["translation_upper"]) } : {}),
898
+ ...(strv(data["translation_suffix_type"]) !== undefined ? { translation_suffix_type: strv(data["translation_suffix_type"]) } : {}),
899
+ ...(strv(data["reaffirmation_year"]) !== undefined ? { reaffirmation_year: strv(data["reaffirmation_year"]) } : {}),
900
+ });
901
+ if (extractedSupplements.length > 0) {
902
+ identifier = wrapWithConsolidated(identifier, extractedSupplements);
903
+ }
904
+ if (data["expert_commentary"] !== undefined || data["expert_commentary_full"] !== undefined) {
905
+ identifier = wrapWithExpertCommentary(identifier, original);
906
+ }
907
+ return identifier;
908
+ }
909
+ return undefined;
910
+ }
911
+ // --- The parse entry -------------------------------------------------------------------
912
+ function parseBsi(input) {
913
+ const cleaned = preprocessBsi(input);
914
+ try {
915
+ const tree = parseGrammar(bsiGrammar, cleaned);
916
+ return buildBsiIdentifier(tree);
917
+ }
918
+ catch {
919
+ return undefined;
920
+ }
921
+ }
922
+ export function bsiGrammarImplementation() {
923
+ return {
924
+ parse(input) {
925
+ // Delegate to IEC for bare IEC identifiers with VAP suffixes or
926
+ // consolidated supplements (single_identifier.rb .parse).
927
+ if (/\bIEC\b/.test(input)
928
+ && (/\s+(CSV|CMV|RLV|SER|EXV|PAC|PRV)\b/.test(input)
929
+ || /\+AMD\d+:/.test(input)
930
+ || /\+COR\d+:/.test(input))) {
931
+ const iec = grammarImplementation("iec");
932
+ return iec.parse(input);
933
+ }
934
+ const parsed = parseBsi(input);
935
+ if (parsed === undefined) {
936
+ throw new Error("Failed to parse BSI identifier");
937
+ }
938
+ return parsed;
939
+ },
940
+ };
941
+ }