marc-ts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +642 -0
- package/dist/clone.d.ts +53 -0
- package/dist/convenience.d.ts +150 -0
- package/dist/field-ops.d.ts +166 -0
- package/dist/field-utils.d.ts +88 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +936 -0
- package/dist/index.js.map +1 -0
- package/dist/marc8.d.ts +39 -0
- package/dist/marcjson.cjs +3 -0
- package/dist/marcjson.cjs.map +1 -0
- package/dist/marcjson.d.ts +34 -0
- package/dist/marcjson.js +75 -0
- package/dist/marcjson.js.map +1 -0
- package/dist/marctxt.cjs +8 -0
- package/dist/marctxt.cjs.map +1 -0
- package/dist/marctxt.d.ts +22 -0
- package/dist/marctxt.js +88 -0
- package/dist/marctxt.js.map +1 -0
- package/dist/marcxml.cjs +8 -0
- package/dist/marcxml.cjs.map +1 -0
- package/dist/marcxml.d.ts +20 -0
- package/dist/marcxml.js +220 -0
- package/dist/marcxml.js.map +1 -0
- package/dist/parser.d.ts +43 -0
- package/dist/query.d.ts +38 -0
- package/dist/serializer.d.ts +57 -0
- package/dist/types-CJcxHJff.cjs +3 -0
- package/dist/types-CJcxHJff.cjs.map +1 -0
- package/dist/types-c4Mo9m9u.js +12 -0
- package/dist/types-c4Mo9m9u.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/warnings.d.ts +23 -0
- package/package.json +91 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marcxml.cjs","names":[],"sources":["../src/marcxml.ts"],"sourcesContent":["/**\n * MARCXML parser and serializer.\n *\n * Supports the Library of Congress MARCXML schema:\n * http://www.loc.gov/MARC21/slim\n *\n * Parsing is done with a hand-rolled state machine — no XML library needed.\n * The MARCXML format is sufficiently regular (fixed element names, no arbitrary\n * nesting) that a full DOM parser is unnecessary.\n */\n\nimport type { MarcRecord, ControlField, DataField, Subfield } from './types';\nimport { isControlField } from './types';\n\n// ─── XML entity handling ─────────────────────────────────────────────────────\n\nconst ENTITY_MAP: ReadonlyMap<string, string> = new Map([\n ['amp', '&'],\n ['lt', '<'],\n ['gt', '>'],\n ['quot', '\"'],\n ['apos', \"'\"],\n]);\n\nfunction unescapeXml(text: string): string {\n return text.replace(/&(?:#x([0-9a-fA-F]+)|#([0-9]+)|([a-zA-Z]+));/g, (_, hex, dec, name) => {\n if (hex !== undefined) {\n const cp = parseInt(hex, 16);\n return cp >= 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';\n }\n if (dec !== undefined) {\n const cp = parseInt(dec, 10);\n return cp >= 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';\n }\n return ENTITY_MAP.get(name) ?? _;\n });\n}\n\nfunction escapeXml(text: string): string {\n return text\n // XML 1.0 forbids most C0 control characters in document text. There is no\n // valid XML 1.0 representation for them, so substitute the Unicode\n // replacement character to keep the output well-formed.\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g, '�')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n // Preserve literal CR through the XML round-trip: XML parsers normalize\n // bare \\r and \\r\\n to \\n, so we must encode CR as a numeric reference.\n .replace(/\\r/g, ' ');\n}\n\n// ─── Minimal tokeniser ────────────────────────────────────────────────────────\n\ninterface Token {\n type: 'open' | 'close' | 'self-close' | 'text';\n /** Local name (no namespace prefix) */\n name?: string;\n attrs?: Record<string, string>;\n text?: string;\n}\n\n/**\n * Strip namespace prefix from a tag name, e.g. \"marc:record\" → \"record\".\n */\nfunction localName(raw: string): string {\n const colon = raw.indexOf(':');\n return colon === -1 ? raw : raw.slice(colon + 1);\n}\n\n/**\n * Parse `key=\"value\"` pairs out of an attribute string.\n */\nfunction parseAttrs(attrStr: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n const re = /([a-zA-Z_:][^\\s=]*)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(attrStr)) !== null) {\n const key = localName(m[1]!);\n attrs[key] = unescapeXml(m[2] ?? m[3] ?? '');\n }\n return attrs;\n}\n\n/**\n * Tokenise an XML string into a flat stream of open/close/text tokens.\n * Skips processing instructions, comments, and DOCTYPE declarations.\n * Sufficient for the well-constrained MARCXML format.\n */\nfunction tokenise(xml: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < xml.length) {\n const ltPos = xml.indexOf('<', i);\n\n // Text node before next tag\n if (ltPos === -1) {\n const text = xml.slice(i).trim();\n if (text) tokens.push({ type: 'text', text: unescapeXml(xml.slice(i)) });\n break;\n }\n\n if (ltPos > i) {\n const raw = xml.slice(i, ltPos);\n const text = raw.trim();\n if (text) tokens.push({ type: 'text', text: unescapeXml(raw) });\n }\n\n const gtPos = xml.indexOf('>', ltPos);\n if (gtPos === -1) break;\n\n const tag = xml.slice(ltPos + 1, gtPos);\n\n // Skip comments, PIs, DOCTYPE\n if (tag.startsWith('!') || tag.startsWith('?')) {\n i = gtPos + 1;\n continue;\n }\n\n if (tag.startsWith('/')) {\n tokens.push({ type: 'close', name: localName(tag.slice(1).trim()) });\n } else if (tag.endsWith('/')) {\n const inner = tag.slice(0, -1).trim();\n const spaceIdx = inner.search(/\\s/);\n const name = spaceIdx === -1 ? inner : inner.slice(0, spaceIdx);\n const attrStr = spaceIdx === -1 ? '' : inner.slice(spaceIdx);\n tokens.push({ type: 'self-close', name: localName(name), attrs: parseAttrs(attrStr) });\n } else {\n const spaceIdx = tag.search(/\\s/);\n const name = spaceIdx === -1 ? tag : tag.slice(0, spaceIdx);\n const attrStr = spaceIdx === -1 ? '' : tag.slice(spaceIdx);\n tokens.push({ type: 'open', name: localName(name), attrs: parseAttrs(attrStr) });\n }\n\n i = gtPos + 1;\n }\n\n return tokens;\n}\n\n// ─── MARCXML parser ───────────────────────────────────────────────────────────\n\n/**\n * Parse one `<record>` element's worth of tokens into a MarcRecord.\n * Mutates `pos` via the returned index.\n */\nfunction parseRecordTokens(tokens: Token[], start: number): { record: MarcRecord; end: number } {\n let leader = '';\n const fields: (ControlField | DataField)[] = [];\n let i = start;\n\n while (i < tokens.length) {\n const tok = tokens[i]!;\n\n if (tok.type === 'close' && tok.name === 'record') {\n return { record: { leader, fields }, end: i + 1 };\n }\n\n if (tok.type === 'open' && tok.name === 'leader') {\n i++;\n if (i < tokens.length && tokens[i]!.type === 'text') {\n leader = tokens[i]!.text!.trim();\n i++;\n }\n // consume </leader>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n continue;\n }\n\n if (tok.type === 'self-close' && tok.name === 'controlfield') {\n fields.push({ tag: tok.attrs?.['tag'] ?? '', data: '' });\n i++;\n continue;\n }\n\n if (tok.type === 'open' && tok.name === 'controlfield') {\n const tag = tok.attrs?.['tag'] ?? '';\n i++;\n let data = '';\n if (i < tokens.length && tokens[i]!.type === 'text') {\n data = tokens[i]!.text ?? '';\n i++;\n }\n // consume </controlfield>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n fields.push({ tag, data });\n continue;\n }\n\n if (tok.type === 'self-close' && tok.name === 'datafield') {\n fields.push({\n tag: tok.attrs?.['tag'] ?? '',\n indicator1: tok.attrs?.['ind1'] ?? ' ',\n indicator2: tok.attrs?.['ind2'] ?? ' ',\n subfields: [],\n });\n i++;\n continue;\n }\n\n if (tok.type === 'open' && tok.name === 'datafield') {\n const tag = tok.attrs?.['tag'] ?? '';\n const indicator1 = tok.attrs?.['ind1'] ?? ' ';\n const indicator2 = tok.attrs?.['ind2'] ?? ' ';\n const subfields: Subfield[] = [];\n i++;\n\n while (i < tokens.length) {\n const stok = tokens[i]!;\n if (stok.type === 'close' && stok.name === 'datafield') {\n i++;\n break;\n }\n if (stok.type === 'open' && stok.name === 'subfield') {\n const code = stok.attrs?.['code'] ?? '';\n i++;\n let value = '';\n if (i < tokens.length && tokens[i]!.type === 'text') {\n value = tokens[i]!.text ?? '';\n i++;\n }\n // consume </subfield>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n subfields.push({ code, value });\n continue;\n }\n i++;\n }\n\n fields.push({ tag, indicator1, indicator2, subfields });\n continue;\n }\n\n i++;\n }\n\n return { record: { leader, fields }, end: i };\n}\n\n/**\n * Parse a MARCXML string containing one `<collection>` or one bare `<record>`.\n * Returns all records found.\n */\nexport function parseMarcXml(xml: string): MarcRecord[] {\n const tokens = tokenise(xml);\n const records: MarcRecord[] = [];\n let i = 0;\n\n while (i < tokens.length) {\n const tok = tokens[i]!;\n if (tok.type === 'open' && tok.name === 'record') {\n const { record, end } = parseRecordTokens(tokens, i + 1);\n records.push(record);\n i = end;\n continue;\n }\n i++;\n }\n\n return records;\n}\n\n/**\n * Parse a MARCXML string expected to contain exactly one `<record>`.\n * Throws if no record is found.\n */\nexport function parseMarcXmlRecord(xml: string): MarcRecord {\n const records = parseMarcXml(xml);\n if (records.length === 0) throw new Error('No MARC record found in MARCXML input');\n return records[0]!;\n}\n\n// ─── MARCXML serializer ───────────────────────────────────────────────────────\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nconst COLLECTION_NS = 'xmlns=\"http://www.loc.gov/MARC21/slim\"';\nconst INDENT = ' ';\n\n/**\n * Serialize a single MarcRecord to a `<record>` XML element string (no collection wrapper).\n */\nexport function serializeMarcXmlRecord(record: MarcRecord): string {\n const lines: string[] = [`<record ${COLLECTION_NS}>`];\n lines.push(`${INDENT}<leader>${escapeXml(record.leader)}</leader>`);\n\n for (const field of record.fields) {\n if (isControlField(field)) {\n lines.push(`${INDENT}<controlfield tag=\"${field.tag}\">${escapeXml(field.data)}</controlfield>`);\n } else {\n const ind1 = field.indicator1 === ' ' ? ' ' : field.indicator1;\n const ind2 = field.indicator2 === ' ' ? ' ' : field.indicator2;\n lines.push(`${INDENT}<datafield tag=\"${field.tag}\" ind1=\"${ind1}\" ind2=\"${ind2}\">`);\n for (const sf of field.subfields) {\n lines.push(\n `${INDENT}${INDENT}<subfield code=\"${sf.code}\">${escapeXml(sf.value)}</subfield>`\n );\n }\n lines.push(`${INDENT}</datafield>`);\n }\n }\n\n lines.push('</record>');\n return lines.join('\\n');\n}\n\n/**\n * Serialize one or more MarcRecords into a MARCXML `<collection>` document.\n */\nexport function serializeMarcXml(records: MarcRecord[]): string {\n const parts: string[] = [\n XML_HEADER,\n `<collection ${COLLECTION_NS}>`,\n ];\n\n for (const record of records) {\n // Indent each record element by one level inside <collection>\n const recordXml = serializeMarcXmlRecord(record)\n .split('\\n')\n .map((line) => INDENT + line)\n .join('\\n');\n parts.push(recordXml);\n }\n\n parts.push('</collection>');\n return parts.join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MarcRecord } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a MARCXML string containing one `<collection>` or one bare `<record>`.
|
|
4
|
+
* Returns all records found.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseMarcXml(xml: string): MarcRecord[];
|
|
7
|
+
/**
|
|
8
|
+
* Parse a MARCXML string expected to contain exactly one `<record>`.
|
|
9
|
+
* Throws if no record is found.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseMarcXmlRecord(xml: string): MarcRecord;
|
|
12
|
+
/**
|
|
13
|
+
* Serialize a single MarcRecord to a `<record>` XML element string (no collection wrapper).
|
|
14
|
+
*/
|
|
15
|
+
export declare function serializeMarcXmlRecord(record: MarcRecord): string;
|
|
16
|
+
/**
|
|
17
|
+
* Serialize one or more MarcRecords into a MARCXML `<collection>` document.
|
|
18
|
+
*/
|
|
19
|
+
export declare function serializeMarcXml(records: MarcRecord[]): string;
|
|
20
|
+
//# sourceMappingURL=marcxml.d.ts.map
|
package/dist/marcxml.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { t as $ } from "./types-c4Mo9m9u.js";
|
|
2
|
+
var M = /* @__PURE__ */ new Map([
|
|
3
|
+
["amp", "&"],
|
|
4
|
+
["lt", "<"],
|
|
5
|
+
["gt", ">"],
|
|
6
|
+
["quot", '"'],
|
|
7
|
+
["apos", "'"]
|
|
8
|
+
]);
|
|
9
|
+
function h(e) {
|
|
10
|
+
return e.replace(/&(?:#x([0-9a-fA-F]+)|#([0-9]+)|([a-zA-Z]+));/g, (i, r, o, t) => {
|
|
11
|
+
if (r !== void 0) {
|
|
12
|
+
const n = parseInt(r, 16);
|
|
13
|
+
return n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : "�";
|
|
14
|
+
}
|
|
15
|
+
if (o !== void 0) {
|
|
16
|
+
const n = parseInt(o, 10);
|
|
17
|
+
return n >= 0 && n <= 1114111 ? String.fromCodePoint(n) : "�";
|
|
18
|
+
}
|
|
19
|
+
return M.get(t) ?? i;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function u(e) {
|
|
23
|
+
return e.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "�").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/\r/g, " ");
|
|
24
|
+
}
|
|
25
|
+
function f(e) {
|
|
26
|
+
const i = e.indexOf(":");
|
|
27
|
+
return i === -1 ? e : e.slice(i + 1);
|
|
28
|
+
}
|
|
29
|
+
function m(e) {
|
|
30
|
+
const i = {}, r = /([a-zA-Z_:][^\s=]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
31
|
+
let o;
|
|
32
|
+
for (; (o = r.exec(e)) !== null; ) {
|
|
33
|
+
const t = f(o[1]);
|
|
34
|
+
i[t] = h(o[2] ?? o[3] ?? "");
|
|
35
|
+
}
|
|
36
|
+
return i;
|
|
37
|
+
}
|
|
38
|
+
function v(e) {
|
|
39
|
+
const i = [];
|
|
40
|
+
let r = 0;
|
|
41
|
+
for (; r < e.length; ) {
|
|
42
|
+
const o = e.indexOf("<", r);
|
|
43
|
+
if (o === -1) {
|
|
44
|
+
e.slice(r).trim() && i.push({
|
|
45
|
+
type: "text",
|
|
46
|
+
text: h(e.slice(r))
|
|
47
|
+
});
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (o > r) {
|
|
51
|
+
const s = e.slice(r, o);
|
|
52
|
+
s.trim() && i.push({
|
|
53
|
+
type: "text",
|
|
54
|
+
text: h(s)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const t = e.indexOf(">", o);
|
|
58
|
+
if (t === -1) break;
|
|
59
|
+
const n = e.slice(o + 1, t);
|
|
60
|
+
if (n.startsWith("!") || n.startsWith("?")) {
|
|
61
|
+
r = t + 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (n.startsWith("/")) i.push({
|
|
65
|
+
type: "close",
|
|
66
|
+
name: f(n.slice(1).trim())
|
|
67
|
+
});
|
|
68
|
+
else if (n.endsWith("/")) {
|
|
69
|
+
const s = n.slice(0, -1).trim(), c = s.search(/\s/), l = c === -1 ? s : s.slice(0, c), p = c === -1 ? "" : s.slice(c);
|
|
70
|
+
i.push({
|
|
71
|
+
type: "self-close",
|
|
72
|
+
name: f(l),
|
|
73
|
+
attrs: m(p)
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
const s = n.search(/\s/), c = s === -1 ? n : n.slice(0, s), l = s === -1 ? "" : n.slice(s);
|
|
77
|
+
i.push({
|
|
78
|
+
type: "open",
|
|
79
|
+
name: f(c),
|
|
80
|
+
attrs: m(l)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
r = t + 1;
|
|
84
|
+
}
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
function w(e, i) {
|
|
88
|
+
let r = "";
|
|
89
|
+
const o = [];
|
|
90
|
+
let t = i;
|
|
91
|
+
for (; t < e.length; ) {
|
|
92
|
+
const n = e[t];
|
|
93
|
+
if (n.type === "close" && n.name === "record") return {
|
|
94
|
+
record: {
|
|
95
|
+
leader: r,
|
|
96
|
+
fields: o
|
|
97
|
+
},
|
|
98
|
+
end: t + 1
|
|
99
|
+
};
|
|
100
|
+
if (n.type === "open" && n.name === "leader") {
|
|
101
|
+
t++, t < e.length && e[t].type === "text" && (r = e[t].text.trim(), t++), t < e.length && e[t].type === "close" && t++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (n.type === "self-close" && n.name === "controlfield") {
|
|
105
|
+
o.push({
|
|
106
|
+
tag: n.attrs?.tag ?? "",
|
|
107
|
+
data: ""
|
|
108
|
+
}), t++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (n.type === "open" && n.name === "controlfield") {
|
|
112
|
+
const s = n.attrs?.tag ?? "";
|
|
113
|
+
t++;
|
|
114
|
+
let c = "";
|
|
115
|
+
t < e.length && e[t].type === "text" && (c = e[t].text ?? "", t++), t < e.length && e[t].type === "close" && t++, o.push({
|
|
116
|
+
tag: s,
|
|
117
|
+
data: c
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (n.type === "self-close" && n.name === "datafield") {
|
|
122
|
+
o.push({
|
|
123
|
+
tag: n.attrs?.tag ?? "",
|
|
124
|
+
indicator1: n.attrs?.ind1 ?? " ",
|
|
125
|
+
indicator2: n.attrs?.ind2 ?? " ",
|
|
126
|
+
subfields: []
|
|
127
|
+
}), t++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (n.type === "open" && n.name === "datafield") {
|
|
131
|
+
const s = n.attrs?.tag ?? "", c = n.attrs?.ind1 ?? " ", l = n.attrs?.ind2 ?? " ", p = [];
|
|
132
|
+
for (t++; t < e.length; ) {
|
|
133
|
+
const d = e[t];
|
|
134
|
+
if (d.type === "close" && d.name === "datafield") {
|
|
135
|
+
t++;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
if (d.type === "open" && d.name === "subfield") {
|
|
139
|
+
const x = d.attrs?.code ?? "";
|
|
140
|
+
t++;
|
|
141
|
+
let g = "";
|
|
142
|
+
t < e.length && e[t].type === "text" && (g = e[t].text ?? "", t++), t < e.length && e[t].type === "close" && t++, p.push({
|
|
143
|
+
code: x,
|
|
144
|
+
value: g
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
t++;
|
|
149
|
+
}
|
|
150
|
+
o.push({
|
|
151
|
+
tag: s,
|
|
152
|
+
indicator1: c,
|
|
153
|
+
indicator2: l,
|
|
154
|
+
subfields: p
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
t++;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
record: {
|
|
162
|
+
leader: r,
|
|
163
|
+
fields: o
|
|
164
|
+
},
|
|
165
|
+
end: t
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function b(e) {
|
|
169
|
+
const i = v(e), r = [];
|
|
170
|
+
let o = 0;
|
|
171
|
+
for (; o < i.length; ) {
|
|
172
|
+
const t = i[o];
|
|
173
|
+
if (t.type === "open" && t.name === "record") {
|
|
174
|
+
const { record: n, end: s } = w(i, o + 1);
|
|
175
|
+
r.push(n), o = s;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
o++;
|
|
179
|
+
}
|
|
180
|
+
return r;
|
|
181
|
+
}
|
|
182
|
+
function E(e) {
|
|
183
|
+
const i = b(e);
|
|
184
|
+
if (i.length === 0) throw new Error("No MARC record found in MARCXML input");
|
|
185
|
+
return i[0];
|
|
186
|
+
}
|
|
187
|
+
var A = `<?xml version="1.0" encoding="UTF-8"?>
|
|
188
|
+
`, y = 'xmlns="http://www.loc.gov/MARC21/slim"', a = " ";
|
|
189
|
+
function C(e) {
|
|
190
|
+
const i = [`<record ${y}>`];
|
|
191
|
+
i.push(`${a}<leader>${u(e.leader)}</leader>`);
|
|
192
|
+
for (const r of e.fields) if ($(r)) i.push(`${a}<controlfield tag="${r.tag}">${u(r.data)}</controlfield>`);
|
|
193
|
+
else {
|
|
194
|
+
const o = r.indicator1 === " " ? " " : r.indicator1, t = r.indicator2 === " " ? " " : r.indicator2;
|
|
195
|
+
i.push(`${a}<datafield tag="${r.tag}" ind1="${o}" ind2="${t}">`);
|
|
196
|
+
for (const n of r.subfields) i.push(`${a}${a}<subfield code="${n.code}">${u(n.value)}</subfield>`);
|
|
197
|
+
i.push(`${a}</datafield>`);
|
|
198
|
+
}
|
|
199
|
+
return i.push("</record>"), i.join(`
|
|
200
|
+
`);
|
|
201
|
+
}
|
|
202
|
+
function I(e) {
|
|
203
|
+
const i = [A, `<collection ${y}>`];
|
|
204
|
+
for (const r of e) {
|
|
205
|
+
const o = C(r).split(`
|
|
206
|
+
`).map((t) => a + t).join(`
|
|
207
|
+
`);
|
|
208
|
+
i.push(o);
|
|
209
|
+
}
|
|
210
|
+
return i.push("</collection>"), i.join(`
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
213
|
+
export {
|
|
214
|
+
b as parseMarcXml,
|
|
215
|
+
E as parseMarcXmlRecord,
|
|
216
|
+
I as serializeMarcXml,
|
|
217
|
+
C as serializeMarcXmlRecord
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
//# sourceMappingURL=marcxml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marcxml.js","names":[],"sources":["../src/marcxml.ts"],"sourcesContent":["/**\n * MARCXML parser and serializer.\n *\n * Supports the Library of Congress MARCXML schema:\n * http://www.loc.gov/MARC21/slim\n *\n * Parsing is done with a hand-rolled state machine — no XML library needed.\n * The MARCXML format is sufficiently regular (fixed element names, no arbitrary\n * nesting) that a full DOM parser is unnecessary.\n */\n\nimport type { MarcRecord, ControlField, DataField, Subfield } from './types';\nimport { isControlField } from './types';\n\n// ─── XML entity handling ─────────────────────────────────────────────────────\n\nconst ENTITY_MAP: ReadonlyMap<string, string> = new Map([\n ['amp', '&'],\n ['lt', '<'],\n ['gt', '>'],\n ['quot', '\"'],\n ['apos', \"'\"],\n]);\n\nfunction unescapeXml(text: string): string {\n return text.replace(/&(?:#x([0-9a-fA-F]+)|#([0-9]+)|([a-zA-Z]+));/g, (_, hex, dec, name) => {\n if (hex !== undefined) {\n const cp = parseInt(hex, 16);\n return cp >= 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';\n }\n if (dec !== undefined) {\n const cp = parseInt(dec, 10);\n return cp >= 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : '�';\n }\n return ENTITY_MAP.get(name) ?? _;\n });\n}\n\nfunction escapeXml(text: string): string {\n return text\n // XML 1.0 forbids most C0 control characters in document text. There is no\n // valid XML 1.0 representation for them, so substitute the Unicode\n // replacement character to keep the output well-formed.\n .replace(/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]/g, '�')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n // Preserve literal CR through the XML round-trip: XML parsers normalize\n // bare \\r and \\r\\n to \\n, so we must encode CR as a numeric reference.\n .replace(/\\r/g, ' ');\n}\n\n// ─── Minimal tokeniser ────────────────────────────────────────────────────────\n\ninterface Token {\n type: 'open' | 'close' | 'self-close' | 'text';\n /** Local name (no namespace prefix) */\n name?: string;\n attrs?: Record<string, string>;\n text?: string;\n}\n\n/**\n * Strip namespace prefix from a tag name, e.g. \"marc:record\" → \"record\".\n */\nfunction localName(raw: string): string {\n const colon = raw.indexOf(':');\n return colon === -1 ? raw : raw.slice(colon + 1);\n}\n\n/**\n * Parse `key=\"value\"` pairs out of an attribute string.\n */\nfunction parseAttrs(attrStr: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n const re = /([a-zA-Z_:][^\\s=]*)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(attrStr)) !== null) {\n const key = localName(m[1]!);\n attrs[key] = unescapeXml(m[2] ?? m[3] ?? '');\n }\n return attrs;\n}\n\n/**\n * Tokenise an XML string into a flat stream of open/close/text tokens.\n * Skips processing instructions, comments, and DOCTYPE declarations.\n * Sufficient for the well-constrained MARCXML format.\n */\nfunction tokenise(xml: string): Token[] {\n const tokens: Token[] = [];\n let i = 0;\n\n while (i < xml.length) {\n const ltPos = xml.indexOf('<', i);\n\n // Text node before next tag\n if (ltPos === -1) {\n const text = xml.slice(i).trim();\n if (text) tokens.push({ type: 'text', text: unescapeXml(xml.slice(i)) });\n break;\n }\n\n if (ltPos > i) {\n const raw = xml.slice(i, ltPos);\n const text = raw.trim();\n if (text) tokens.push({ type: 'text', text: unescapeXml(raw) });\n }\n\n const gtPos = xml.indexOf('>', ltPos);\n if (gtPos === -1) break;\n\n const tag = xml.slice(ltPos + 1, gtPos);\n\n // Skip comments, PIs, DOCTYPE\n if (tag.startsWith('!') || tag.startsWith('?')) {\n i = gtPos + 1;\n continue;\n }\n\n if (tag.startsWith('/')) {\n tokens.push({ type: 'close', name: localName(tag.slice(1).trim()) });\n } else if (tag.endsWith('/')) {\n const inner = tag.slice(0, -1).trim();\n const spaceIdx = inner.search(/\\s/);\n const name = spaceIdx === -1 ? inner : inner.slice(0, spaceIdx);\n const attrStr = spaceIdx === -1 ? '' : inner.slice(spaceIdx);\n tokens.push({ type: 'self-close', name: localName(name), attrs: parseAttrs(attrStr) });\n } else {\n const spaceIdx = tag.search(/\\s/);\n const name = spaceIdx === -1 ? tag : tag.slice(0, spaceIdx);\n const attrStr = spaceIdx === -1 ? '' : tag.slice(spaceIdx);\n tokens.push({ type: 'open', name: localName(name), attrs: parseAttrs(attrStr) });\n }\n\n i = gtPos + 1;\n }\n\n return tokens;\n}\n\n// ─── MARCXML parser ───────────────────────────────────────────────────────────\n\n/**\n * Parse one `<record>` element's worth of tokens into a MarcRecord.\n * Mutates `pos` via the returned index.\n */\nfunction parseRecordTokens(tokens: Token[], start: number): { record: MarcRecord; end: number } {\n let leader = '';\n const fields: (ControlField | DataField)[] = [];\n let i = start;\n\n while (i < tokens.length) {\n const tok = tokens[i]!;\n\n if (tok.type === 'close' && tok.name === 'record') {\n return { record: { leader, fields }, end: i + 1 };\n }\n\n if (tok.type === 'open' && tok.name === 'leader') {\n i++;\n if (i < tokens.length && tokens[i]!.type === 'text') {\n leader = tokens[i]!.text!.trim();\n i++;\n }\n // consume </leader>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n continue;\n }\n\n if (tok.type === 'self-close' && tok.name === 'controlfield') {\n fields.push({ tag: tok.attrs?.['tag'] ?? '', data: '' });\n i++;\n continue;\n }\n\n if (tok.type === 'open' && tok.name === 'controlfield') {\n const tag = tok.attrs?.['tag'] ?? '';\n i++;\n let data = '';\n if (i < tokens.length && tokens[i]!.type === 'text') {\n data = tokens[i]!.text ?? '';\n i++;\n }\n // consume </controlfield>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n fields.push({ tag, data });\n continue;\n }\n\n if (tok.type === 'self-close' && tok.name === 'datafield') {\n fields.push({\n tag: tok.attrs?.['tag'] ?? '',\n indicator1: tok.attrs?.['ind1'] ?? ' ',\n indicator2: tok.attrs?.['ind2'] ?? ' ',\n subfields: [],\n });\n i++;\n continue;\n }\n\n if (tok.type === 'open' && tok.name === 'datafield') {\n const tag = tok.attrs?.['tag'] ?? '';\n const indicator1 = tok.attrs?.['ind1'] ?? ' ';\n const indicator2 = tok.attrs?.['ind2'] ?? ' ';\n const subfields: Subfield[] = [];\n i++;\n\n while (i < tokens.length) {\n const stok = tokens[i]!;\n if (stok.type === 'close' && stok.name === 'datafield') {\n i++;\n break;\n }\n if (stok.type === 'open' && stok.name === 'subfield') {\n const code = stok.attrs?.['code'] ?? '';\n i++;\n let value = '';\n if (i < tokens.length && tokens[i]!.type === 'text') {\n value = tokens[i]!.text ?? '';\n i++;\n }\n // consume </subfield>\n if (i < tokens.length && tokens[i]!.type === 'close') i++;\n subfields.push({ code, value });\n continue;\n }\n i++;\n }\n\n fields.push({ tag, indicator1, indicator2, subfields });\n continue;\n }\n\n i++;\n }\n\n return { record: { leader, fields }, end: i };\n}\n\n/**\n * Parse a MARCXML string containing one `<collection>` or one bare `<record>`.\n * Returns all records found.\n */\nexport function parseMarcXml(xml: string): MarcRecord[] {\n const tokens = tokenise(xml);\n const records: MarcRecord[] = [];\n let i = 0;\n\n while (i < tokens.length) {\n const tok = tokens[i]!;\n if (tok.type === 'open' && tok.name === 'record') {\n const { record, end } = parseRecordTokens(tokens, i + 1);\n records.push(record);\n i = end;\n continue;\n }\n i++;\n }\n\n return records;\n}\n\n/**\n * Parse a MARCXML string expected to contain exactly one `<record>`.\n * Throws if no record is found.\n */\nexport function parseMarcXmlRecord(xml: string): MarcRecord {\n const records = parseMarcXml(xml);\n if (records.length === 0) throw new Error('No MARC record found in MARCXML input');\n return records[0]!;\n}\n\n// ─── MARCXML serializer ───────────────────────────────────────────────────────\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n';\nconst COLLECTION_NS = 'xmlns=\"http://www.loc.gov/MARC21/slim\"';\nconst INDENT = ' ';\n\n/**\n * Serialize a single MarcRecord to a `<record>` XML element string (no collection wrapper).\n */\nexport function serializeMarcXmlRecord(record: MarcRecord): string {\n const lines: string[] = [`<record ${COLLECTION_NS}>`];\n lines.push(`${INDENT}<leader>${escapeXml(record.leader)}</leader>`);\n\n for (const field of record.fields) {\n if (isControlField(field)) {\n lines.push(`${INDENT}<controlfield tag=\"${field.tag}\">${escapeXml(field.data)}</controlfield>`);\n } else {\n const ind1 = field.indicator1 === ' ' ? ' ' : field.indicator1;\n const ind2 = field.indicator2 === ' ' ? ' ' : field.indicator2;\n lines.push(`${INDENT}<datafield tag=\"${field.tag}\" ind1=\"${ind1}\" ind2=\"${ind2}\">`);\n for (const sf of field.subfields) {\n lines.push(\n `${INDENT}${INDENT}<subfield code=\"${sf.code}\">${escapeXml(sf.value)}</subfield>`\n );\n }\n lines.push(`${INDENT}</datafield>`);\n }\n }\n\n lines.push('</record>');\n return lines.join('\\n');\n}\n\n/**\n * Serialize one or more MarcRecords into a MARCXML `<collection>` document.\n */\nexport function serializeMarcXml(records: MarcRecord[]): string {\n const parts: string[] = [\n XML_HEADER,\n `<collection ${COLLECTION_NS}>`,\n ];\n\n for (const record of records) {\n // Indent each record element by one level inside <collection>\n const recordXml = serializeMarcXmlRecord(record)\n .split('\\n')\n .map((line) => INDENT + line)\n .join('\\n');\n parts.push(recordXml);\n }\n\n parts.push('</collection>');\n return parts.join('\\n');\n}\n"],"mappings":"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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { MarcRecord, ParseOptions, ParseResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a MARC21 record from ISO2709 binary format.
|
|
4
|
+
*
|
|
5
|
+
* @param buffer - The binary data to parse (use Uint8Array for browser compatibility)
|
|
6
|
+
* @param options - Parsing options (strict mode, max warnings)
|
|
7
|
+
* @returns Parse result containing the record and any warnings
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const buffer = new Uint8Array([...]); // MARC21 binary data
|
|
12
|
+
* const result = parseMarcRecord(buffer);
|
|
13
|
+
*
|
|
14
|
+
* if (result.record) {
|
|
15
|
+
* console.log('Parsed successfully');
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* if (result.warnings.length > 0) {
|
|
19
|
+
* console.warn('Warnings:', result.warnings);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseMarcRecord(buffer: Uint8Array, options?: ParseOptions): ParseResult;
|
|
24
|
+
/**
|
|
25
|
+
* Convenience wrapper for strict parsing.
|
|
26
|
+
* Throws an error if parsing fails.
|
|
27
|
+
*
|
|
28
|
+
* @param buffer - The binary data to parse
|
|
29
|
+
* @returns The parsed MARC record
|
|
30
|
+
* @throws Error if parsing fails
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* try {
|
|
35
|
+
* const record = parseMarcRecordStrict(buffer);
|
|
36
|
+
* console.log('Title:', title(record));
|
|
37
|
+
* } catch (error) {
|
|
38
|
+
* console.error('Parsing failed:', error);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseMarcRecordStrict(buffer: Uint8Array): MarcRecord;
|
|
43
|
+
//# sourceMappingURL=parser.d.ts.map
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MarcRecord, ControlField, DataField } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Get all fields matching a wildcard pattern.
|
|
4
|
+
*
|
|
5
|
+
* @param record - The MARC record to search
|
|
6
|
+
* @param pattern - The pattern to match (e.g., '6..', '7XX', '24X', 'X00')
|
|
7
|
+
* @returns Array of matching fields (empty if none found)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Get all 6XX subject fields
|
|
12
|
+
* const subjectFields = getFieldsByPattern(record, '6..');
|
|
13
|
+
*
|
|
14
|
+
* // Get all 7XX added entry fields
|
|
15
|
+
* const addedEntries = getFieldsByPattern(record, '7XX');
|
|
16
|
+
*
|
|
17
|
+
* // Get all X00 fields (100, 200, 300, etc.)
|
|
18
|
+
* const x00Fields = getFieldsByPattern(record, 'X00');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function getFieldsByPattern(record: MarcRecord, pattern: string): (ControlField | DataField)[];
|
|
22
|
+
/**
|
|
23
|
+
* Get the first field matching a wildcard pattern.
|
|
24
|
+
*
|
|
25
|
+
* @param record - The MARC record to search
|
|
26
|
+
* @param pattern - The pattern to match (e.g., '6..', '7XX')
|
|
27
|
+
* @returns The first matching field, or undefined if not found
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const firstSubject = getFirstFieldByPattern(record, '6..');
|
|
32
|
+
* if (firstSubject && isDataField(firstSubject)) {
|
|
33
|
+
* console.log('First subject:', getSubfield(firstSubject, 'a'));
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function getFirstFieldByPattern(record: MarcRecord, pattern: string): ControlField | DataField | undefined;
|
|
38
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { MarcRecord, MarcWarning } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Options for serializing MARC records.
|
|
4
|
+
*/
|
|
5
|
+
export interface SerializeOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Character encoding to use for field content.
|
|
8
|
+
* - 'utf8' (default): UTF-8; sets leader byte 9 to 'a'.
|
|
9
|
+
* - 'marc8': conservative MARC-8/ANSEL output; sets leader byte 9 to ' ' (space).
|
|
10
|
+
* Broad MARC-8 script decoding is supported by the parser, but encoding is limited
|
|
11
|
+
* to ASCII plus ANSEL Latin/combining characters.
|
|
12
|
+
*/
|
|
13
|
+
readonly encoding?: 'utf8' | 'marc8';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Serialize a MARC record to ISO2709 binary format.
|
|
17
|
+
*
|
|
18
|
+
* @param record - The MARC record to serialize
|
|
19
|
+
* @returns Binary representation of the record (Uint8Array for browser compatibility)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const record: MarcRecord = {
|
|
24
|
+
* leader: '00000nam 2200000 4500',
|
|
25
|
+
* fields: [
|
|
26
|
+
* { tag: '001', data: 'ocm12345678' },
|
|
27
|
+
* {
|
|
28
|
+
* tag: '245',
|
|
29
|
+
* indicator1: '1',
|
|
30
|
+
* indicator2: '0',
|
|
31
|
+
* subfields: [{ code: 'a', value: 'Title' }],
|
|
32
|
+
* },
|
|
33
|
+
* ],
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* const buffer = serializeMarcRecord(record);
|
|
37
|
+
* // Can now be written to file or transmitted
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function serializeMarcRecord(record: MarcRecord, options?: SerializeOptions): Uint8Array;
|
|
41
|
+
/**
|
|
42
|
+
* Result of {@link serializeMarcRecordWithWarnings}: the serialized bytes
|
|
43
|
+
* along with any warnings generated by the encoder (e.g. lossy MARC-8
|
|
44
|
+
* substitutions).
|
|
45
|
+
*/
|
|
46
|
+
export interface SerializeResult {
|
|
47
|
+
readonly bytes: Uint8Array;
|
|
48
|
+
readonly warnings: readonly MarcWarning[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Serialize a MARC record and surface any warnings generated by the encoder.
|
|
52
|
+
* Use this when you need programmatic visibility into lossy encodings — for
|
|
53
|
+
* example, MARC-8 output of records containing characters with no MARC-8
|
|
54
|
+
* equivalent.
|
|
55
|
+
*/
|
|
56
|
+
export declare function serializeMarcRecordWithWarnings(record: MarcRecord, options?: SerializeOptions): SerializeResult;
|
|
57
|
+
//# sourceMappingURL=serializer.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function t(e){return"data"in e}function n(e){return"subfields"in e}Object.defineProperty(exports,"isControlField",{enumerable:!0,get:function(){return t}});Object.defineProperty(exports,"isDataField",{enumerable:!0,get:function(){return n}});
|
|
2
|
+
|
|
3
|
+
//# sourceMappingURL=types-CJcxHJff.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-CJcxHJff.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Core type definitions for MARC21 records.\n * All types use readonly modifiers to enforce immutability.\n */\n\n/**\n * A subfield within a MARC data field.\n * Contains a single-character code and its associated value.\n *\n * @example\n * ```typescript\n * const subfield: Subfield = { code: 'a', value: 'The Catcher in the Rye' };\n * ```\n */\nexport interface Subfield {\n readonly code: string;\n readonly value: string;\n}\n\n/**\n * A MARC control field (tag 00X).\n * Control fields have no indicators or subfields, only a tag and data.\n *\n * @example\n * ```typescript\n * const controlField: ControlField = { tag: '001', data: 'ocm12345678' };\n * ```\n */\nexport interface ControlField {\n readonly tag: string;\n readonly data: string;\n}\n\n/**\n * A MARC data field (tag 01X-9XX).\n * Data fields have a tag, two indicators, and one or more subfields.\n *\n * @example\n * ```typescript\n * const dataField: DataField = {\n * tag: '245',\n * indicator1: '1',\n * indicator2: '0',\n * subfields: [\n * { code: 'a', value: 'The Catcher in the Rye /' },\n * { code: 'c', value: 'J.D. Salinger.' },\n * ],\n * };\n * ```\n */\nexport interface DataField {\n readonly tag: string;\n readonly indicator1: string; // Use ' ' for blank indicator\n readonly indicator2: string; // Use ' ' for blank indicator\n readonly subfields: readonly Subfield[];\n}\n\n/**\n * A complete MARC21 record.\n * Contains a 24-character leader and an array of fields (control or data fields).\n *\n * @example\n * ```typescript\n * const record: MarcRecord = {\n * leader: '00000nam 2200000 4500',\n * fields: [\n * { tag: '001', data: 'ocm12345678' },\n * {\n * tag: '245',\n * indicator1: '1',\n * indicator2: '0',\n * subfields: [{ code: 'a', value: 'Title' }],\n * },\n * ],\n * };\n * ```\n */\nexport interface MarcRecord {\n readonly leader: string; // Always 24 characters\n readonly fields: readonly (ControlField | DataField)[];\n}\n\n/**\n * Warning type categories for MARC parsing errors.\n */\nexport type MarcWarningType =\n | 'invalid_leader'\n | 'invalid_directory'\n | 'invalid_field'\n | 'truncated_record'\n | 'encoding_error';\n\n/**\n * A warning generated during MARC record parsing.\n * Warnings indicate non-fatal issues that were encountered and recovered from.\n */\nexport interface MarcWarning {\n readonly type: MarcWarningType;\n readonly message: string;\n readonly position?: number; // Byte position in the record\n readonly tag?: string; // Field tag associated with the warning\n}\n\n/**\n * Options for parsing MARC records.\n */\nexport interface ParseOptions {\n /**\n * If true, throw errors instead of collecting warnings.\n * Default: false\n */\n readonly strict?: boolean;\n\n /**\n * Maximum number of warnings to collect before stopping.\n * Prevents memory issues with severely malformed records.\n * Default: 100\n */\n readonly maxWarnings?: number;\n}\n\n/**\n * Result of parsing a MARC record.\n * Contains the parsed record (if successful) and any warnings encountered.\n */\nexport interface ParseResult {\n readonly record: MarcRecord | null;\n readonly warnings: readonly MarcWarning[];\n}\n\n/**\n * Type guard to check if a field is a control field.\n *\n * @param field - The field to check\n * @returns True if the field is a control field\n *\n * @example\n * ```typescript\n * if (isControlField(field)) {\n * console.log(field.data); // TypeScript knows field is ControlField\n * }\n * ```\n */\nexport function isControlField(field: ControlField | DataField): field is ControlField {\n return 'data' in field;\n}\n\n/**\n * Type guard to check if a field is a data field.\n *\n * @param field - The field to check\n * @returns True if the field is a data field\n *\n * @example\n * ```typescript\n * if (isDataField(field)) {\n * console.log(field.subfields); // TypeScript knows field is DataField\n * }\n * ```\n */\nexport function isDataField(field: ControlField | DataField): field is DataField {\n return 'subfields' in field;\n}\n"],"mappings":"AA+IA,SAAgB,EAAe,EAAwD,CACrF,MAAO,SAAU,CACnB,CAeA,SAAgB,EAAY,EAAqD,CAC/E,MAAO,cAAe,CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-c4Mo9m9u.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Core type definitions for MARC21 records.\n * All types use readonly modifiers to enforce immutability.\n */\n\n/**\n * A subfield within a MARC data field.\n * Contains a single-character code and its associated value.\n *\n * @example\n * ```typescript\n * const subfield: Subfield = { code: 'a', value: 'The Catcher in the Rye' };\n * ```\n */\nexport interface Subfield {\n readonly code: string;\n readonly value: string;\n}\n\n/**\n * A MARC control field (tag 00X).\n * Control fields have no indicators or subfields, only a tag and data.\n *\n * @example\n * ```typescript\n * const controlField: ControlField = { tag: '001', data: 'ocm12345678' };\n * ```\n */\nexport interface ControlField {\n readonly tag: string;\n readonly data: string;\n}\n\n/**\n * A MARC data field (tag 01X-9XX).\n * Data fields have a tag, two indicators, and one or more subfields.\n *\n * @example\n * ```typescript\n * const dataField: DataField = {\n * tag: '245',\n * indicator1: '1',\n * indicator2: '0',\n * subfields: [\n * { code: 'a', value: 'The Catcher in the Rye /' },\n * { code: 'c', value: 'J.D. Salinger.' },\n * ],\n * };\n * ```\n */\nexport interface DataField {\n readonly tag: string;\n readonly indicator1: string; // Use ' ' for blank indicator\n readonly indicator2: string; // Use ' ' for blank indicator\n readonly subfields: readonly Subfield[];\n}\n\n/**\n * A complete MARC21 record.\n * Contains a 24-character leader and an array of fields (control or data fields).\n *\n * @example\n * ```typescript\n * const record: MarcRecord = {\n * leader: '00000nam 2200000 4500',\n * fields: [\n * { tag: '001', data: 'ocm12345678' },\n * {\n * tag: '245',\n * indicator1: '1',\n * indicator2: '0',\n * subfields: [{ code: 'a', value: 'Title' }],\n * },\n * ],\n * };\n * ```\n */\nexport interface MarcRecord {\n readonly leader: string; // Always 24 characters\n readonly fields: readonly (ControlField | DataField)[];\n}\n\n/**\n * Warning type categories for MARC parsing errors.\n */\nexport type MarcWarningType =\n | 'invalid_leader'\n | 'invalid_directory'\n | 'invalid_field'\n | 'truncated_record'\n | 'encoding_error';\n\n/**\n * A warning generated during MARC record parsing.\n * Warnings indicate non-fatal issues that were encountered and recovered from.\n */\nexport interface MarcWarning {\n readonly type: MarcWarningType;\n readonly message: string;\n readonly position?: number; // Byte position in the record\n readonly tag?: string; // Field tag associated with the warning\n}\n\n/**\n * Options for parsing MARC records.\n */\nexport interface ParseOptions {\n /**\n * If true, throw errors instead of collecting warnings.\n * Default: false\n */\n readonly strict?: boolean;\n\n /**\n * Maximum number of warnings to collect before stopping.\n * Prevents memory issues with severely malformed records.\n * Default: 100\n */\n readonly maxWarnings?: number;\n}\n\n/**\n * Result of parsing a MARC record.\n * Contains the parsed record (if successful) and any warnings encountered.\n */\nexport interface ParseResult {\n readonly record: MarcRecord | null;\n readonly warnings: readonly MarcWarning[];\n}\n\n/**\n * Type guard to check if a field is a control field.\n *\n * @param field - The field to check\n * @returns True if the field is a control field\n *\n * @example\n * ```typescript\n * if (isControlField(field)) {\n * console.log(field.data); // TypeScript knows field is ControlField\n * }\n * ```\n */\nexport function isControlField(field: ControlField | DataField): field is ControlField {\n return 'data' in field;\n}\n\n/**\n * Type guard to check if a field is a data field.\n *\n * @param field - The field to check\n * @returns True if the field is a data field\n *\n * @example\n * ```typescript\n * if (isDataField(field)) {\n * console.log(field.subfields); // TypeScript knows field is DataField\n * }\n * ```\n */\nexport function isDataField(field: ControlField | DataField): field is DataField {\n return 'subfields' in field;\n}\n"],"mappings":"AA+IA,SAAgB,EAAe,GAAwD;AACrF,SAAO,UAAU;AACnB;AAeA,SAAgB,EAAY,GAAqD;AAC/E,SAAO,eAAe;AACxB"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for MARC21 records.
|
|
3
|
+
* All types use readonly modifiers to enforce immutability.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A subfield within a MARC data field.
|
|
7
|
+
* Contains a single-character code and its associated value.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const subfield: Subfield = { code: 'a', value: 'The Catcher in the Rye' };
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export interface Subfield {
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly value: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A MARC control field (tag 00X).
|
|
20
|
+
* Control fields have no indicators or subfields, only a tag and data.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const controlField: ControlField = { tag: '001', data: 'ocm12345678' };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface ControlField {
|
|
28
|
+
readonly tag: string;
|
|
29
|
+
readonly data: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A MARC data field (tag 01X-9XX).
|
|
33
|
+
* Data fields have a tag, two indicators, and one or more subfields.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const dataField: DataField = {
|
|
38
|
+
* tag: '245',
|
|
39
|
+
* indicator1: '1',
|
|
40
|
+
* indicator2: '0',
|
|
41
|
+
* subfields: [
|
|
42
|
+
* { code: 'a', value: 'The Catcher in the Rye /' },
|
|
43
|
+
* { code: 'c', value: 'J.D. Salinger.' },
|
|
44
|
+
* ],
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export interface DataField {
|
|
49
|
+
readonly tag: string;
|
|
50
|
+
readonly indicator1: string;
|
|
51
|
+
readonly indicator2: string;
|
|
52
|
+
readonly subfields: readonly Subfield[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A complete MARC21 record.
|
|
56
|
+
* Contains a 24-character leader and an array of fields (control or data fields).
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const record: MarcRecord = {
|
|
61
|
+
* leader: '00000nam 2200000 4500',
|
|
62
|
+
* fields: [
|
|
63
|
+
* { tag: '001', data: 'ocm12345678' },
|
|
64
|
+
* {
|
|
65
|
+
* tag: '245',
|
|
66
|
+
* indicator1: '1',
|
|
67
|
+
* indicator2: '0',
|
|
68
|
+
* subfields: [{ code: 'a', value: 'Title' }],
|
|
69
|
+
* },
|
|
70
|
+
* ],
|
|
71
|
+
* };
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export interface MarcRecord {
|
|
75
|
+
readonly leader: string;
|
|
76
|
+
readonly fields: readonly (ControlField | DataField)[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Warning type categories for MARC parsing errors.
|
|
80
|
+
*/
|
|
81
|
+
export type MarcWarningType = 'invalid_leader' | 'invalid_directory' | 'invalid_field' | 'truncated_record' | 'encoding_error';
|
|
82
|
+
/**
|
|
83
|
+
* A warning generated during MARC record parsing.
|
|
84
|
+
* Warnings indicate non-fatal issues that were encountered and recovered from.
|
|
85
|
+
*/
|
|
86
|
+
export interface MarcWarning {
|
|
87
|
+
readonly type: MarcWarningType;
|
|
88
|
+
readonly message: string;
|
|
89
|
+
readonly position?: number;
|
|
90
|
+
readonly tag?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options for parsing MARC records.
|
|
94
|
+
*/
|
|
95
|
+
export interface ParseOptions {
|
|
96
|
+
/**
|
|
97
|
+
* If true, throw errors instead of collecting warnings.
|
|
98
|
+
* Default: false
|
|
99
|
+
*/
|
|
100
|
+
readonly strict?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Maximum number of warnings to collect before stopping.
|
|
103
|
+
* Prevents memory issues with severely malformed records.
|
|
104
|
+
* Default: 100
|
|
105
|
+
*/
|
|
106
|
+
readonly maxWarnings?: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Result of parsing a MARC record.
|
|
110
|
+
* Contains the parsed record (if successful) and any warnings encountered.
|
|
111
|
+
*/
|
|
112
|
+
export interface ParseResult {
|
|
113
|
+
readonly record: MarcRecord | null;
|
|
114
|
+
readonly warnings: readonly MarcWarning[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Type guard to check if a field is a control field.
|
|
118
|
+
*
|
|
119
|
+
* @param field - The field to check
|
|
120
|
+
* @returns True if the field is a control field
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* if (isControlField(field)) {
|
|
125
|
+
* console.log(field.data); // TypeScript knows field is ControlField
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function isControlField(field: ControlField | DataField): field is ControlField;
|
|
130
|
+
/**
|
|
131
|
+
* Type guard to check if a field is a data field.
|
|
132
|
+
*
|
|
133
|
+
* @param field - The field to check
|
|
134
|
+
* @returns True if the field is a data field
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* if (isDataField(field)) {
|
|
139
|
+
* console.log(field.subfields); // TypeScript knows field is DataField
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function isDataField(field: ControlField | DataField): field is DataField;
|
|
144
|
+
//# sourceMappingURL=types.d.ts.map
|