@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,180 @@
1
+ import { match, ref, str, } from "../../grammar/engine.js";
2
+ /**
3
+ * 1:1 port of lib/pubid/oiml/parser.rb (including CommonParseRules).
4
+ * Rule names match the Ruby rule names so the two files can be diffed
5
+ * side by side while porting.
6
+ */
7
+ function buildRules() {
8
+ const rules = {};
9
+ const rule = (name, build) => {
10
+ rules[name] = build();
11
+ };
12
+ // --- CommonParseRules ---
13
+ rule("space", () => str(" "));
14
+ rule("space?", () => ref(rules, "space").maybe());
15
+ rule("digits", () => match("\\d").repeat(1));
16
+ rule("year", () => match("\\d").repeat(4, 4).as("year"));
17
+ rule("comma", () => str(", "));
18
+ rule("comma?", () => ref(rules, "comma").maybe());
19
+ rule("comma_space", () => ref(rules, "comma").or(ref(rules, "space")));
20
+ rule("dash", () => str("-"));
21
+ rule("dot", () => str("."));
22
+ rule("words_digits", () => match("[\\dA-Za-z]").repeat(1));
23
+ rule("words", () => match("[A-Za-z]").repeat(1));
24
+ rule("words?", () => ref(rules, "words").maybe());
25
+ rule("year_digits", () => str("19").or(str("20")).then(match("\\d").repeat(2, 2), ref(rules, "digits").absent()));
26
+ rule("month_digits", () => match("\\d").repeat(2, 2));
27
+ rule("day_digits", () => match("\\d").repeat(2, 2));
28
+ rule("originator", () => ref(rules, "organization")
29
+ .as("publisher")
30
+ .then(ref(rules, "space?")
31
+ .then(str("/"), ref(rules, "organization").as("copublisher"))
32
+ .repeat(0)));
33
+ rule("comma_month_year", () => ref(rules, "comma").then(ref(rules, "words").as("month"), str(" "), ref(rules, "year_digits").as("year")));
34
+ rule("year_month", () => ref(rules, "year_digits").then(ref(rules, "dash"), ref(rules, "month_digits")));
35
+ // organization: OIML's parser never uses it (publisher is the literal
36
+ // "OIML"); provide a minimal definition so refs resolve.
37
+ rule("organization", () => str("OIML"));
38
+ // --- Additional basic rules not in CommonParseRules ---
39
+ rule("colon", () => str(":"));
40
+ rule("lparen", () => str("("));
41
+ rule("rparen", () => str(")"));
42
+ rule("slash", () => str("/"));
43
+ // Main identifier pattern - check supplements first
44
+ rule("identifier", () => ref(rules, "amendment_identifier")
45
+ .or(ref(rules, "amendment_short"))
46
+ .or(ref(rules, "annex_letter_identifier"))
47
+ .or(ref(rules, "annex_identifier"))
48
+ .or(ref(rules, "plus_supplement_identifier"))
49
+ .or(ref(rules, "trailing_supplement_identifier"))
50
+ .or(ref(rules, "bulletin_identifier"))
51
+ .or(ref(rules, "base")));
52
+ // Publisher - always "OIML"
53
+ rule("publisher", () => str("OIML").as("publisher").then(ref(rules, "space")));
54
+ // Document type - single letter
55
+ rule("doc_type", () => match("[BDEGRSVX]").as("type").then(ref(rules, "space")));
56
+ // Bulletin locator - structured YYYY[-II[-SS]]
57
+ rule("bulletin_date", () => ref(rules, "space")
58
+ .then(ref(rules, "year_digits").as("year"))
59
+ .then(ref(rules, "dash").then(ref(rules, "two_digits").as("issue")).maybe())
60
+ .then(ref(rules, "dash").then(ref(rules, "two_digits").as("sequence")).maybe()));
61
+ rule("two_digits", () => match("\\d").repeat(2, 2));
62
+ rule("roman_numeral", () => match("[IVXLCDM]").repeat(1).as("volume_roman"));
63
+ // Citation form: LXVII(2) 20260211
64
+ rule("bulletin_citation", () => ref(rules, "space")
65
+ .then(ref(rules, "roman_numeral"))
66
+ .then(ref(rules, "lparen"), ref(rules, "digits").as("issue_arabic"), ref(rules, "rparen"))
67
+ .then(str(" "), match("\\d").repeat(8, 8).as("article_id")));
68
+ rule("bulletin_identifier", () => ref(rules, "publisher")
69
+ .then(str("Bulletin").as("type"))
70
+ .then(ref(rules, "bulletin_citation").or(ref(rules, "bulletin_date")).maybe())
71
+ .then(ref(rules, "language_portion").maybe().as("language")));
72
+ rule("number_only", () => ref(rules, "digits").as("number"));
73
+ rule("part_number", () => ref(rules, "dash").then(ref(rules, "digits")
74
+ .then(ref(rules, "slash").then(ref(rules, "dash"), ref(rules, "digits")).repeat(0))
75
+ .as("part")));
76
+ rule("subpart_number", () => ref(rules, "dash").then(ref(rules, "digits").as("subpart")));
77
+ rule("named_suffix", () => ref(rules, "dash")
78
+ .then(str("GUM")
79
+ .then(ref(rules, "space"), ref(rules, "digits"))
80
+ .or(match("[A-Za-z]").repeat(1).then(str("_").maybe(), ref(rules, "digits")).repeat(0))
81
+ .as("code_suffix"))
82
+ .or(str(" ").then(str("Brochure").as("code_suffix"), str("").as("space_suffix"))));
83
+ rule("full_number", () => ref(rules, "number_only")
84
+ .then(ref(rules, "part_number"), ref(rules, "subpart_number"), ref(rules, "named_suffix").maybe())
85
+ .or(ref(rules, "number_only").then(ref(rules, "part_number"), ref(rules, "named_suffix").maybe()))
86
+ .or(ref(rules, "number_only").then(ref(rules, "named_suffix").maybe())));
87
+ rule("edition_number", () => str("6th")
88
+ .or(str("5th"))
89
+ .or(str("4th"))
90
+ .or(str("3rd"))
91
+ .or(str("2nd"))
92
+ .or(str("1st"))
93
+ .or(match("\\d").repeat(1).then(str("th").or(str("nd")).or(str("rd")).or(str("st"))))
94
+ .as("edition"));
95
+ rule("edition_text", () => str("Edition").or(str("edition")));
96
+ rule("edition_portion", () => str(", ")
97
+ .or(ref(rules, "space"))
98
+ .then(ref(rules, "edition_number").maybe(), ref(rules, "space?"), ref(rules, "edition_text"), ref(rules, "space?"), ref(rules, "year_digits").as("year"))
99
+ .as("edition_format"));
100
+ rule("date", () => ref(rules, "edition_portion")
101
+ // pubid#403: both sides of the colon are whitespace-tolerant
102
+ .or(ref(rules, "space?").then(ref(rules, "colon"), ref(rules, "space?"), ref(rules, "year_digits").as("year")))
103
+ .or(ref(rules, "space?").then(ref(rules, "lparen"), ref(rules, "year_digits").as("year"), ref(rules, "rparen"))));
104
+ rule("stage_iteration", () => match("\\d").repeat(1).then(str("."), match("\\d").repeat(1)).or(match("\\d").repeat(1)).as("iteration"));
105
+ rule("stage_abbr", () => str("WD").or(str("CD")).as("stage"));
106
+ rule("draft_stage", () => ref(rules, "space").then(ref(rules, "stage_iteration").maybe(), ref(rules, "stage_abbr")));
107
+ // Language codes: two-letter OIML codes before the single letters
108
+ rule("lang_single", () => match("[EFRXDSCAU]"));
109
+ rule("lang_multi_oiml", () => str("PO").or(str("PT")).or(str("PE")).or(str("SR")));
110
+ rule("lang_multi", () => match("[a-z]").repeat(2, 2));
111
+ rule("language_code", () => ref(rules, "lang_single")
112
+ .then(ref(rules, "slash"), ref(rules, "lang_single"))
113
+ .or(ref(rules, "lang_multi_oiml"))
114
+ .or(ref(rules, "lang_single"))
115
+ .or(ref(rules, "lang_multi"))
116
+ .as("language"));
117
+ rule("language_with_space", () => ref(rules, "space")
118
+ .then(ref(rules, "lparen"), ref(rules, "language_code"), ref(rules, "rparen"))
119
+ .then(str("").as("space_before_lang")));
120
+ rule("language_without_space", () => ref(rules, "lparen").then(ref(rules, "language_code"), ref(rules, "rparen")));
121
+ rule("language_portion", () => ref(rules, "language_with_space").or(ref(rules, "language_without_space")));
122
+ // Amendment identifier - "Amendment (YYYY) to BASE"
123
+ rule("amendment_identifier", () => str("Amendment")
124
+ .then(ref(rules, "space"), ref(rules, "lparen"), ref(rules, "year_digits").as("year"), ref(rules, "rparen"))
125
+ .then(str(" "), str("to"), str(" "))
126
+ .then(ref(rules, "base_without_language").as("base"))
127
+ .then(ref(rules, "language_portion").maybe().as("language")));
128
+ rule("amendment_short", () => ref(rules, "publisher")
129
+ .then(ref(rules, "doc_type"))
130
+ .then(ref(rules, "full_number").as("base_code"))
131
+ .then(str(" "), str("Amendment").as("amd_marker"))
132
+ .then(str(" ")
133
+ .then(ref(rules, "edition_text"), ref(rules, "space?"), ref(rules, "year_digits").as("year"))
134
+ .as("edition_format")
135
+ .or(ref(rules, "colon").then(ref(rules, "space?"), ref(rules, "year_digits").as("year"))))
136
+ .then(ref(rules, "language_portion").maybe().as("language")));
137
+ rule("trailing_supplement_identifier", () => ref(rules, "base_without_language")
138
+ .as("base")
139
+ .then(str(" "), str("Amendment").or(str("Errata")).as("trailing_marker"))
140
+ .then(ref(rules, "language_portion").maybe().as("language")));
141
+ rule("plus_supplement_identifier", () => ref(rules, "base_without_language")
142
+ .as("base")
143
+ .then(str("+"), str("Amendment").or(str("Errata")).as("plus_marker"))
144
+ .then(ref(rules, "colon").then(ref(rules, "year_digits").as("year")).maybe())
145
+ .then(ref(rules, "language_portion").maybe().as("language")));
146
+ rule("annex_identifier", () => ref(rules, "base_without_language")
147
+ .as("base")
148
+ .then(str(" "), str("Annexes").as("annex_marker"))
149
+ .then(str(" ")
150
+ .then(ref(rules, "edition_text"), str(" "), ref(rules, "year_digits").as("year"))
151
+ .as("edition_format")
152
+ .or(ref(rules, "colon").then(ref(rules, "year_digits").as("year")))
153
+ .maybe())
154
+ .then(ref(rules, "language_portion").maybe().as("language")));
155
+ rule("annex_letter_value", () => match("[A-Z]").then(ref(rules, "dash").then(match("[A-Z]")).maybe()).as("annex_letter"));
156
+ rule("annex_letter_identifier", () => ref(rules, "base_without_language")
157
+ .as("base")
158
+ .then(str(" "), str("Annex"), str(" "), ref(rules, "annex_letter_value"))
159
+ .then(str(" ")
160
+ .then(ref(rules, "edition_text"), str(" "), ref(rules, "year_digits").as("year"))
161
+ .or(ref(rules, "colon").then(ref(rules, "year_digits").as("year")))
162
+ .maybe())
163
+ .then(ref(rules, "language_portion").maybe().as("language")));
164
+ rule("base_without_language", () => ref(rules, "publisher")
165
+ .then(ref(rules, "doc_type"))
166
+ .then(ref(rules, "full_number"))
167
+ .then(ref(rules, "date").maybe())
168
+ .then(ref(rules, "draft_stage").maybe()));
169
+ rule("base", () => ref(rules, "publisher")
170
+ .then(ref(rules, "doc_type"))
171
+ .then(ref(rules, "full_number"))
172
+ .then(ref(rules, "date").maybe())
173
+ .then(ref(rules, "draft_stage").maybe())
174
+ .then(ref(rules, "language_portion").maybe()));
175
+ return rules;
176
+ }
177
+ export const oimlGrammar = {
178
+ rules: buildRules(),
179
+ root: "identifier",
180
+ };
@@ -0,0 +1,9 @@
1
+ import type { FlavorImplementation } from "../../conformance/implementation.js";
2
+ import { ParseFailed } from "../../grammar/engine.js";
3
+ /**
4
+ * Grammar-backed OIML on the unified model: the model classes are
5
+ * BaseIdentifier subclasses, so they satisfy the Identifier adapter
6
+ * directly (toHash/toHuman/toUrn/fromHash come from the model).
7
+ */
8
+ export declare function oimlGrammarImplementation(): FlavorImplementation;
9
+ export { ParseFailed };
@@ -0,0 +1,16 @@
1
+ import { ParseFailed, parseGrammar } from "../../grammar/engine.js";
2
+ import { oimlGrammar } from "./grammar.js";
3
+ import { buildOimlIdentifier } from "./model.js";
4
+ /**
5
+ * Grammar-backed OIML on the unified model: the model classes are
6
+ * BaseIdentifier subclasses, so they satisfy the Identifier adapter
7
+ * directly (toHash/toHuman/toUrn/fromHash come from the model).
8
+ */
9
+ export function oimlGrammarImplementation() {
10
+ return {
11
+ parse(input) {
12
+ return buildOimlIdentifier(parseGrammar(oimlGrammar, input));
13
+ },
14
+ };
15
+ }
16
+ export { ParseFailed };
@@ -0,0 +1,4 @@
1
+ import type { Tree } from "../../grammar/engine.js";
2
+ import { BaseIdentifier } from "../../model/identifier.js";
3
+ /** ---- builder (lib/pubid/oiml/builder.rb) ---- */
4
+ export declare function buildOimlIdentifier(tree: Tree): BaseIdentifier;
@@ -0,0 +1,462 @@
1
+ import { ParseFailed } from "../../grammar/engine.js";
2
+ import { BaseIdentifier, registerType } from "../../model/identifier.js";
3
+ import { extendAttributes, keyValue } from "../../model/attribute.js";
4
+ import { BaseUrnGenerator } from "../../model/urn-generator.js";
5
+ const KIND_BY_TYPE = {
6
+ B: "basic-publication",
7
+ D: "document",
8
+ E: "expert-report",
9
+ G: "guide",
10
+ R: "recommendation",
11
+ S: "seminar-report",
12
+ V: "vocabulary",
13
+ };
14
+ const TYPE_STRINGS = {
15
+ "basic-publication": "B",
16
+ document: "D",
17
+ "expert-report": "E",
18
+ guide: "G",
19
+ recommendation: "R",
20
+ "seminar-report": "S",
21
+ vocabulary: "V",
22
+ };
23
+ function isObj(v) {
24
+ return typeof v === "object" && v !== null && !Array.isArray(v);
25
+ }
26
+ function str(v) {
27
+ return v === undefined || v === null ? undefined : String(v);
28
+ }
29
+ /** Ruby Builder#extract_language. */
30
+ function extractLanguage(langData) {
31
+ if (isObj(langData))
32
+ return str(langData["language"]);
33
+ return str(langData);
34
+ }
35
+ /** ---- shared attribute tables (snake_case = default wire names) ---- */
36
+ const SINGLE_ATTRS = {
37
+ publisher: { type: "string" },
38
+ language: { type: "string" },
39
+ parsed_format: { type: "string", default: "short" },
40
+ number: { type: "string" },
41
+ part: { type: "string" },
42
+ subpart: { type: "string" },
43
+ suffix: { type: "string" },
44
+ space_suffix: { type: "boolean", default: false },
45
+ year: { type: "string" },
46
+ edition: { type: "string" },
47
+ stage: { type: "string" },
48
+ iteration: { type: "string" },
49
+ };
50
+ class OimlBase extends BaseIdentifier {
51
+ effectiveFormat() {
52
+ return this.parsed_format === "long" ? "long" : "short";
53
+ }
54
+ }
55
+ /** ---- single documents (CodeNumber + Bulletin) ---- */
56
+ class OimlSingle extends OimlBase {
57
+ /** Ruby Identifiers::CodeNumber#code. */
58
+ composedCode() {
59
+ if (this.number === undefined)
60
+ return undefined;
61
+ let result = this.number;
62
+ if (this.part)
63
+ result += `-${this.part}`;
64
+ if (this.subpart)
65
+ result += `-${this.subpart}`;
66
+ if (this.suffix)
67
+ result += `${this.space_suffix ? " " : "-"}${this.suffix}`;
68
+ return result;
69
+ }
70
+ typeString() {
71
+ return TYPE_STRINGS[this.constructor.polymorphicName.slice("pubid:oiml:".length)] ?? "R";
72
+ }
73
+ /** renderSingle — the formatOverride threads through supplement rendering. */
74
+ render(formatOverride) {
75
+ const format = formatOverride ?? this.effectiveFormat();
76
+ let result = `${this.publisher} ${this.typeString()} ${this.composedCode()}`;
77
+ let usingEditionFormat = false;
78
+ if (this.edition && this.year) {
79
+ result += ` ${this.edition} Edition ${this.year}`;
80
+ usingEditionFormat = true;
81
+ }
82
+ else if (this.edition) {
83
+ result += ` ${this.edition}`;
84
+ usingEditionFormat = true;
85
+ }
86
+ else if (this.year) {
87
+ if (format === "long") {
88
+ result += ` Edition ${this.year}`;
89
+ usingEditionFormat = true;
90
+ }
91
+ else {
92
+ result += `:${this.year}`;
93
+ }
94
+ }
95
+ if (this.stage || this.iteration) {
96
+ result += " ";
97
+ if (this.iteration)
98
+ result += this.iteration;
99
+ if (this.stage)
100
+ result += this.stage;
101
+ }
102
+ if (this.language) {
103
+ result +=
104
+ usingEditionFormat || this.parsed_format === "short_with_space"
105
+ ? ` (${this.language})`
106
+ : `(${this.language})`;
107
+ }
108
+ return result;
109
+ }
110
+ }
111
+ function oimlSingleClass(kind) {
112
+ const isBulletin = kind === "bulletin";
113
+ class OimlSingleIdentifier extends OimlSingle {
114
+ static polymorphicName = `pubid:oiml:${kind}`;
115
+ static attributes = isBulletin
116
+ ? extendAttributes(BaseIdentifier, { ...SINGLE_ATTRS, sequence: { type: "string" } })
117
+ : extendAttributes(BaseIdentifier, SINGLE_ATTRS);
118
+ render(formatOverride) {
119
+ if (!isBulletin)
120
+ return super.render(formatOverride);
121
+ // renderBulletin
122
+ if (this.parsed_format === "citation" &&
123
+ this.year &&
124
+ this.number &&
125
+ this.sequence) {
126
+ return `${this.publisher} Bulletin ${toRoman(Number(this.year) - 1959)}(${Number(this.number)}) ${this.year}${this.number}${this.sequence}`;
127
+ }
128
+ let result = `${this.publisher} Bulletin`;
129
+ if (this.year) {
130
+ result += ` ${this.year}`;
131
+ if (this.number)
132
+ result += `-${this.number}`;
133
+ if (this.sequence)
134
+ result += `-${this.sequence}`;
135
+ }
136
+ if (this.language)
137
+ result += ` (${this.language})`;
138
+ return result;
139
+ }
140
+ }
141
+ registerType(OimlSingleIdentifier);
142
+ return OimlSingleIdentifier;
143
+ }
144
+ const SUPP_ATTRS = {
145
+ language: { type: "string" },
146
+ parsed_format: { type: "string", default: "short" },
147
+ base: { type: OimlSingle },
148
+ supp_year: { type: "string" },
149
+ trailing: { type: "boolean", default: false },
150
+ joined: { type: "boolean", default: false },
151
+ letter: { type: "string" },
152
+ year_on_base: { type: "boolean", default: false },
153
+ };
154
+ const SUPP_MAPPINGS = keyValue({ wire: "language", to: "language" }, { wire: "parsed_format", to: "parsed_format" }, { wire: "base", to: "base" }, { wire: "year", to: "supp_year" }, { wire: "trailing", to: "trailing" }, { wire: "joined", to: "joined" }, { wire: "letter", to: "letter" }, { wire: "year_on_base", to: "year_on_base" });
155
+ /** ---- supplements (Amendment / Errata) and Annex ---- */
156
+ class OimlSupplement extends OimlBase {
157
+ supplementType() {
158
+ const kind = this.constructor.polymorphicName.slice("pubid:oiml:".length);
159
+ if (kind === "annex")
160
+ return this.letter ? `Annex ${this.letter}` : "Annexes";
161
+ return kind === "errata" ? "Errata" : "Amendment";
162
+ }
163
+ render() {
164
+ const kind = this.constructor.polymorphicName.slice("pubid:oiml:".length);
165
+ if (kind === "annex")
166
+ return this.renderAnnex();
167
+ return this.renderSupplement();
168
+ }
169
+ /** renderSupplement */
170
+ renderSupplement() {
171
+ if (this.joined) {
172
+ let result = `${stripLanguage(this.base.render())}+${this.supplementType()}`;
173
+ if (this.supp_year)
174
+ result += `:${this.supp_year}`;
175
+ if (this.language)
176
+ result += ` (${this.language})`;
177
+ return result;
178
+ }
179
+ if (this.trailing) {
180
+ let result = `${stripLanguage(this.base.render())} ${this.supplementType()}`;
181
+ if (this.language)
182
+ result += ` (${this.language})`;
183
+ return result;
184
+ }
185
+ const baseFormat = this.effectiveFormat() !== "short"
186
+ ? this.effectiveFormat()
187
+ : this.base.parsed_format === "long"
188
+ ? "long"
189
+ : "short";
190
+ const baseStr = stripLanguage(this.base.render(baseFormat));
191
+ let result = `${this.supplementType()} (${this.supp_year}) to ${baseStr}`;
192
+ if (this.language)
193
+ result += ` (${this.language})`;
194
+ return result;
195
+ }
196
+ /** renderAnnex */
197
+ renderAnnex() {
198
+ if (this.year_on_base) {
199
+ const marker = this.letter ? `Annex ${this.letter}` : "Annexes";
200
+ let result = `${stripLanguage(this.base.render())} ${marker}`;
201
+ if (this.language)
202
+ result += ` (${this.language})`;
203
+ return result;
204
+ }
205
+ const annexFormat = this.effectiveFormat();
206
+ const baseStr = this.base
207
+ .render(this.base.parsed_format === "long" ? "long" : "short")
208
+ .replace(/:.*/, "")
209
+ .replace(/\s+Edition\s+\d{4}/, "")
210
+ .replace(/\(.*\)/, "")
211
+ .trim();
212
+ let result = baseStr;
213
+ if (this.letter) {
214
+ result += ` Annex ${this.letter}`;
215
+ if (this.supp_year)
216
+ result += ` Edition ${this.supp_year}`;
217
+ }
218
+ else {
219
+ result += " Annexes";
220
+ if (this.supp_year) {
221
+ if (annexFormat === "long") {
222
+ result += ` Edition ${this.supp_year}`;
223
+ }
224
+ else {
225
+ result += `:${this.supp_year}`;
226
+ }
227
+ }
228
+ }
229
+ if (this.language)
230
+ result += ` (${this.language})`;
231
+ return result;
232
+ }
233
+ }
234
+ function oimlSupplementClass(kind) {
235
+ class OimlSupplementIdentifier extends OimlSupplement {
236
+ static polymorphicName = `pubid:oiml:${kind}`;
237
+ static attributes = extendAttributes(BaseIdentifier, SUPP_ATTRS);
238
+ static mappings = SUPP_MAPPINGS;
239
+ }
240
+ registerType(OimlSupplementIdentifier);
241
+ return OimlSupplementIdentifier;
242
+ }
243
+ const KIND_CLASSES = {};
244
+ for (const kind of [
245
+ "recommendation", "basic-publication", "document", "guide", "vocabulary",
246
+ "expert-report", "seminar-report", "bulletin",
247
+ ]) {
248
+ KIND_CLASSES[kind] = oimlSingleClass(kind);
249
+ }
250
+ KIND_CLASSES["amendment"] = oimlSupplementClass("amendment");
251
+ KIND_CLASSES["errata"] = oimlSupplementClass("errata");
252
+ KIND_CLASSES["annex"] = oimlSupplementClass("annex");
253
+ /** ---- URN generator (lib/pubid/oiml/urn_generator.rb, all kinds) ---- */
254
+ class OimlUrnGenerator extends BaseUrnGenerator {
255
+ generate() {
256
+ const id = this.identifier;
257
+ const kind = id.constructor.polymorphicName.slice("pubid:oiml:".length);
258
+ if (kind === "bulletin") {
259
+ const b = id;
260
+ const parts = ["urn", "oiml", "bulletin"];
261
+ if (b.year) {
262
+ let locator = b.year;
263
+ if (b.number)
264
+ locator += `-${b.number}`;
265
+ if (b.sequence)
266
+ locator += `-${b.sequence}`;
267
+ parts.push(locator);
268
+ }
269
+ if (id.language)
270
+ parts.push(id.language.toLowerCase());
271
+ return parts.join(":");
272
+ }
273
+ const isSupp = kind === "amendment" || kind === "errata" || kind === "annex";
274
+ const single = (isSupp ? id.base : id);
275
+ const parts = ["urn", "oiml"];
276
+ // Ruby: `return "r" unless identifier.type` — a supplement has no
277
+ // type letter, so its URN carries the default "r" (corpus quirk).
278
+ parts.push(isSupp ? "r" : (TYPE_STRINGS[kind] ?? "r").toLowerCase());
279
+ // Ruby SupplementIdentifier#code delegates to the wrapped standard.
280
+ const code = single.composedCode();
281
+ if (code)
282
+ parts.push(code);
283
+ const year = id.year ??
284
+ id.supp_year;
285
+ if (year)
286
+ parts.push(year);
287
+ const stage = id.stage;
288
+ if (stage)
289
+ parts.push(stage.toLowerCase());
290
+ const iteration = id.iteration;
291
+ if (iteration)
292
+ parts.push(iteration);
293
+ if (id.language)
294
+ parts.push(id.language.toLowerCase());
295
+ return parts.join(":");
296
+ }
297
+ }
298
+ for (const klass of Object.values(KIND_CLASSES)) {
299
+ klass.urnGenerator = OimlUrnGenerator;
300
+ }
301
+ /** ---- builder (lib/pubid/oiml/builder.rb) ---- */
302
+ export function buildOimlIdentifier(tree) {
303
+ if (!isObj(tree))
304
+ throw new ParseFailed("OIML: unexpected parse tree", 0);
305
+ if (tree["amd_marker"] !== undefined)
306
+ return buildShortAmendment(tree);
307
+ if (tree["base"] !== undefined)
308
+ return buildSupplement(tree);
309
+ return buildBaseDocument(tree);
310
+ }
311
+ function buildShortAmendment(tree) {
312
+ const baseCode = isObj(tree["base_code"]) ? tree["base_code"] : undefined;
313
+ const base = buildBaseDocument({
314
+ publisher: tree["publisher"],
315
+ type: tree["type"],
316
+ number: baseCode?.["number"],
317
+ part: baseCode?.["part"],
318
+ subpart: baseCode?.["subpart"],
319
+ });
320
+ const editionFormat = isObj(tree["edition_format"]) ? tree["edition_format"] : undefined;
321
+ const yearValue = editionFormat ? editionFormat["year"] : tree["year"];
322
+ const attrs = {
323
+ publisher: "OIML",
324
+ base,
325
+ parsed_format: editionFormat ? "long" : "short",
326
+ };
327
+ const suppYear = str(yearValue);
328
+ if (suppYear !== undefined)
329
+ attrs["supp_year"] = suppYear;
330
+ const language = extractLanguage(tree["language"]);
331
+ if (language !== undefined)
332
+ attrs["language"] = language;
333
+ return new KIND_CLASSES["amendment"](attrs);
334
+ }
335
+ function buildSupplement(tree) {
336
+ const marker = str(tree["trailing_marker"]);
337
+ const plusMarker = str(tree["plus_marker"]);
338
+ let kind;
339
+ if (tree["annex_letter"] !== undefined || tree["annex_marker"] !== undefined) {
340
+ kind = "annex";
341
+ }
342
+ else if (marker === "Errata" || plusMarker === "Errata") {
343
+ kind = "errata";
344
+ }
345
+ else {
346
+ kind = "amendment";
347
+ }
348
+ const base = buildOimlIdentifier(tree["base"]);
349
+ const editionFormat = isObj(tree["edition_format"]) ? tree["edition_format"] : undefined;
350
+ const yearValue = editionFormat ? editionFormat["year"] : tree["year"];
351
+ const attrs = {
352
+ publisher: "OIML",
353
+ base,
354
+ parsed_format: editionFormat ? "long" : "short",
355
+ };
356
+ const suppYear = str(yearValue);
357
+ if (suppYear !== undefined)
358
+ attrs["supp_year"] = suppYear;
359
+ const language = extractLanguage(tree["language"]);
360
+ if (language !== undefined)
361
+ attrs["language"] = language;
362
+ if (marker !== undefined)
363
+ attrs["trailing"] = true;
364
+ if (plusMarker !== undefined)
365
+ attrs["joined"] = true;
366
+ const letter = str(tree["annex_letter"]);
367
+ if (letter !== undefined)
368
+ attrs["letter"] = letter;
369
+ // Annex with no year of its own but a dated base: the year belongs to
370
+ // the base and must render glued to it.
371
+ if (kind === "annex" && !yearValue && base.year)
372
+ attrs["year_on_base"] = true;
373
+ return new KIND_CLASSES[kind](attrs);
374
+ }
375
+ function buildBaseDocument(tree) {
376
+ const type = str(tree["type"]);
377
+ const kind = type === "Bulletin" ? "bulletin" : KIND_BY_TYPE[type ?? ""] ?? "recommendation";
378
+ const attrs = {
379
+ publisher: str(tree["publisher"]) ?? "OIML",
380
+ };
381
+ const number = str(tree["number"]);
382
+ if (number !== undefined)
383
+ attrs["number"] = number;
384
+ const part = str(tree["part"]);
385
+ if (part !== undefined)
386
+ attrs["part"] = part;
387
+ const subpart = str(tree["subpart"]);
388
+ if (subpart !== undefined)
389
+ attrs["subpart"] = subpart;
390
+ const codeSuffix = str(tree["code_suffix"]);
391
+ if (codeSuffix !== undefined)
392
+ attrs["suffix"] = codeSuffix;
393
+ if ("space_suffix" in tree)
394
+ attrs["space_suffix"] = true;
395
+ const editionFormat = isObj(tree["edition_format"]) ? tree["edition_format"] : undefined;
396
+ let yearValue;
397
+ if (editionFormat) {
398
+ yearValue = editionFormat["year"];
399
+ const edition = str(editionFormat["edition"]);
400
+ if (edition !== undefined)
401
+ attrs["edition"] = edition;
402
+ }
403
+ else {
404
+ yearValue = tree["year"];
405
+ }
406
+ const year = str(yearValue);
407
+ if (year !== undefined)
408
+ attrs["year"] = year;
409
+ if (kind === "bulletin")
410
+ applyBulletinLocator(attrs, tree);
411
+ attrs["parsed_format"] = editionFormat
412
+ ? "long"
413
+ : tree["space_before_lang"] !== undefined
414
+ ? "short_with_space"
415
+ : tree["article_id"] !== undefined
416
+ ? "citation"
417
+ : "short";
418
+ const stage = str(tree["stage"]);
419
+ if (stage !== undefined)
420
+ attrs["stage"] = stage;
421
+ const iteration = str(tree["iteration"]);
422
+ if (iteration !== undefined)
423
+ attrs["iteration"] = iteration;
424
+ const language = extractLanguage(tree["language"]);
425
+ if (language !== undefined)
426
+ attrs["language"] = language;
427
+ return new KIND_CLASSES[kind](attrs);
428
+ }
429
+ function applyBulletinLocator(attrs, tree) {
430
+ const articleId = str(tree["article_id"]);
431
+ if (articleId) {
432
+ attrs["year"] = articleId.slice(0, 4);
433
+ attrs["number"] = articleId.slice(4, 6);
434
+ attrs["sequence"] = articleId.slice(6, 8);
435
+ return;
436
+ }
437
+ const issue = str(tree["issue"]);
438
+ if (issue !== undefined)
439
+ attrs["number"] = issue;
440
+ const sequence = str(tree["sequence"]);
441
+ if (sequence !== undefined)
442
+ attrs["sequence"] = sequence;
443
+ }
444
+ /** ---- shared render helpers ---- */
445
+ function stripLanguage(s) {
446
+ return s.replace(/\s*\([^)]+\)\s*$/, "").trim();
447
+ }
448
+ function toRoman(n) {
449
+ const table = [
450
+ [1000, "M"], [900, "CM"], [500, "D"], [400, "CD"], [100, "C"],
451
+ [90, "XC"], [50, "L"], [40, "XL"], [10, "X"], [9, "IX"],
452
+ [5, "V"], [4, "IV"], [1, "I"],
453
+ ];
454
+ let out = "";
455
+ for (const [value, sym] of table) {
456
+ while (n >= value) {
457
+ out += sym;
458
+ n -= value;
459
+ }
460
+ }
461
+ return out;
462
+ }