Skip to content

Repository files navigation

JsMarc

License: GPL v3 TypeScript Zero Dependencies

A zero-dependency TypeScript library for parsing, filtering, extracting, and explaining bibliographic MARC (MAchine Readable Cataloging) records. Works in browsers and Node.js with no bundler.

JsMarc was created by Clément Corbin to make MARC data accessible from the web and the terminal.


Features

  • Parse MARC records — Convert raw ISO 2709 MARC data into structured objects, handling leader, directory, fields, and subfields.
  • Filter records — Extract matching records from a batch by field/subfield values (e.g. filter by ISBN).
  • Extract data — Pull specific fields from large record sets, with JSON output.
  • Explain fields — Annotate parsed records with human-readable labels using standard MARC definition files (MARC21, UNIMARC).
  • Search fields — Reverse-lookup field codes by keyword (e.g. search "author" to find all related fields).
  • Works everywhere — Browser (ES modules), Node.js (native ESM), and a command-line tool.
  • Batch processing — Parse entire .mrc files or piped records, not just single entries.

Installation

npm install jsmarc

Or for development, clone the repository:

git clone https://github.com/corbin-c/jsmarc.git
cd jsmarc
npm install

No bundler required. The source ships as TypeScript — run it directly with a TypeScript-aware runtime (tsx, ts-node, etc.) or compile it yourself.

Node.js

Use native ESM imports with a TypeScript runtime such as tsx:

import { parseRecord } from 'jsmarc'
import { explainRecord } from 'jsmarc/helper'

Quick Start

Node.js

import { parseRecord } from 'jsmarc'
import { explainRecord } from 'jsmarc/helper'

const record = parseRecord(rawMarcString)
const explained = await explainRecord(record, 'marc21')

console.log(explained)

CLI (one-liner)

# Fetch records and display with field explanations
curl "https://z3950.nibroc.fr/?server=lx2.loc.gov:210/LCDB&isbn=0066620724&format=usmarc" | npx marc-node display - --format=marc21

CLI Usage

marc-node COMMAND FILE [OPTIONS]

If FILE is -, the tool reads from stdin. Run directly with tsx if not using npx:

tsx marc-node.ts COMMAND FILE [OPTIONS]

Commands

Command Description
display Parse and display records (with optional field explanation via --format)
filter Filter records by field/subfield values
extract Extract specific fields as JSON
help Show usage information

Options

Option Syntax Default Description
--encoding string utf8 File encoding when reading from disk
--record-separator string \u001d Character that separates records in the batch
--field-separator string \u001e Field separator within a record
--subfield-separator string \u001f Subfield separator within a field
--format marc21 or unimarc Enrich display with field/subfield labels from definitions
--fields notation string * Field notation (e.g. 020$a,856$u) — use \ to escape $ in shells
--values comma-separated Values for filtering (quote values containing spaces)

Examples

Display all records with explained fields:

curl "https://z3950.nibroc.fr/?server=lx2.loc.gov:210/LCDB&isbn=0066620724,0596001312&format=usmarc" | npx marc-node display - --format=marc21

Limit display to specific fields:

npx marc-node display /path/to/records.mrc --fields=856\$u

Extract fields as JSON:

npx marc-node extract /path/to/records.mrc --fields=100\$a,020\$a

Output:

[
  {
    "leader": "01208cam a22003014a 4500",
    "fields": [
      { "code": "020", "indicator": "  ", "subfields": [{ "code": "a", "value": "0066620724 (hc)" }] },
      { "code": "100", "indicator": "1 ", "subfields": [{ "code": "a", "value": "Torvalds, Linus," }] }
    ]
  }
]

Filter records by value:

npx marc-node filter ./records.mrc --fields=020\$a --values=0596001312,"0066620724 (hc)"

Only records whose 020$a matches one of the given comma-separated values are kept. Output is raw MARC.

Pipe from stdin:

cat /path/to/records.mrc | npx marc-node display - --fields=245\$a

API Reference

All exports from the parser and helper modules.

src/parser.ts

parseRecord(recordString, options?)

Parse a raw MARC record string into a structured MarcParser object.

Parameter Type Default Description
recordString string required Raw ISO 2709 MARC record
options.toParse string | string[] "*" Field notation(s) to parse (e.g. "020\$a,856\$u" or ["020\$a"]). "*" parses all fields.
options.fields string "\u001e" Custom field separator
options.subfields string "\u001f" Custom subfield separator

Returns: MarcParser — an object with the following shape:

{
  rawRecord: string,        // the original record string
  leader: string,           // first 24 characters
  header: string,           // leader + directory + field separator
  fieldSeparator: string,   // field separator used
  subfieldSeparator: string,// subfield separator used
  parseCode: string,        // field notation filter applied
  directory: [{             // parsed directory entries
    code: string,           // field tag (e.g. "001")
    length: string,         // field length (zero-padded)
    position: string        // byte offset from start of body (zero-padded)
  }],
  fields: [{               // parsed fields
    code: string,           // field tag
    value?: string,         // raw value (control fields: 001-009)
    indicator?: string,     // two indicator characters (data fields: 010+)
    subfields?: [{          // parsed subfields (data fields only)
      code: string,         // subfield code (single character)
      value: string         // subfield data
    }]
  }]
}

filterRecord(parsedRecord, fieldNotation, values)

Check if a parsed record contains matching values for a given field.

Parameter Type Description
parsedRecord MarcParser A record returned by parseRecord()
fieldNotation string Field notation (e.g. "020\$a")
values string[] Values to match against

Returns: booleantrue if the record matches at least one value.

analyzeFieldNotation(notationString)

Parse a field notation string into a predicate function used internally by the parser.

Parameter Type Description
notationString string Comma-separated field notations (e.g. "020\$a,856\$u") or "*"

Returns: Function — a filter function (recordPart) => boolean that tests if a record part matches.

bin

Binary-safe string utility for byte-level length and slicing (handles multi-byte characters correctly in both browser and Node.js contexts).

bin.length(str) // → number (byte length)
bin.slice(str, start, end) // → string (byte-level slice)

MARC

Default configuration template with ISO 2709 separators:

MARC.recordSeparator // "\u001d"
MARC.fieldSeparator // "\u001e"
MARC.subfieldSeparator // "\u001f"

MarcParser

The parser class. Always prefer the parseRecord() wrapper unless you need the class directly.

src/helper.ts

explainRecord(parsedRecord, format)

Enrich a parsed record with human-readable labels from MARC definition files.

Parameter Type Description
parsedRecord MarcParser A record returned by parseRecord()
format string Format key from formats.json (e.g. "marc21", "unimarc")

Returns: Promise<object> — the record with added label properties on fields, subfields, and indicators.

explainField(field, format)

Same as explainRecord but operates on a single field object.

Returns: Promise<object> — the field object with added labels.

searchField(searchString, format)

Search for field/subfield codes by keyword in the definition files.

Parameter Type Description
searchString string Keyword to search (e.g. "author", "auteur")
format string Format key (e.g. "marc21", "unimarc")

Returns: Promise<Array<{code: string, value: string}>> — matching code/label pairs.

await searchField('auteur', 'unimarc')
// [
//   { code: "200\$c", value: "Titre propre d'un auteur différent" },
//   { code: "701\$4", value: "Auteur d'oeuvre adaptée ou continuée" },
//   ...
// ]

formats

A Promise that resolves to the loaded format definitions registry (from formats.json). Used internally — you typically access definitions through explainRecord or searchField.

Project Structure

jsmarc/
├── app/                    # Web application
│   ├── index.html
│   ├── front.js
│   └── style.css
├── definitions/            # MARC format definitions
│   ├── marc21.json
│   └── unimarc.json
├── samples/                # Sample MARC record files
├── src/                    # Core source modules
│   ├── parser.ts           # Main MARC record parser (TypeScript)
│   ├── parser.test.ts      # Vitest test suites
│   ├── helper.ts           # Field explanation & search via definitions
│   ├── helper.test.ts
│   ├── CLI.ts              # Terminal display utilities (TypeScript)
├── marc-node.ts            # Node.js CLI executable (TypeScript, published as bin)
├── formats.json            # Registry mapping format names to definition files
├── rec.mrc                 # Sample record file
├── package.json            # npm package definition
├── tsconfig.json           # TypeScript configuration
├── vitest.config.ts        # Vitest test runner configuration
└── LICENSE                 # GNU GPL v3

Supported Formats

JsMarc ships with definition files for two MARC variants:

Format Source License Language
MARC21 Library of Congress Public domain English
UNIMARC ABES (Agence Bibliographique de l'Enseignement Supérieur) CC BY-SA French

Definitions provide human-readable labels for every field, subfield, and indicator value. These labels power the explainRecord and searchField APIs as well as the CLI's --format flag.

Format Definitions

The formats.json file maps format names to their definition files:

{
  "marc21": "definitions/marc21.json",
  "unimarc": "definitions/unimarc.json"
}

Adding a custom format

  1. Create a JSON file in definitions/ following the existing schema (see definitions/marc21.json for reference):

    {
      "010": {
        "value": "Library of Congress Control Number",
        "ind1": { "#": "Undefined" },
        "ind2": { "#": "Undefined" },
        "subfields": {
          "a": { "*": "LC control number" }
        }
      }
    }
  2. Add an entry to formats.json:

    {
      "myformat": "definitions/myformat.json"
    }
  3. Use it immediately:

    npx marc-node display records.mrc --format=myformat

Web App

A full-featured web interface is hosted at corbin-c.github.io/jsmarc/. It supports batch record parsing, filtering, data extraction (HTML table or JSON), and field explanation on hover.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes — keep it dependency-free at runtime
  4. Run the test suite: npm test (Vitest)
  5. Run the type checker: npm run typecheck (TypeScript — tsc --noEmit)
  6. Submit a pull request

For major changes, consider opening an issue first to discuss your approach.

License

JsMarc is released under the GNU General Public License v3.0. You are free to use, modify, and distribute it under those terms.

Sample Records

The MARC records in samples/ and rec.mrc are provided for demonstration and educational purposes only. They were obtained from publicly accessible library catalogs and belong to their respective institutions (Library of Congress, SUDOC/ABES, OpenEdition, BnF). These files are not part of the JsMarc project and are not covered by the project's license. If you believe your rights are infringed, please open an issue.

Acknowledgments

  • Library of Congress for the MARC21 standard and public-domain field definitions
  • ABES for the UNIMARC field definitions (CC BY-SA)
  • The MARC standards community for decades of cataloging infrastructure
  • Web-Z3950 by the same author, used in CLI examples for fetching live records

About

JavaScript Marc Utility: A zero-dependency Vanilla JavaScript library for parsing, filtering, extracting, and explaining bibliographic MARC (MAchine Readable Cataloging) records.

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages