marc-ts 0.1.0 → 0.4.2
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/README.md +118 -523
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +399 -358
- package/dist/index.js.map +1 -1
- package/dist/marcjson.cjs +1 -1
- package/dist/marcjson.cjs.map +1 -1
- package/dist/marcjson.d.ts +11 -6
- package/dist/marcjson.js +53 -44
- package/dist/marcjson.js.map +1 -1
- package/dist/marctxt.cjs +4 -4
- package/dist/marctxt.cjs.map +1 -1
- package/dist/marctxt.d.ts +0 -10
- package/dist/marctxt.js +44 -51
- package/dist/marctxt.js.map +1 -1
- package/dist/marcxml.cjs +6 -6
- package/dist/marcxml.cjs.map +1 -1
- package/dist/marcxml.d.ts +7 -11
- package/dist/marcxml.js +183 -129
- package/dist/marcxml.js.map +1 -1
- package/dist/parser.d.ts +15 -35
- package/dist/serializer.d.ts +16 -37
- package/dist/{types-c4Mo9m9u.js → types-BMKDHD1l.js} +1 -1
- package/dist/types-BMKDHD1l.js.map +1 -0
- package/dist/{types-CJcxHJff.cjs → types-CsOhH4OF.cjs} +1 -1
- package/dist/types-CsOhH4OF.cjs.map +1 -0
- package/dist/types.d.ts +23 -1
- package/dist/warnings-6yoB06xI.cjs +3 -0
- package/dist/warnings-6yoB06xI.cjs.map +1 -0
- package/dist/warnings-Bt6wvWFe.js +13 -0
- package/dist/warnings-Bt6wvWFe.js.map +1 -0
- package/dist/warnings.d.ts +1 -2
- package/package.json +2 -2
- package/dist/types-CJcxHJff.cjs.map +0 -1
- package/dist/types-c4Mo9m9u.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **Functional design** - Pure functions for composability and predictability
|
|
10
|
+
- **Four formats** — ISO2709 binary, MARCXML, MARC-in-JSON, MARCBreaker/marctxt
|
|
11
|
+
- **Consistent API** — every format uses `parse*(input) → MarcRecord[]` and `serialize*(records) → output`
|
|
12
|
+
- **Functional-style** — all operations return new objects; originals are never mutated
|
|
13
|
+
- **Zero dependencies** — no runtime deps, including no XML parser. MARCXML has only 5 element types with no arbitrary nesting, so it's parsed with a lightweight hand-rolled tokenizer instead of a full DOM/SAX library. Works in Node.js and modern browsers.
|
|
14
|
+
- **Fully typed** — strict TypeScript throughout
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
@@ -23,620 +22,216 @@ npm install marc-ts
|
|
|
23
22
|
## Quick Start
|
|
24
23
|
|
|
25
24
|
```typescript
|
|
26
|
-
import {
|
|
25
|
+
import { parseMarcBinary, serializeMarcBinary, title, author } from 'marc-ts';
|
|
27
26
|
import { parseMarcXml, serializeMarcXml } from 'marc-ts/xml';
|
|
28
27
|
import { parseMarcJson, serializeMarcJsonString } from 'marc-ts/json';
|
|
29
28
|
import { parseMarcTxt, serializeMarcTxt } from 'marc-ts/txt';
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const result = parseMarcRecord(buffer);
|
|
30
|
+
const records = parseMarcBinary(buffer);
|
|
31
|
+
console.log(title(records[0]));
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
console.log('Author:', author(result.record));
|
|
38
|
-
console.log('ISBNs:', isbn(result.record));
|
|
39
|
-
console.log('Subjects:', subjects(result.record));
|
|
40
|
-
}
|
|
41
|
-
if (result.warnings.length > 0) {
|
|
42
|
-
console.warn('Parsing warnings:', result.warnings);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Serialize back to binary (UTF-8 by default; pass { encoding: 'marc8' } for MARC-8 output)
|
|
46
|
-
const binary = serializeMarcRecord(result.record!, { encoding: 'utf8' });
|
|
47
|
-
|
|
48
|
-
// --- MARCXML ---
|
|
49
|
-
const xmlString = `<?xml version="1.0"?>
|
|
50
|
-
<collection xmlns="http://www.loc.gov/MARC21/slim">
|
|
51
|
-
<record>
|
|
52
|
-
<leader>00000nam a2200000 4500</leader>
|
|
53
|
-
<datafield tag="245" ind1="1" ind2="0">
|
|
54
|
-
<subfield code="a">The Hobbit</subfield>
|
|
55
|
-
</datafield>
|
|
56
|
-
</record>
|
|
57
|
-
</collection>`;
|
|
58
|
-
|
|
59
|
-
const [xmlRecord] = parseMarcXml(xmlString);
|
|
60
|
-
console.log('Title from XML:', title(xmlRecord));
|
|
61
|
-
const roundtripXml = serializeMarcXml([xmlRecord]); // back to a <collection> document
|
|
62
|
-
|
|
63
|
-
// --- MARC-in-JSON ---
|
|
64
|
-
const jsonString = JSON.stringify({
|
|
65
|
-
leader: '00000nam a2200000 4500',
|
|
66
|
-
fields: [
|
|
67
|
-
{ '245': { subfields: [{ a: 'The Hobbit' }], ind1: '1', ind2: '0' } },
|
|
68
|
-
],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const jsonRecord = parseMarcJson(jsonString);
|
|
72
|
-
console.log('Title from JSON:', title(jsonRecord));
|
|
73
|
-
const roundtripJson = serializeMarcJsonString(jsonRecord); // back to a JSON string
|
|
74
|
-
|
|
75
|
-
// --- MARCBreaker (marctxt) ---
|
|
76
|
-
const txtString = `=LDR 00000nam a2200000 4500
|
|
77
|
-
=001 5490
|
|
78
|
-
=245 10$aThe Hobbit /$cJ.R.R. Tolkien.
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
const [txtRecord] = parseMarcTxt(txtString);
|
|
82
|
-
console.log('Title from marctxt:', title(txtRecord));
|
|
83
|
-
const roundtripTxt = serializeMarcTxt([txtRecord]); // back to marctxt string
|
|
84
|
-
|
|
85
|
-
// --- MARC-8 binary ---
|
|
86
|
-
// parseMarcRecord detects MARC-8 automatically from leader byte 9 (' ');
|
|
87
|
-
// records are decoded to Unicode transparently — no special handling needed.
|
|
88
|
-
const marc8Buffer = new Uint8Array([...]); // MARC-8 encoded binary
|
|
89
|
-
const marc8Result = parseMarcRecord(marc8Buffer); // decoded to Unicode automatically
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Why marc-ts?
|
|
93
|
-
|
|
94
|
-
Existing JavaScript/TypeScript MARC libraries are often:
|
|
95
|
-
- Node.js-only (using streams, fs, Buffer APIs)
|
|
96
|
-
- Class-based OOP patterns that don't leverage TypeScript's strengths
|
|
97
|
-
- Mutable APIs that can lead to unexpected bugs
|
|
98
|
-
- Lacking comprehensive type definitions
|
|
99
|
-
|
|
100
|
-
**marc-ts** addresses these limitations:
|
|
101
|
-
- Universal browser and Node.js compatibility
|
|
102
|
-
- TypeScript-native with full type safety and functional patterns
|
|
103
|
-
- Immutable operations for safer, more predictable code
|
|
104
|
-
- Zero runtime dependencies for minimal bundle size
|
|
105
|
-
|
|
106
|
-
## Core Concepts
|
|
107
|
-
|
|
108
|
-
### Immutability
|
|
109
|
-
|
|
110
|
-
Mutation-style operations in **marc-ts** return new records or fields rather than modifying existing ones:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
const updated = appendField(record, field); // record remains unchanged
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
This approach prevents accidental mutations and makes code easier to reason about, especially in reactive frameworks like React or Vue.
|
|
117
|
-
|
|
118
|
-
### Functional API
|
|
33
|
+
const xmlRecords = parseMarcXml(xmlString);
|
|
34
|
+
const xml = serializeMarcXml(xmlRecords);
|
|
119
35
|
|
|
120
|
-
|
|
36
|
+
const jsonRecords = parseMarcJson(jsonString);
|
|
37
|
+
const json = serializeMarcJsonString(jsonRecords);
|
|
121
38
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const bookTitle = title(record);
|
|
125
|
-
const bookAuthor = author(record);
|
|
126
|
-
|
|
127
|
-
// Access fields functionally
|
|
128
|
-
const titleField = getField(record, '245');
|
|
39
|
+
const txtRecords = parseMarcTxt(txtString);
|
|
40
|
+
const txt = serializeMarcTxt(txtRecords);
|
|
129
41
|
```
|
|
130
42
|
|
|
131
|
-
|
|
43
|
+
## Formats
|
|
132
44
|
|
|
133
|
-
|
|
45
|
+
### ISO2709 Binary (`marc-ts`)
|
|
134
46
|
|
|
135
47
|
```typescript
|
|
136
|
-
import
|
|
137
|
-
import { isDataField } from 'marc-ts';
|
|
138
|
-
|
|
139
|
-
const field = getField(record, '245');
|
|
140
|
-
if (field && isDataField(field)) {
|
|
141
|
-
// TypeScript knows field is a DataField
|
|
142
|
-
const titleValue = getSubfield(field, 'a');
|
|
143
|
-
}
|
|
48
|
+
import { parseMarcBinary, serializeMarcBinary } from 'marc-ts';
|
|
144
49
|
```
|
|
145
50
|
|
|
146
|
-
|
|
51
|
+
#### `parseMarcBinary(buffer, options?): MarcRecord[]`
|
|
147
52
|
|
|
148
|
-
|
|
53
|
+
Splits on `0x1D` record terminators and parses each record. Failed records are skipped in lenient mode; `strict: true` throws on the first error.
|
|
149
54
|
|
|
150
|
-
|
|
55
|
+
| Option | Type | Default | Description |
|
|
56
|
+
|--------|------|---------|-------------|
|
|
57
|
+
| `strict` | `boolean` | `false` | Throw on fatal parse errors instead of skipping |
|
|
58
|
+
| `maxWarnings` | `number` | `100` | Stop collecting warnings after this many (per record) |
|
|
151
59
|
|
|
152
|
-
|
|
60
|
+
Leader byte 9 controls character decoding: `'a'` = UTF-8, `' '` = MARC-8. MARC-8 handles ANSEL Latin, Greek, Hebrew, Cyrillic, Arabic, and sub/superscript scripts. EACC/CJK coverage is minimal (~33 of ~16k triples) — prefer UTF-8 sources for CJK catalogs.
|
|
153
61
|
|
|
154
|
-
|
|
155
|
-
const result = parseMarcRecord(buffer, {
|
|
156
|
-
strict: false, // If true, throw on fatal parse errors
|
|
157
|
-
maxWarnings: 100, // Maximum warnings to collect
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
if (result.record) {
|
|
161
|
-
// Successfully parsed
|
|
162
|
-
} else {
|
|
163
|
-
// Parsing failed, check result.warnings
|
|
164
|
-
}
|
|
165
|
-
```
|
|
62
|
+
#### `parseMarcBinaryWithWarnings(buffer, options?): ParseBatchResult`
|
|
166
63
|
|
|
167
|
-
|
|
64
|
+
Same as `parseMarcBinary`, but returns per-record results with warnings. Failed records appear with `record: null`.
|
|
168
65
|
|
|
169
|
-
#### `
|
|
66
|
+
#### `serializeMarcBinary(records, options?): Uint8Array`
|
|
170
67
|
|
|
171
|
-
|
|
68
|
+
| Option | Type | Default | Description |
|
|
69
|
+
|--------|------|---------|-------------|
|
|
70
|
+
| `encoding` | `'utf8' \| 'marc8'` | `'utf8'` | Character encoding; `'marc8'` replaces unsupported Unicode with `?` |
|
|
71
|
+
| `maxWarnings` | `number` | `100` | Stop collecting warnings after this many (per record) |
|
|
172
72
|
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const record = parseMarcRecordStrict(buffer);
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error('Parsing failed:', error);
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
#### `serializeMarcRecord(record): Uint8Array`
|
|
182
|
-
|
|
183
|
-
Serialize a MARC record to ISO2709 binary format.
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
const buffer = serializeMarcRecord(record);
|
|
187
|
-
// Can be written to file or transmitted over network
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
`parseMarcRecord` decodes UTF-8 records and MARC-8 records signaled by leader
|
|
191
|
-
byte 9. MARC-8 decoding handles escape-designated scripts such as ANSEL Latin,
|
|
192
|
-
Greek, Hebrew, Cyrillic, Arabic, subscript/superscript, and mapped EACC/CJK
|
|
193
|
-
triples. MARC-8 serialization is intentionally conservative: `encoding:
|
|
194
|
-
'marc8'` writes ASCII plus ANSEL Latin/combining characters and replaces
|
|
195
|
-
unsupported Unicode characters with `?`.
|
|
196
|
-
|
|
197
|
-
**EACC coverage caveat:** the bundled EACC table maps only ~33 of the ~16,000
|
|
198
|
-
official triples. Records with substantial Chinese/Japanese/Korean content
|
|
199
|
-
will mostly decode to U+FFFD. For CJK catalogs, prefer UTF-8 sources
|
|
200
|
-
(`leader[9] === 'a'`).
|
|
201
|
-
|
|
202
|
-
**Surfacing lossy MARC-8 encoding:** because `serializeMarcRecord` returns a
|
|
203
|
-
plain `Uint8Array`, lossy substitutions are invisible to callers. Use
|
|
204
|
-
`serializeMarcRecordWithWarnings(record, { encoding: 'marc8' })` to get
|
|
205
|
-
`{ bytes, warnings }` — any character that could not be encoded surfaces as
|
|
206
|
-
an `encoding_error` warning. For just-the-encoder visibility, use
|
|
207
|
-
`unicodeToMarc8WithStats(text)` to get `{ bytes, lossyCount }`.
|
|
208
|
-
|
|
209
|
-
### Convenience Accessors
|
|
210
|
-
|
|
211
|
-
Extract common bibliographic metadata:
|
|
212
|
-
|
|
213
|
-
| Function | Field | Description | Example |
|
|
214
|
-
|----------|-------|-------------|---------|
|
|
215
|
-
| `title(record)` | 245 $a$b | Full title with subtitle | `"The Catcher in the Rye"` |
|
|
216
|
-
| `titleProper(record)` | 245 $a | Main title only | `"The Catcher in the Rye"` |
|
|
217
|
-
| `author(record)` | 100/110 $a | Main author/creator | `"Salinger, J. D."` |
|
|
218
|
-
| `edition(record)` | 250 $a | Edition statement | `"1st ed."` |
|
|
219
|
-
| `publisher(record)` | 260/264 $b | Publisher name | `"Little, Brown,"` |
|
|
220
|
-
| `publicationDate(record)` | 260/264 $c | Publication date | `"1951."` |
|
|
221
|
-
| `isbn(record)` | 020 $a | ISBN(s) - array | `["978-0-316-76948-0"]` |
|
|
222
|
-
| `issn(record)` | 022 $a | ISSN | `"0028-0836"` |
|
|
223
|
-
| `lccn(record)` | 010 $a | Library of Congress Control Number | `"50011915"` |
|
|
224
|
-
| `subjects(record)` | 6XX $a | All subject headings - array | `["Fiction", "History"]` |
|
|
225
|
-
| `seriesStatement(record)` | 490 $a | Series statement | `"Penguin classics"` |
|
|
226
|
-
|
|
227
|
-
### Field Access
|
|
228
|
-
|
|
229
|
-
#### `getField(record, tag): ControlField | DataField | undefined`
|
|
230
|
-
|
|
231
|
-
Get the first field with a specific tag.
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
const titleField = getField(record, '245');
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
#### `getFields(record, tag): (ControlField | DataField)[]`
|
|
238
|
-
|
|
239
|
-
Get all fields with a specific tag.
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
const subjectFields = getFields(record, '650');
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
#### `getSubfield(field, code): string | undefined`
|
|
246
|
-
|
|
247
|
-
Get the first subfield value from a data field.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
const field = getField(record, '245');
|
|
251
|
-
if (field && isDataField(field)) {
|
|
252
|
-
const titleValue = getSubfield(field, 'a');
|
|
253
|
-
}
|
|
254
|
-
```
|
|
73
|
+
#### `serializeMarcBinaryWithWarnings(records, options?): SerializeBatchResult`
|
|
255
74
|
|
|
256
|
-
|
|
75
|
+
Same as `serializeMarcBinary`, but returns per-record serialization warnings alongside the bytes.
|
|
257
76
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
const field = getField(record, '650');
|
|
262
|
-
if (field && isDataField(field)) {
|
|
263
|
-
const subdivisions = getSubfields(field, 'x');
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
#### `getAllSubfields(field): Array<{ code: string; value: string }>`
|
|
268
|
-
|
|
269
|
-
Get all subfields from a data field.
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
const field = getField(record, '245');
|
|
273
|
-
if (field && isDataField(field)) {
|
|
274
|
-
const allSubfields = getAllSubfields(field);
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Wildcard Querying
|
|
279
|
-
|
|
280
|
-
#### `getFieldsByPattern(record, pattern): (ControlField | DataField)[]`
|
|
77
|
+
---
|
|
281
78
|
|
|
282
|
-
|
|
79
|
+
### MARCXML (`marc-ts/xml`)
|
|
283
80
|
|
|
284
81
|
```typescript
|
|
285
|
-
|
|
286
|
-
const subjects = getFieldsByPattern(record, '6..');
|
|
287
|
-
|
|
288
|
-
// Get all 7XX added entry fields
|
|
289
|
-
const addedEntries = getFieldsByPattern(record, '7XX');
|
|
290
|
-
|
|
291
|
-
// Get all X00 fields (100, 200, ..., 900)
|
|
292
|
-
const x00Fields = getFieldsByPattern(record, 'X00');
|
|
82
|
+
import { parseMarcXml, serializeMarcXml } from 'marc-ts/xml';
|
|
293
83
|
```
|
|
294
84
|
|
|
295
|
-
#### `
|
|
296
|
-
|
|
297
|
-
Get the first field matching a wildcard pattern.
|
|
85
|
+
#### `parseMarcXml(xml): MarcRecord[]`
|
|
298
86
|
|
|
299
|
-
|
|
300
|
-
const firstSubject = getFirstFieldByPattern(record, '6..');
|
|
301
|
-
```
|
|
87
|
+
Accepts `<collection>`, bare `<record>` elements, or namespace-prefixed variants. Returns `[]` for empty input.
|
|
302
88
|
|
|
303
|
-
|
|
89
|
+
#### `serializeMarcXml(records): string`
|
|
304
90
|
|
|
305
|
-
|
|
91
|
+
Produces a full MARCXML `<collection>` document with XML declaration and MARC21 namespace.
|
|
306
92
|
|
|
307
|
-
|
|
93
|
+
---
|
|
308
94
|
|
|
309
|
-
|
|
95
|
+
### MARC-in-JSON (`marc-ts/json`)
|
|
310
96
|
|
|
311
97
|
```typescript
|
|
312
|
-
|
|
313
|
-
tag: '650',
|
|
314
|
-
indicator1: ' ',
|
|
315
|
-
indicator2: '0',
|
|
316
|
-
subfields: [{ code: 'a', value: 'New subject' }],
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const updated = appendField(record, newField);
|
|
320
|
-
// record is unchanged, updated has the new field
|
|
98
|
+
import { parseMarcJson, serializeMarcJson, serializeMarcJsonString } from 'marc-ts/json';
|
|
321
99
|
```
|
|
322
100
|
|
|
323
|
-
|
|
101
|
+
Implements the [MARC-in-JSON](https://wiki.code4lib.org/MARCJSONification) spec.
|
|
324
102
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
const updated = insertFieldBefore(record, '700', newField);
|
|
329
|
-
```
|
|
103
|
+
#### `parseMarcJson(json): MarcRecord[]`
|
|
330
104
|
|
|
331
|
-
|
|
105
|
+
Accepts a JSON string (array or single object), a `MarcJsonObject[]`, or a single `MarcJsonObject`.
|
|
332
106
|
|
|
333
|
-
|
|
107
|
+
#### `serializeMarcJson(records): MarcJsonObject[]`
|
|
334
108
|
|
|
335
|
-
|
|
336
|
-
const updated = insertFieldAfter(record, '245', newField);
|
|
337
|
-
```
|
|
109
|
+
#### `serializeMarcJsonString(records): string`
|
|
338
110
|
|
|
339
|
-
|
|
111
|
+
---
|
|
340
112
|
|
|
341
|
-
|
|
113
|
+
### MARCBreaker / marctxt (`marc-ts/txt`)
|
|
342
114
|
|
|
343
115
|
```typescript
|
|
344
|
-
|
|
345
|
-
// Field is inserted in proper MARC order
|
|
116
|
+
import { parseMarcTxt, serializeMarcTxt } from 'marc-ts/txt';
|
|
346
117
|
```
|
|
347
118
|
|
|
348
|
-
|
|
119
|
+
Line-oriented format: one field per line, blank lines between records, `$` for subfield delimiters, `\` for blank indicators.
|
|
349
120
|
|
|
350
|
-
|
|
121
|
+
Reserved characters are escaped: `$` → `{dollar}`, `{` → `{lcub}`, `}` → `{rcub}`, `\` → `{bsol}`.
|
|
351
122
|
|
|
352
|
-
|
|
353
|
-
const updated = removeFields(record, '650');
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
#### `removeField(record, field): MarcRecord`
|
|
123
|
+
#### `parseMarcTxt(text): MarcRecord[]`
|
|
357
124
|
|
|
358
|
-
|
|
125
|
+
#### `serializeMarcTxt(records): string`
|
|
359
126
|
|
|
360
|
-
|
|
361
|
-
const field = getField(record, '650');
|
|
362
|
-
const updated = field ? removeField(record, field) : record;
|
|
363
|
-
```
|
|
127
|
+
---
|
|
364
128
|
|
|
365
|
-
|
|
129
|
+
## Convenience Accessors
|
|
366
130
|
|
|
367
131
|
```typescript
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// Remove all subfields with code
|
|
372
|
-
const updated = removeSubfield(field, 'x');
|
|
373
|
-
|
|
374
|
-
// Replace first subfield with code
|
|
375
|
-
const updated = replaceSubfield(field, 'a', 'New value');
|
|
132
|
+
import { title, titleProper, author, edition, publisher, publicationDate,
|
|
133
|
+
isbn, issn, lccn, subjects, seriesStatement } from 'marc-ts';
|
|
376
134
|
```
|
|
377
135
|
|
|
378
|
-
|
|
136
|
+
| Function | Source field | Returns |
|
|
137
|
+
|----------|-------------|---------|
|
|
138
|
+
| `title(record)` | 245 $a$b | Full title with subtitle |
|
|
139
|
+
| `titleProper(record)` | 245 $a | Main title only |
|
|
140
|
+
| `author(record)` | 100/110 $a | Main author/creator |
|
|
141
|
+
| `edition(record)` | 250 $a | Edition statement |
|
|
142
|
+
| `publisher(record)` | 260/264 $b | Publisher name |
|
|
143
|
+
| `publicationDate(record)` | 260/264 $c | Publication date |
|
|
144
|
+
| `isbn(record)` | 020 $a | `string[]` of ISBNs |
|
|
145
|
+
| `issn(record)` | 022 $a | ISSN |
|
|
146
|
+
| `lccn(record)` | 010 $a | Library of Congress Control Number |
|
|
147
|
+
| `subjects(record)` | 6XX $a | `string[]` of subject headings |
|
|
148
|
+
| `seriesStatement(record)` | 490 $a | Series statement |
|
|
379
149
|
|
|
380
|
-
|
|
150
|
+
---
|
|
381
151
|
|
|
382
|
-
|
|
152
|
+
## Field Access
|
|
383
153
|
|
|
384
154
|
```typescript
|
|
385
|
-
|
|
386
|
-
|
|
155
|
+
import { getField, getFields, getSubfield, getSubfields, getAllSubfields } from 'marc-ts';
|
|
156
|
+
import { isControlField, isDataField } from 'marc-ts';
|
|
387
157
|
```
|
|
388
158
|
|
|
389
|
-
#### `recordsEqual(a, b, ignoreFieldOrder?): boolean`
|
|
390
|
-
|
|
391
|
-
Check if two records are equal.
|
|
392
|
-
|
|
393
159
|
```typescript
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Ignore field order
|
|
399
|
-
if (recordsEqual(record1, record2, true)) {
|
|
400
|
-
console.log('Records have same content');
|
|
401
|
-
}
|
|
402
|
-
```
|
|
160
|
+
const field = getField(record, '245'); // first match or undefined
|
|
161
|
+
const fields = getFields(record, '650'); // all matches
|
|
403
162
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
if (fieldsEqual(field1, field2)) {
|
|
410
|
-
console.log('Fields are identical');
|
|
163
|
+
if (field && isDataField(field)) {
|
|
164
|
+
const a = getSubfield(field, 'a');
|
|
165
|
+
const xs = getSubfields(field, 'x');
|
|
166
|
+
const all = getAllSubfields(field); // [{ code, value }, ...]
|
|
411
167
|
}
|
|
412
168
|
```
|
|
413
169
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
#### `createWarning(type, message, position?, tag?): MarcWarning`
|
|
417
|
-
|
|
418
|
-
Create a parsing warning object.
|
|
170
|
+
## Wildcard Querying
|
|
419
171
|
|
|
420
172
|
```typescript
|
|
421
|
-
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
## Additional Formats
|
|
425
|
-
|
|
426
|
-
### MARCXML (`marc-ts/xml`)
|
|
427
|
-
|
|
428
|
-
Import from the `marc-ts/xml` subpath for MARCXML support (Library of Congress schema).
|
|
173
|
+
import { getFieldsByPattern, getFirstFieldByPattern } from 'marc-ts';
|
|
429
174
|
|
|
430
|
-
|
|
431
|
-
import {
|
|
432
|
-
parseMarcXml,
|
|
433
|
-
parseMarcXmlRecord,
|
|
434
|
-
serializeMarcXml,
|
|
435
|
-
serializeMarcXmlRecord,
|
|
436
|
-
} from 'marc-ts/xml';
|
|
175
|
+
const subjects = getFieldsByPattern(record, '6..'); // all 6XX fields
|
|
437
176
|
```
|
|
438
177
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
Parse a MARCXML string containing a `<collection>` or one or more bare `<record>` elements.
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
const records = parseMarcXml(xmlString);
|
|
445
|
-
// Returns all records found in the document
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
#### `parseMarcXmlRecord(xml): MarcRecord`
|
|
449
|
-
|
|
450
|
-
Parse a MARCXML string expected to contain exactly one `<record>`. Throws if none is found.
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
const record = parseMarcXmlRecord(xmlString);
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
#### `serializeMarcXml(records): string`
|
|
457
|
-
|
|
458
|
-
Serialize one or more records into a full MARCXML `<collection>` document (with XML declaration).
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
const xml = serializeMarcXml([record1, record2]);
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### `serializeMarcXmlRecord(record): string`
|
|
465
|
-
|
|
466
|
-
Serialize a single record to a `<record>` XML element string (no collection wrapper or XML declaration).
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
const recordXml = serializeMarcXmlRecord(record);
|
|
470
|
-
```
|
|
178
|
+
`.` and `X` each match any single digit.
|
|
471
179
|
|
|
472
180
|
---
|
|
473
181
|
|
|
474
|
-
|
|
182
|
+
## Field Operations
|
|
475
183
|
|
|
476
|
-
|
|
184
|
+
All operations return new objects — originals are never mutated.
|
|
477
185
|
|
|
478
186
|
```typescript
|
|
479
187
|
import {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
} from 'marc-ts
|
|
484
|
-
import type { MarcJsonObject } from 'marc-ts/json';
|
|
485
|
-
```
|
|
188
|
+
appendField, insertFieldBefore, insertFieldAfter, insertGroupedField,
|
|
189
|
+
removeFields, removeField,
|
|
190
|
+
addSubfield, removeSubfield, replaceSubfield,
|
|
191
|
+
} from 'marc-ts';
|
|
486
192
|
|
|
487
|
-
|
|
193
|
+
const r1 = appendField(record, newField);
|
|
194
|
+
const r2 = insertFieldBefore(record, '700', newField);
|
|
195
|
+
const r3 = insertFieldAfter(record, '245', newField);
|
|
196
|
+
const r4 = insertGroupedField(record, newField); // maintains MARC tag order
|
|
197
|
+
const r5 = removeFields(record, '650');
|
|
198
|
+
const r6 = removeField(record, specificField); // reference equality
|
|
488
199
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
"fields": [
|
|
493
|
-
{ "001": "5490" },
|
|
494
|
-
{ "245": { "subfields": [{ "a": "The Hobbit" }], "ind1": "1", "ind2": "0" } }
|
|
495
|
-
]
|
|
496
|
-
}
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
#### `parseMarcJson(json): MarcRecord`
|
|
500
|
-
|
|
501
|
-
Parse a MARC-in-JSON object or JSON string into a `MarcRecord`. Throws on structural errors.
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
const record = parseMarcJson(jsonString); // from a JSON string
|
|
505
|
-
const record = parseMarcJson(jsonObject); // from a plain object
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
#### `serializeMarcJson(record): MarcJsonObject`
|
|
509
|
-
|
|
510
|
-
Serialize a `MarcRecord` to a MARC-in-JSON plain object.
|
|
511
|
-
|
|
512
|
-
```typescript
|
|
513
|
-
const obj = serializeMarcJson(record);
|
|
514
|
-
// obj.leader, obj.fields — ready for JSON.stringify or further processing
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
#### `serializeMarcJsonString(record): string`
|
|
518
|
-
|
|
519
|
-
Serialize a `MarcRecord` directly to a JSON string.
|
|
520
|
-
|
|
521
|
-
```typescript
|
|
522
|
-
const json = serializeMarcJsonString(record);
|
|
200
|
+
const f1 = addSubfield(field, 'b', 'Subtitle');
|
|
201
|
+
const f2 = removeSubfield(field, 'x');
|
|
202
|
+
const f3 = replaceSubfield(field, 'a', 'New value');
|
|
523
203
|
```
|
|
524
204
|
|
|
525
205
|
---
|
|
526
206
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
Import from the `marc-ts/txt` subpath for MARCBreaker support. This format (also called MARCMaker or marctxt) is a human-readable line-oriented representation originated by the Library of Congress MARCMaker/MARCBreaker tools and widely used for editing MARC data in plain text.
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
import {
|
|
533
|
-
parseMarcTxt,
|
|
534
|
-
parseMarcTxtRecord,
|
|
535
|
-
serializeMarcTxt,
|
|
536
|
-
serializeMarcTxtRecord,
|
|
537
|
-
} from 'marc-ts/txt';
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
Each field occupies one line. Blank indicators are written as `\`. Subfields use `$` followed by a single-character code. Records are separated by blank lines:
|
|
541
|
-
|
|
542
|
-
```
|
|
543
|
-
=LDR 00706cam a2200217 a 4500
|
|
544
|
-
=001 5490
|
|
545
|
-
=003 OCoLC
|
|
546
|
-
=245 14$aThe Hobbit /$cJ.R.R. Tolkien.
|
|
547
|
-
=650 \1$aHobbits (Fictitious characters)$vFiction.
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
**Value escaping.** Standard MARCBreaker has no way to represent a literal `$`
|
|
551
|
-
in a value, and `marc-ts` follows the same convention as other tools for the
|
|
552
|
-
remaining reserved characters:
|
|
553
|
-
|
|
554
|
-
- `$` → `{dollar}`
|
|
555
|
-
- `{` → `{lcub}`, `}` → `{rcub}`
|
|
556
|
-
- `\` → `{bsol}`
|
|
557
|
-
|
|
558
|
-
Embedded newlines (`\n`) in field values are replaced with a space on
|
|
559
|
-
serialize, matching the behavior of other MARCBreaker tools. Source values that
|
|
560
|
-
do not contain any of these characters are emitted verbatim.
|
|
561
|
-
|
|
562
|
-
#### `parseMarcTxt(text): MarcRecord[]`
|
|
563
|
-
|
|
564
|
-
Parse a marctxt string containing one or more records separated by blank lines. Accepts both `\n` and `\r\n` line endings.
|
|
565
|
-
|
|
566
|
-
```typescript
|
|
567
|
-
const records = parseMarcTxt(txtString);
|
|
568
|
-
// Returns all records found
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
#### `parseMarcTxtRecord(text): MarcRecord`
|
|
572
|
-
|
|
573
|
-
Parse a marctxt string expected to contain exactly one record. Throws if none is found.
|
|
207
|
+
## Clone and Equality
|
|
574
208
|
|
|
575
209
|
```typescript
|
|
576
|
-
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
#### `serializeMarcTxt(records): string`
|
|
210
|
+
import { cloneRecord, recordsEqual, fieldsEqual } from 'marc-ts';
|
|
580
211
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
212
|
+
const copy = cloneRecord(record);
|
|
213
|
+
recordsEqual(a, b); // strict field order
|
|
214
|
+
recordsEqual(a, b, true); // ignore field order
|
|
215
|
+
fieldsEqual(field1, field2);
|
|
585
216
|
```
|
|
586
217
|
|
|
587
|
-
|
|
218
|
+
---
|
|
588
219
|
|
|
589
|
-
|
|
220
|
+
## Types
|
|
590
221
|
|
|
591
222
|
```typescript
|
|
592
|
-
|
|
223
|
+
import type { MarcRecord, ControlField, DataField, Subfield,
|
|
224
|
+
ParseOptions, SerializeOptions, MarcWarning, MarcWarningType } from 'marc-ts';
|
|
593
225
|
```
|
|
594
226
|
|
|
595
227
|
---
|
|
596
228
|
|
|
597
|
-
## Browser Usage
|
|
598
|
-
|
|
599
|
-
**marc-ts** works in modern browsers without any bundler configuration:
|
|
600
|
-
|
|
601
|
-
```html
|
|
602
|
-
<!DOCTYPE html>
|
|
603
|
-
<html>
|
|
604
|
-
<head>
|
|
605
|
-
<title>marc-ts Browser Example</title>
|
|
606
|
-
</head>
|
|
607
|
-
<body>
|
|
608
|
-
<input type="file" id="fileInput" accept=".mrc" />
|
|
609
|
-
<pre id="output"></pre>
|
|
610
|
-
|
|
611
|
-
<script type="module">
|
|
612
|
-
import { parseMarcRecord, title, author } from 'https://cdn.skypack.dev/marc-ts';
|
|
613
|
-
|
|
614
|
-
document.getElementById('fileInput').addEventListener('change', async (e) => {
|
|
615
|
-
const file = e.target.files[0];
|
|
616
|
-
const arrayBuffer = await file.arrayBuffer();
|
|
617
|
-
const buffer = new Uint8Array(arrayBuffer);
|
|
618
|
-
|
|
619
|
-
const result = parseMarcRecord(buffer);
|
|
620
|
-
if (result.record) {
|
|
621
|
-
document.getElementById('output').textContent = `
|
|
622
|
-
Title: ${title(result.record) || 'N/A'}
|
|
623
|
-
Author: ${author(result.record) || 'N/A'}
|
|
624
|
-
`;
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
</script>
|
|
628
|
-
</body>
|
|
629
|
-
</html>
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
## Examples
|
|
633
|
-
|
|
634
|
-
See the [examples/](./examples/) directory for more examples:
|
|
635
|
-
- [basic-usage.ts](./examples/basic-usage.ts) - Common usage patterns
|
|
636
|
-
- [browser.html](./examples/browser.html) - Browser integration
|
|
637
|
-
|
|
638
229
|
## Development
|
|
639
230
|
|
|
640
|
-
Requires Node.js **20.19** or **22.12+** (driven by Vite 8).
|
|
641
|
-
|
|
642
|
-
|
|
231
|
+
Requires Node.js **20.19** or **22.12+** (driven by Vite 8).
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm test # run tests
|
|
235
|
+
npm run build # compile to dist/
|
|
236
|
+
npm run type-check # TypeScript check without emit
|
|
237
|
+
```
|