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
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
Copyright 2026 James Fournie <james.fournie@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# marc-ts
|
|
2
|
+
|
|
3
|
+
> TypeScript MARC21 library for Node.js and browsers
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/marc-ts)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Immutable API** - All operations return new objects, never mutate existing records
|
|
11
|
+
- **Zero runtime dependencies** - Works in browsers and Node.js (≥14) without any dependencies
|
|
12
|
+
- **Type-safe** - Full TypeScript type definitions with strict typing
|
|
13
|
+
- **Well-tested** - >90% code coverage with comprehensive test suite
|
|
14
|
+
- **Universal** - Runs in Node.js and modern browsers (Chrome, Firefox, Safari, Edge)
|
|
15
|
+
- **Functional design** - Pure functions for composability and predictability
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install marc-ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { parseMarcRecord, serializeMarcRecord, title, author, isbn, subjects } from 'marc-ts';
|
|
27
|
+
import { parseMarcXml, serializeMarcXml } from 'marc-ts/xml';
|
|
28
|
+
import { parseMarcJson, serializeMarcJsonString } from 'marc-ts/json';
|
|
29
|
+
import { parseMarcTxt, serializeMarcTxt } from 'marc-ts/txt';
|
|
30
|
+
|
|
31
|
+
// --- ISO 2709 binary (MARC21) ---
|
|
32
|
+
const buffer = new Uint8Array([...]); // Your MARC21 binary data
|
|
33
|
+
const result = parseMarcRecord(buffer);
|
|
34
|
+
|
|
35
|
+
if (result.record) {
|
|
36
|
+
console.log('Title:', title(result.record));
|
|
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
|
|
119
|
+
|
|
120
|
+
**marc-ts** uses pure functions for maximum composability:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Extract metadata using pure functions
|
|
124
|
+
const bookTitle = title(record);
|
|
125
|
+
const bookAuthor = author(record);
|
|
126
|
+
|
|
127
|
+
// Access fields functionally
|
|
128
|
+
const titleField = getField(record, '245');
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Type Safety
|
|
132
|
+
|
|
133
|
+
Full TypeScript types ensure compile-time correctness:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import type { MarcRecord, DataField } from 'marc-ts';
|
|
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
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## API Reference
|
|
147
|
+
|
|
148
|
+
### Parsing and Serialization
|
|
149
|
+
|
|
150
|
+
#### `parseMarcRecord(buffer, options?): ParseResult`
|
|
151
|
+
|
|
152
|
+
Parse ISO2709 binary data into a MARC record.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
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
|
+
```
|
|
166
|
+
|
|
167
|
+
Recoverable issues may still be returned in `warnings`, such as MARC leader compatibility warnings.
|
|
168
|
+
|
|
169
|
+
#### `parseMarcRecordStrict(buffer): MarcRecord`
|
|
170
|
+
|
|
171
|
+
Convenience wrapper for strict parsing (throws on fatal parse errors).
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
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
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `getSubfields(field, code): string[]`
|
|
257
|
+
|
|
258
|
+
Get all subfield values with a specific code (for repeatable subfields).
|
|
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)[]`
|
|
281
|
+
|
|
282
|
+
Match fields using wildcard patterns (`.` or `X` = any digit).
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Get all 6XX subject fields
|
|
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');
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### `getFirstFieldByPattern(record, pattern): ControlField | DataField | undefined`
|
|
296
|
+
|
|
297
|
+
Get the first field matching a wildcard pattern.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
const firstSubject = getFirstFieldByPattern(record, '6..');
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Field Operations (Immutable)
|
|
304
|
+
|
|
305
|
+
All operations return new records/fields without mutating the original.
|
|
306
|
+
|
|
307
|
+
#### `appendField(record, field): MarcRecord`
|
|
308
|
+
|
|
309
|
+
Append a field to the end of a record.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
const newField: DataField = {
|
|
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
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### `insertFieldBefore(record, tag, field): MarcRecord`
|
|
324
|
+
|
|
325
|
+
Insert a field before the first occurrence of a tag.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const updated = insertFieldBefore(record, '700', newField);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### `insertFieldAfter(record, tag, field): MarcRecord`
|
|
332
|
+
|
|
333
|
+
Insert a field after the first occurrence of a tag.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
const updated = insertFieldAfter(record, '245', newField);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### `insertGroupedField(record, field): MarcRecord`
|
|
340
|
+
|
|
341
|
+
Insert a field maintaining MARC block order (00X → 0XX → 1XX → ... → 9XX).
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
const updated = insertGroupedField(record, field);
|
|
345
|
+
// Field is inserted in proper MARC order
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### `removeFields(record, tag): MarcRecord`
|
|
349
|
+
|
|
350
|
+
Remove all fields with a specific tag.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
const updated = removeFields(record, '650');
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### `removeField(record, field): MarcRecord`
|
|
357
|
+
|
|
358
|
+
Remove a specific field instance using reference equality.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const field = getField(record, '650');
|
|
362
|
+
const updated = field ? removeField(record, field) : record;
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Subfield Operations
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// Add subfield to a field
|
|
369
|
+
const updated = addSubfield(field, 'b', 'Subtitle');
|
|
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');
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Clone and Equality
|
|
379
|
+
|
|
380
|
+
#### `cloneRecord(record): MarcRecord`
|
|
381
|
+
|
|
382
|
+
Create a deep copy of a record.
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
const copy = cloneRecord(record);
|
|
386
|
+
// Modifying copy will not affect record
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### `recordsEqual(a, b, ignoreFieldOrder?): boolean`
|
|
390
|
+
|
|
391
|
+
Check if two records are equal.
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
if (recordsEqual(record1, record2)) {
|
|
395
|
+
console.log('Records are identical');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Ignore field order
|
|
399
|
+
if (recordsEqual(record1, record2, true)) {
|
|
400
|
+
console.log('Records have same content');
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### `fieldsEqual(a, b): boolean`
|
|
405
|
+
|
|
406
|
+
Check if two fields are equal.
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
if (fieldsEqual(field1, field2)) {
|
|
410
|
+
console.log('Fields are identical');
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Warnings
|
|
415
|
+
|
|
416
|
+
#### `createWarning(type, message, position?, tag?): MarcWarning`
|
|
417
|
+
|
|
418
|
+
Create a parsing warning object.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const warning = createWarning('invalid_field', 'Field is out of bounds', 42, '245');
|
|
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).
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import {
|
|
432
|
+
parseMarcXml,
|
|
433
|
+
parseMarcXmlRecord,
|
|
434
|
+
serializeMarcXml,
|
|
435
|
+
serializeMarcXmlRecord,
|
|
436
|
+
} from 'marc-ts/xml';
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### `parseMarcXml(xml): MarcRecord[]`
|
|
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
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
### MARC-in-JSON (`marc-ts/json`)
|
|
475
|
+
|
|
476
|
+
Import from the `marc-ts/json` subpath for [MARC-in-JSON](https://wiki.code4lib.org/MARCJSONification) support (used by Open Library and many REST APIs).
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import {
|
|
480
|
+
parseMarcJson,
|
|
481
|
+
serializeMarcJson,
|
|
482
|
+
serializeMarcJsonString,
|
|
483
|
+
} from 'marc-ts/json';
|
|
484
|
+
import type { MarcJsonObject } from 'marc-ts/json';
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
The format represents each field as a single-key object in an array:
|
|
488
|
+
|
|
489
|
+
```json
|
|
490
|
+
{
|
|
491
|
+
"leader": "01142cam a2200301 a 4500",
|
|
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);
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
### MARCBreaker / marctxt (`marc-ts/txt`)
|
|
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.
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
const record = parseMarcTxtRecord(txtString);
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### `serializeMarcTxt(records): string`
|
|
580
|
+
|
|
581
|
+
Serialize one or more records into a marctxt string, with records separated by blank lines.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
const txt = serializeMarcTxt([record1, record2]);
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### `serializeMarcTxtRecord(record): string`
|
|
588
|
+
|
|
589
|
+
Serialize a single record to marctxt (no surrounding blank line).
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
const txt = serializeMarcTxtRecord(record);
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
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
|
+
## Development
|
|
639
|
+
|
|
640
|
+
Requires Node.js **20.19** or **22.12+** (driven by Vite 8). Older Node versions
|
|
641
|
+
are EOL and will fail to install the dev toolchain. The compiled output is
|
|
642
|
+
compatible with modern browsers and any actively-supported Node release.
|
package/dist/clone.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MarcRecord, ControlField, DataField } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a deep clone of a MARC record.
|
|
4
|
+
*
|
|
5
|
+
* @param record - The MARC record to clone
|
|
6
|
+
* @returns A new, independent copy of the record
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const copy = cloneRecord(record);
|
|
11
|
+
* // Modifying copy will not affect record
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function cloneRecord(record: MarcRecord): MarcRecord;
|
|
15
|
+
/**
|
|
16
|
+
* Check if two MARC records are deeply equal.
|
|
17
|
+
*
|
|
18
|
+
* @param a - First record to compare
|
|
19
|
+
* @param b - Second record to compare
|
|
20
|
+
* @param ignoreFieldOrder - If true, records with same fields in different order are considered equal
|
|
21
|
+
* @returns True if records are equal
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* if (recordsEqual(record1, record2)) {
|
|
26
|
+
* console.log('Records are identical');
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* // Ignore field order
|
|
30
|
+
* if (recordsEqual(record1, record2, true)) {
|
|
31
|
+
* console.log('Records have same content');
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function recordsEqual(a: MarcRecord, b: MarcRecord, ignoreFieldOrder?: boolean): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if two fields are equal.
|
|
38
|
+
*
|
|
39
|
+
* @param a - First field to compare
|
|
40
|
+
* @param b - Second field to compare
|
|
41
|
+
* @returns True if fields are equal
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const field1 = getField(record1, '245');
|
|
46
|
+
* const field2 = getField(record2, '245');
|
|
47
|
+
* if (field1 && field2 && fieldsEqual(field1, field2)) {
|
|
48
|
+
* console.log('Title fields are identical');
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function fieldsEqual(a: ControlField | DataField, b: ControlField | DataField): boolean;
|
|
53
|
+
//# sourceMappingURL=clone.d.ts.map
|