@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.
- package/artifact/corpus.json.gz +0 -0
- package/dist/conformance/cli.js +5 -1
- package/dist/flavors/amca/grammar.d.ts +4 -0
- package/dist/flavors/amca/grammar.js +87 -0
- package/dist/flavors/amca/index.d.ts +4 -0
- package/dist/flavors/amca/index.js +76 -0
- package/dist/flavors/amca/model.d.ts +29 -0
- package/dist/flavors/amca/model.js +146 -0
- package/dist/flavors/ansi/index.d.ts +24 -0
- package/dist/flavors/ansi/index.js +147 -0
- package/dist/flavors/api/grammar.d.ts +4 -0
- package/dist/flavors/api/grammar.js +79 -0
- package/dist/flavors/api/index.d.ts +4 -0
- package/dist/flavors/api/index.js +71 -0
- package/dist/flavors/api/model.d.ts +27 -0
- package/dist/flavors/api/model.js +114 -0
- package/dist/flavors/ashrae/grammar.d.ts +2 -0
- package/dist/flavors/ashrae/grammar.js +667 -0
- package/dist/flavors/ashrae/index.d.ts +4 -0
- package/dist/flavors/ashrae/index.js +306 -0
- package/dist/flavors/ashrae/model.d.ts +87 -0
- package/dist/flavors/ashrae/model.js +335 -0
- package/dist/flavors/ashrae/preprocessor.d.ts +7 -0
- package/dist/flavors/ashrae/preprocessor.js +44 -0
- package/dist/flavors/asme/grammar.d.ts +2 -0
- package/dist/flavors/asme/grammar.js +200 -0
- package/dist/flavors/asme/index.d.ts +4 -0
- package/dist/flavors/asme/index.js +138 -0
- package/dist/flavors/asme/model.d.ts +34 -0
- package/dist/flavors/asme/model.js +123 -0
- package/dist/flavors/astm/grammar.d.ts +2 -0
- package/dist/flavors/astm/grammar.js +110 -0
- package/dist/flavors/astm/index.d.ts +2 -0
- package/dist/flavors/astm/index.js +127 -0
- package/dist/flavors/astm/model.d.ts +42 -0
- package/dist/flavors/astm/model.js +263 -0
- package/dist/flavors/bipm/index.d.ts +30 -0
- package/dist/flavors/bipm/index.js +434 -0
- package/dist/flavors/bsi/grammar.d.ts +4 -0
- package/dist/flavors/bsi/grammar.js +535 -0
- package/dist/flavors/bsi/index.d.ts +4 -0
- package/dist/flavors/bsi/index.js +941 -0
- package/dist/flavors/bsi/model.d.ts +127 -0
- package/dist/flavors/bsi/model.js +863 -0
- package/dist/flavors/calconnect/index.d.ts +21 -0
- package/dist/flavors/calconnect/index.js +113 -0
- package/dist/flavors/cen_cenelec/grammar.d.ts +4 -0
- package/dist/flavors/cen_cenelec/grammar.js +107 -0
- package/dist/flavors/cen_cenelec/index.d.ts +5 -0
- package/dist/flavors/cen_cenelec/index.js +263 -0
- package/dist/flavors/cen_cenelec/model.d.ts +45 -0
- package/dist/flavors/cen_cenelec/model.js +490 -0
- package/dist/flavors/cie/grammar.d.ts +5 -0
- package/dist/flavors/cie/grammar.js +127 -0
- package/dist/flavors/cie/index.d.ts +2 -0
- package/dist/flavors/cie/index.js +311 -0
- package/dist/flavors/cie/model.d.ts +122 -0
- package/dist/flavors/cie/model.js +464 -0
- package/dist/flavors/csa/grammar.d.ts +6 -0
- package/dist/flavors/csa/grammar.js +239 -0
- package/dist/flavors/csa/index.d.ts +2 -0
- package/dist/flavors/csa/index.js +634 -0
- package/dist/flavors/csa/model.d.ts +33 -0
- package/dist/flavors/csa/model.js +505 -0
- package/dist/flavors/doi/index.d.ts +13 -0
- package/dist/flavors/doi/index.js +65 -0
- package/dist/flavors/easc/index.d.ts +4 -0
- package/dist/flavors/easc/index.js +107 -0
- package/dist/flavors/ecma/index.d.ts +4 -0
- package/dist/flavors/ecma/index.js +117 -0
- package/dist/flavors/etsi/index.d.ts +14 -0
- package/dist/flavors/etsi/index.js +307 -0
- package/dist/flavors/evs/grammar.d.ts +2 -0
- package/dist/flavors/evs/grammar.js +22 -0
- package/dist/flavors/evs/index.d.ts +2 -0
- package/dist/flavors/evs/index.js +24 -0
- package/dist/flavors/evs/model.d.ts +19 -0
- package/dist/flavors/evs/model.js +36 -0
- package/dist/flavors/gb/index.d.ts +24 -0
- package/dist/flavors/gb/index.js +111 -0
- package/dist/flavors/gost/grammar.d.ts +2 -0
- package/dist/flavors/gost/grammar.js +46 -0
- package/dist/flavors/gost/index.d.ts +2 -0
- package/dist/flavors/gost/index.js +165 -0
- package/dist/flavors/gost/model.d.ts +61 -0
- package/dist/flavors/gost/model.js +147 -0
- package/dist/flavors/gost/prefix_routes.d.ts +15 -0
- package/dist/flavors/gost/prefix_routes.js +192 -0
- package/dist/flavors/iala/index.d.ts +32 -0
- package/dist/flavors/iala/index.js +300 -0
- package/dist/flavors/iana/index.d.ts +19 -0
- package/dist/flavors/iana/index.js +71 -0
- package/dist/flavors/idf/grammar.d.ts +2 -0
- package/dist/flavors/idf/grammar.js +71 -0
- package/dist/flavors/idf/index.d.ts +2 -0
- package/dist/flavors/idf/index.js +100 -0
- package/dist/flavors/idf/model.d.ts +54 -0
- package/dist/flavors/idf/model.js +359 -0
- package/dist/flavors/iec/grammar.d.ts +3 -0
- package/dist/flavors/iec/grammar.js +187 -0
- package/dist/flavors/iec/index.d.ts +4 -0
- package/dist/flavors/iec/index.js +507 -0
- package/dist/flavors/iec/model.d.ts +54 -0
- package/dist/flavors/iec/model.js +437 -0
- package/dist/flavors/ieee/grammar.d.ts +2 -0
- package/dist/flavors/ieee/grammar.js +626 -0
- package/dist/flavors/ieee/index.d.ts +3 -0
- package/dist/flavors/ieee/index.js +1186 -0
- package/dist/flavors/ieee/model.d.ts +142 -0
- package/dist/flavors/ieee/model.js +1148 -0
- package/dist/flavors/ieee/preprocessor.d.ts +8 -0
- package/dist/flavors/ieee/preprocessor.js +410 -0
- package/dist/flavors/ieee/update_codes.d.ts +8 -0
- package/dist/flavors/ieee/update_codes.js +191 -0
- package/dist/flavors/ietf/index.d.ts +30 -0
- package/dist/flavors/ietf/index.js +151 -0
- package/dist/flavors/iho/index.d.ts +4 -0
- package/dist/flavors/iho/index.js +126 -0
- package/dist/flavors/index.d.ts +9 -0
- package/dist/flavors/index.js +136 -0
- package/dist/flavors/isbn/index.d.ts +13 -0
- package/dist/flavors/isbn/index.js +97 -0
- package/dist/flavors/iso/grammar.d.ts +2 -0
- package/dist/flavors/iso/grammar.js +207 -0
- package/dist/flavors/iso/index.d.ts +2 -0
- package/dist/flavors/iso/index.js +343 -0
- package/dist/flavors/iso/model.d.ts +159 -0
- package/dist/flavors/iso/model.js +802 -0
- package/dist/flavors/iso/stages.generated.d.ts +13 -0
- package/dist/flavors/iso/stages.generated.js +391 -0
- package/dist/flavors/itu/grammar.d.ts +4 -0
- package/dist/flavors/itu/grammar.js +205 -0
- package/dist/flavors/itu/index.d.ts +2 -0
- package/dist/flavors/itu/index.js +224 -0
- package/dist/flavors/itu/model.d.ts +184 -0
- package/dist/flavors/itu/model.js +599 -0
- package/dist/flavors/jcgm/index.d.ts +7 -0
- package/dist/flavors/jcgm/index.js +300 -0
- package/dist/flavors/jis/index.d.ts +4 -0
- package/dist/flavors/jis/index.js +267 -0
- package/dist/flavors/nist/grammar.d.ts +2 -0
- package/dist/flavors/nist/grammar.js +287 -0
- package/dist/flavors/nist/index.d.ts +2 -0
- package/dist/flavors/nist/index.js +1244 -0
- package/dist/flavors/nist/model.d.ts +155 -0
- package/dist/flavors/nist/model.js +823 -0
- package/dist/flavors/nist/preprocessor.d.ts +11 -0
- package/dist/flavors/nist/preprocessor.js +281 -0
- package/dist/flavors/oasis/index.d.ts +23 -0
- package/dist/flavors/oasis/index.js +110 -0
- package/dist/flavors/ogc/index.d.ts +20 -0
- package/dist/flavors/ogc/index.js +75 -0
- package/dist/flavors/oiml/grammar.d.ts +2 -0
- package/dist/flavors/oiml/grammar.js +180 -0
- package/dist/flavors/oiml/implementation.d.ts +9 -0
- package/dist/flavors/oiml/implementation.js +16 -0
- package/dist/flavors/oiml/model.d.ts +4 -0
- package/dist/flavors/oiml/model.js +462 -0
- package/dist/flavors/omg/index.d.ts +14 -0
- package/dist/flavors/omg/index.js +73 -0
- package/dist/flavors/plateau/index.d.ts +4 -0
- package/dist/flavors/plateau/index.js +97 -0
- package/dist/flavors/tgpp/index.d.ts +4 -0
- package/dist/flavors/tgpp/index.js +122 -0
- package/dist/flavors/un/index.d.ts +13 -0
- package/dist/flavors/un/index.js +90 -0
- package/dist/flavors/w3c/index.d.ts +14 -0
- package/dist/flavors/w3c/index.js +148 -0
- package/dist/flavors/xsf/index.d.ts +18 -0
- package/dist/flavors/xsf/index.js +65 -0
- package/dist/grammar/engine.d.ts +78 -0
- package/dist/grammar/engine.js +351 -0
- package/dist/model/attribute.d.ts +54 -0
- package/dist/model/attribute.js +53 -0
- package/dist/model/builder.d.ts +39 -0
- package/dist/model/builder.js +124 -0
- package/dist/model/component.d.ts +70 -0
- package/dist/model/component.js +170 -0
- package/dist/model/identifier.d.ts +83 -0
- package/dist/model/identifier.js +270 -0
- package/dist/model/urn-generator.d.ts +27 -0
- package/dist/model/urn-generator.js +91 -0
- package/package.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseIdentifier — the TS mirror of Pubid::Identifier's declaration and
|
|
3
|
+
* canonical-serialization core. Semantics pinned in
|
|
4
|
+
* TODO.unified/01-study-pubid-core.md §2-3:
|
|
5
|
+
*
|
|
6
|
+
* toHash: `_type` first, mapped attributes, then canonicalization
|
|
7
|
+
* (drop empty / default-valued attributes, recursively), then
|
|
8
|
+
* degenerate-component flattening (`date: {year: "2024"}` → `year:
|
|
9
|
+
* "2024"`, `edition: {number: "2"}` → `edition: "2"`), then the class's
|
|
10
|
+
* compactHash hook.
|
|
11
|
+
*
|
|
12
|
+
* fromHash: dispatch on `_type` through the registered type map,
|
|
13
|
+
* re-inflate flat scalars into component hashes (unless the flavor has a
|
|
14
|
+
* converter for the key or declares the flat name as its own attribute),
|
|
15
|
+
* construct with type coercion.
|
|
16
|
+
*
|
|
17
|
+
* Flavors NEVER hand-roll a hash here — they declare attributes and
|
|
18
|
+
* mappings; the only imperative wire code is a converter pair.
|
|
19
|
+
*/
|
|
20
|
+
import { FLAT_SCALAR_COMPONENTS, FLAT_SCALAR_FIELDS, BASE_ATTRIBUTES } from "./attribute.js";
|
|
21
|
+
import { Component } from "./component.js";
|
|
22
|
+
import { BaseUrnGenerator } from "./urn-generator.js";
|
|
23
|
+
const TYPE_REGISTRY = new Map();
|
|
24
|
+
/** Register a concrete identifier class under its polymorphic _type (the *_TYPE_MAP role). */
|
|
25
|
+
export function registerType(klass) {
|
|
26
|
+
TYPE_REGISTRY.set(klass.polymorphicName, klass);
|
|
27
|
+
}
|
|
28
|
+
function resolveType(type) {
|
|
29
|
+
return TYPE_REGISTRY.get(type);
|
|
30
|
+
}
|
|
31
|
+
function isScalarType(t) {
|
|
32
|
+
return typeof t === "string";
|
|
33
|
+
}
|
|
34
|
+
function coerce(value, spec) {
|
|
35
|
+
if (value === undefined || value === null)
|
|
36
|
+
return value;
|
|
37
|
+
if (spec.collection) {
|
|
38
|
+
const list = Array.isArray(value) ? value : [value];
|
|
39
|
+
return list.map((v) => coerceOne(v, spec));
|
|
40
|
+
}
|
|
41
|
+
return coerceOne(value, spec);
|
|
42
|
+
}
|
|
43
|
+
function coerceOne(value, spec) {
|
|
44
|
+
if (isScalarType(spec.type)) {
|
|
45
|
+
if (spec.type === "integer")
|
|
46
|
+
return Number(value);
|
|
47
|
+
if (spec.type === "boolean")
|
|
48
|
+
return Boolean(value);
|
|
49
|
+
return typeof value === "object" && value !== null ? value : String(value);
|
|
50
|
+
}
|
|
51
|
+
if (value instanceof spec.type)
|
|
52
|
+
return value;
|
|
53
|
+
// A cross-flavor identifier instance (e.g. an IEC id nested in an IEEE
|
|
54
|
+
// adoption) is complete already; coercing through the declared flavor's
|
|
55
|
+
// fromHash would rebuild it as the wrong (abstract) class.
|
|
56
|
+
if (value instanceof BaseIdentifier)
|
|
57
|
+
return value;
|
|
58
|
+
if (typeof value === "object" && value !== null) {
|
|
59
|
+
// Identifier-typed attributes (oiml supplement `base`) dispatch
|
|
60
|
+
// polymorphically through the registered type map.
|
|
61
|
+
if (typeof spec.type === "function" && spec.type.prototype instanceof BaseIdentifier) {
|
|
62
|
+
const idCtor = spec.type;
|
|
63
|
+
const v = value;
|
|
64
|
+
return "_type" in v ? idCtor.fromHash(v) : idCtor.fromHash({ ...v, _type: idCtor.polymorphicName });
|
|
65
|
+
}
|
|
66
|
+
return new spec.type(value);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
/** Utils.empty?: nil is NOT empty here (nil is dropped earlier); "" and [] are. */
|
|
71
|
+
function isEmptyValue(value) {
|
|
72
|
+
if (value === "")
|
|
73
|
+
return true;
|
|
74
|
+
if (Array.isArray(value) && value.length === 0)
|
|
75
|
+
return true;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function resolveDefault(spec) {
|
|
79
|
+
return typeof spec.default === "function" ? spec.default() : spec.default;
|
|
80
|
+
}
|
|
81
|
+
export class BaseIdentifier {
|
|
82
|
+
/** The root table; subclasses compose via extendAttributes(BaseIdentifier, …). */
|
|
83
|
+
static attributes = BASE_ATTRIBUTES;
|
|
84
|
+
constructor(attrs = {}) {
|
|
85
|
+
for (const [name, spec] of Object.entries(this.classAttributes())) {
|
|
86
|
+
const value = attrs[name];
|
|
87
|
+
if (value !== undefined) {
|
|
88
|
+
this[name] = coerce(value, spec);
|
|
89
|
+
}
|
|
90
|
+
else if (spec.initializeEmpty && spec.collection) {
|
|
91
|
+
this[name] = [];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** The human form (Ruby render(format: :human) → the flavor renderer). */
|
|
96
|
+
toHuman() {
|
|
97
|
+
return this.render();
|
|
98
|
+
}
|
|
99
|
+
/** Ruby to_urn: the flavor's UrnGenerator, else the base template. */
|
|
100
|
+
toUrn() {
|
|
101
|
+
const Generator = this.constructor.urnGenerator ?? BaseUrnGenerator;
|
|
102
|
+
return new Generator(this).generate();
|
|
103
|
+
}
|
|
104
|
+
fromHash(hash) {
|
|
105
|
+
return this.constructor.fromHash(hash);
|
|
106
|
+
}
|
|
107
|
+
classAttributes() {
|
|
108
|
+
return this.constructor.attributes;
|
|
109
|
+
}
|
|
110
|
+
/** Wire key for an attribute: custom mapping or the attribute name. */
|
|
111
|
+
wireKeyFor(name) {
|
|
112
|
+
const mappings = this.constructor.mappings;
|
|
113
|
+
const found = mappings?.find((m) => m.to === name);
|
|
114
|
+
return found ?? { wire: name };
|
|
115
|
+
}
|
|
116
|
+
attrValue(name) {
|
|
117
|
+
return this[name];
|
|
118
|
+
}
|
|
119
|
+
/** Serialize one attribute's value (component → toWire, nested identifier → toHash, scalars as-is). */
|
|
120
|
+
serializeValue(value) {
|
|
121
|
+
// A nested identifier serializes through the NESTED mapping list: a
|
|
122
|
+
// class whose top-level wire deletes derived keys (the Ruby reference
|
|
123
|
+
// drops JointDevelopment's derived publisher/copublisher only on the
|
|
124
|
+
// public to_hash — lutaml's nested transform reads the accessors and
|
|
125
|
+
// keeps them) must keep them here.
|
|
126
|
+
if (value instanceof BaseIdentifier)
|
|
127
|
+
return value.toHashNested();
|
|
128
|
+
if (value instanceof Component)
|
|
129
|
+
return value.toWire();
|
|
130
|
+
if (Array.isArray(value))
|
|
131
|
+
return value.map((v) => this.serializeValue(v));
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
/** Nested serialization: same as toHash but with the unfiltered mappings. */
|
|
135
|
+
toHashNested() {
|
|
136
|
+
const ctor = this.constructor;
|
|
137
|
+
if (ctor.mappingsNested === undefined)
|
|
138
|
+
return this.toHash();
|
|
139
|
+
return this.toHashWith(ctor.mappingsNested);
|
|
140
|
+
}
|
|
141
|
+
toHash() {
|
|
142
|
+
return this.toHashWith(this.constructor.mappings);
|
|
143
|
+
}
|
|
144
|
+
toHashWith(mappings) {
|
|
145
|
+
const hash = { _type: this.constructor.polymorphicName };
|
|
146
|
+
// A flavor's explicit mapping list is a WHITELIST (lutaml key_value):
|
|
147
|
+
// unmapped attributes never serialize — that is how un keeps its
|
|
148
|
+
// runtime `date` out of the hash while the URN still reads it.
|
|
149
|
+
const emitted = mappings
|
|
150
|
+
? mappings.map((m) => [m.to, m.wire, m.toWire])
|
|
151
|
+
: Object.keys(this.classAttributes()).map((name) => [name, name, undefined]);
|
|
152
|
+
for (const [name, wire, toWire] of emitted) {
|
|
153
|
+
const value = this.attrValue(name);
|
|
154
|
+
// nil never serializes; empty and default-valued attributes are
|
|
155
|
+
// dropped by canonicalization (TODO.unified/01 §2.2).
|
|
156
|
+
if (value === undefined || value === null)
|
|
157
|
+
continue;
|
|
158
|
+
const spec = this.classAttributes()[name];
|
|
159
|
+
if (spec && isEmptyValue(value))
|
|
160
|
+
continue;
|
|
161
|
+
if (spec?.default !== undefined && deepEqual(value, resolveDefault(spec)))
|
|
162
|
+
continue;
|
|
163
|
+
const serialized = toWire ? toWire(this) : this.serializeValue(value);
|
|
164
|
+
if (serialized === undefined || serialized === null)
|
|
165
|
+
continue;
|
|
166
|
+
hash[wire] = serialized;
|
|
167
|
+
}
|
|
168
|
+
this.flattenScalars(hash);
|
|
169
|
+
this.constructor.compactHash?.(this, hash);
|
|
170
|
+
return hash;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Degenerate single-field components collapse to their scalar
|
|
174
|
+
* (identifier.rb flatten_scalar_components): `date` RENAMES to `year`,
|
|
175
|
+
* `edition` keeps its name; guards: never overwrite an emitted wire
|
|
176
|
+
* key, never rename onto a declared attribute name.
|
|
177
|
+
*/
|
|
178
|
+
flattenScalars(hash) {
|
|
179
|
+
const table = { ...FLAT_SCALAR_COMPONENTS, ...this.constructor.flatScalarComponents };
|
|
180
|
+
for (const [attrName, flatKey] of Object.entries(table)) {
|
|
181
|
+
const key = attrName in hash ? attrName : undefined;
|
|
182
|
+
if (key === undefined)
|
|
183
|
+
continue;
|
|
184
|
+
const value = hash[key];
|
|
185
|
+
const field = FLAT_SCALAR_FIELDS[attrName] ?? this.constructor.flatScalarFields?.[attrName];
|
|
186
|
+
if (field === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
const model = this.attrValue(attrName);
|
|
189
|
+
if (Array.isArray(value) && Array.isArray(model)) {
|
|
190
|
+
if (model.every((c) => c instanceof Component) && model.length === value.length) {
|
|
191
|
+
const scalars = model.map((c) => c.degenerateScalar(field));
|
|
192
|
+
if (scalars.every((s) => s !== undefined))
|
|
193
|
+
hash[key] = scalars;
|
|
194
|
+
}
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!(model instanceof Component))
|
|
198
|
+
continue;
|
|
199
|
+
const scalar = model.degenerateScalar(field);
|
|
200
|
+
if (scalar === undefined)
|
|
201
|
+
continue;
|
|
202
|
+
if (flatKey !== key) {
|
|
203
|
+
if (flatKey in hash || flatKey in this.classAttributes())
|
|
204
|
+
continue;
|
|
205
|
+
delete hash[key];
|
|
206
|
+
hash[flatKey] = scalar;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
hash[key] = scalar;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
static fromHash(hash) {
|
|
214
|
+
const klass = typeof hash["_type"] === "string" ? resolveType(hash["_type"]) : undefined;
|
|
215
|
+
if (klass && klass !== this) {
|
|
216
|
+
return klass.fromHash(hash);
|
|
217
|
+
}
|
|
218
|
+
const inflated = this.inflateScalarComponents(hash);
|
|
219
|
+
return new this(this.applyMappings(inflated));
|
|
220
|
+
}
|
|
221
|
+
/** Re-nest flat scalars into component hashes (identifier.rb inflate_scalar_components). */
|
|
222
|
+
static inflateScalarComponents(data) {
|
|
223
|
+
const klass = this;
|
|
224
|
+
if (!klass.attributes)
|
|
225
|
+
return data;
|
|
226
|
+
const convertedKeys = new Set((klass.mappings ?? []).map((m) => m.wire));
|
|
227
|
+
const out = { ...data };
|
|
228
|
+
for (const [attrName, flatKey] of Object.entries({ ...FLAT_SCALAR_COMPONENTS, ...klass.flatScalarComponents })) {
|
|
229
|
+
const spec = klass.attributes[attrName];
|
|
230
|
+
if (!spec || typeof spec.type === "string")
|
|
231
|
+
continue; // component attributes only
|
|
232
|
+
if (convertedKeys.has(flatKey))
|
|
233
|
+
continue;
|
|
234
|
+
if (flatKey !== attrName && flatKey in klass.attributes)
|
|
235
|
+
continue;
|
|
236
|
+
const value = out[flatKey];
|
|
237
|
+
if (value === undefined || value === null || typeof value === "object")
|
|
238
|
+
continue;
|
|
239
|
+
const field = FLAT_SCALAR_FIELDS[attrName];
|
|
240
|
+
if (Array.isArray(value))
|
|
241
|
+
continue; // scalar-list collections keep their name
|
|
242
|
+
delete out[flatKey];
|
|
243
|
+
out[attrName] = { [field]: String(value) };
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
/** Apply custom `fromWire` converters to the inflated hash. */
|
|
248
|
+
static applyMappings(data) {
|
|
249
|
+
const klass = this;
|
|
250
|
+
const mappings = klass.mappings;
|
|
251
|
+
if (!mappings)
|
|
252
|
+
return data;
|
|
253
|
+
const out = { ...data };
|
|
254
|
+
for (const m of mappings) {
|
|
255
|
+
if (m.fromWire && m.wire in out) {
|
|
256
|
+
const value = m.fromWire(out);
|
|
257
|
+
if (value !== undefined && value !== null)
|
|
258
|
+
out[m.to] = value;
|
|
259
|
+
}
|
|
260
|
+
else if (m.wire !== m.to && m.wire in out) {
|
|
261
|
+
out[m.to] = out[m.wire];
|
|
262
|
+
delete out[m.wire];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function deepEqual(a, b) {
|
|
269
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
270
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseUrnGenerator — the TS mirror of Pubid::UrnGenerator::Base
|
|
3
|
+
* (TODO.unified/01 §4). Flavors with a Ruby `urn_generator.rb` subclass
|
|
4
|
+
* and override hooks; flavors without one (doi, isbn, omg, un, gb) use
|
|
5
|
+
* this template directly, and the parts are derived from their declared
|
|
6
|
+
* attributes — never hand-assembled per flavor.
|
|
7
|
+
*/
|
|
8
|
+
import type { BaseIdentifier } from "./identifier.js";
|
|
9
|
+
type UrnableIdentifier = BaseIdentifier;
|
|
10
|
+
export declare class BaseUrnGenerator<T extends UrnableIdentifier = UrnableIdentifier> {
|
|
11
|
+
protected readonly identifier: T;
|
|
12
|
+
constructor(identifier: T);
|
|
13
|
+
generate(): string;
|
|
14
|
+
/** Template methods — override in subclasses (Ruby precedent). */
|
|
15
|
+
protected urnNamespace(): string;
|
|
16
|
+
/** Reads a DECLARED attribute only (Base#maybe) — constants never appear. */
|
|
17
|
+
protected maybe(name: string): unknown;
|
|
18
|
+
protected urnPublisher(): string | undefined;
|
|
19
|
+
protected urnType(): string | undefined;
|
|
20
|
+
protected urnNumber(): string | undefined;
|
|
21
|
+
protected urnPart(): string | undefined;
|
|
22
|
+
protected urnSubpart(): string | undefined;
|
|
23
|
+
protected urnYear(): string | undefined;
|
|
24
|
+
protected urnEdition(): string | undefined;
|
|
25
|
+
protected urnLanguage(): string | undefined;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseUrnGenerator — the TS mirror of Pubid::UrnGenerator::Base
|
|
3
|
+
* (TODO.unified/01 §4). Flavors with a Ruby `urn_generator.rb` subclass
|
|
4
|
+
* and override hooks; flavors without one (doi, isbn, omg, un, gb) use
|
|
5
|
+
* this template directly, and the parts are derived from their declared
|
|
6
|
+
* attributes — never hand-assembled per flavor.
|
|
7
|
+
*/
|
|
8
|
+
import { renderComponent } from "./component.js";
|
|
9
|
+
export class BaseUrnGenerator {
|
|
10
|
+
identifier;
|
|
11
|
+
constructor(identifier) {
|
|
12
|
+
this.identifier = identifier;
|
|
13
|
+
}
|
|
14
|
+
generate() {
|
|
15
|
+
const parts = ["urn", this.urnNamespace()];
|
|
16
|
+
const push = (v) => {
|
|
17
|
+
if (v !== undefined && v !== null && v !== "")
|
|
18
|
+
parts.push(v);
|
|
19
|
+
};
|
|
20
|
+
push(this.urnPublisher());
|
|
21
|
+
push(this.urnType());
|
|
22
|
+
push(this.urnNumber());
|
|
23
|
+
push(this.urnPart());
|
|
24
|
+
push(this.urnSubpart());
|
|
25
|
+
push(this.urnYear());
|
|
26
|
+
push(this.urnEdition());
|
|
27
|
+
push(this.urnLanguage());
|
|
28
|
+
return parts.join(":");
|
|
29
|
+
}
|
|
30
|
+
/** Template methods — override in subclasses (Ruby precedent). */
|
|
31
|
+
urnNamespace() {
|
|
32
|
+
// Pubid::<Flavor>::… → flavor name downcased; derived from _type.
|
|
33
|
+
const [, flavor] = this.identifier.constructor.polymorphicName.split(":");
|
|
34
|
+
return flavor ?? "unknown";
|
|
35
|
+
}
|
|
36
|
+
/** Reads a DECLARED attribute only (Base#maybe) — constants never appear. */
|
|
37
|
+
maybe(name) {
|
|
38
|
+
const attributes = this.identifier.constructor.attributes;
|
|
39
|
+
if (!attributes || !(name in attributes))
|
|
40
|
+
return undefined;
|
|
41
|
+
return this.identifier[name];
|
|
42
|
+
}
|
|
43
|
+
urnPublisher() {
|
|
44
|
+
const pub = this.maybe("publisher");
|
|
45
|
+
if (pub === undefined || pub === null)
|
|
46
|
+
return undefined;
|
|
47
|
+
return renderComponent(pub, "urn");
|
|
48
|
+
}
|
|
49
|
+
urnType() {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
urnNumber() {
|
|
53
|
+
const val = this.maybe("number") ?? this.maybe("code");
|
|
54
|
+
return val === undefined || val === null ? undefined : renderComponent(val, "urn");
|
|
55
|
+
}
|
|
56
|
+
urnPart() {
|
|
57
|
+
const val = this.maybe("part");
|
|
58
|
+
return val === undefined || val === null ? undefined : `-${renderComponent(val, "urn")}`;
|
|
59
|
+
}
|
|
60
|
+
urnSubpart() {
|
|
61
|
+
const val = this.maybe("subpart");
|
|
62
|
+
return val === undefined || val === null ? undefined : `-${renderComponent(val, "urn")}`;
|
|
63
|
+
}
|
|
64
|
+
urnYear() {
|
|
65
|
+
const date = this.maybe("date");
|
|
66
|
+
if (date !== undefined && date !== null && typeof date === "object" && "render" in date) {
|
|
67
|
+
const rendered = date.render("urn");
|
|
68
|
+
return rendered ?? undefined;
|
|
69
|
+
}
|
|
70
|
+
if (date !== undefined && date !== null)
|
|
71
|
+
return String(date);
|
|
72
|
+
const year = this.maybe("year");
|
|
73
|
+
return year === undefined || year === null ? undefined : String(year);
|
|
74
|
+
}
|
|
75
|
+
urnEdition() {
|
|
76
|
+
const ed = this.maybe("edition");
|
|
77
|
+
if (ed === undefined || ed === null)
|
|
78
|
+
return undefined;
|
|
79
|
+
const num = typeof ed === "object" && "number" in ed ? ed.number : ed;
|
|
80
|
+
return num === undefined || num === null || num === "" ? undefined : `ed.${String(num)}`;
|
|
81
|
+
}
|
|
82
|
+
urnLanguage() {
|
|
83
|
+
const langs = this.maybe("languages");
|
|
84
|
+
if (!Array.isArray(langs) || langs.length === 0)
|
|
85
|
+
return undefined;
|
|
86
|
+
return langs
|
|
87
|
+
.map((l) => renderComponent(l, "urn"))
|
|
88
|
+
.filter((s) => s !== undefined)
|
|
89
|
+
.join(",");
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubid/pubid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "TypeScript PubID identifiers: resolution, normalization and canonical hashes for every identifier in the pubid-testsuite corpus (41 standards bodies), with a conformance runner mirroring the Ruby reference",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|