codex

package module
v0.26.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 4 Imported by: 0

README

libcodex

A dependency-free Go module for reading, writing and converting MARC 21 bibliographic records across four serializations: binary ISO 2709 (.mrc), MARCXML, MARC-in-JSON, and the MARCMaker mnemonic text format (.mrk).

  • Module path: github.com/freeeve/libcodex
  • Core package codex: the format-agnostic MARC model and the codec interfaces
  • One subpackage per serialization, all sharing the same surface
  • Standard library only — no third-party dependencies
  • Go 1.25+

The model is domain-agnostic: it exposes leaders, fields, subfields and indicators, and leaves interpretation of specific tags to the caller.

Install

go get github.com/freeeve/libcodex
import (
	"github.com/freeeve/libcodex"          // package codex — the model + interfaces
	"github.com/freeeve/libcodex/iso2709"  // a format codec (one of four)
)

(The core import path ends in libcodex; the package identifier is codex.)

Architecture

codex holds the shared in-memory model — Record, Field, Subfield, Leader — and two interfaces:

type RecordReader interface{ Read() (*Record, error) }  // io.EOF at end of stream
type RecordWriter interface{ Write(*Record) error }

Each serialization is a subpackage that maps that one model onto a wire format, so the same record round-trips through any of them and converts between any two.

Package Format Notes
iso2709 binary ISO 2709 (.mrc) the interchange format; fastest path
marcxml MARCXML (LoC slim schema) Writer wraps a <collection>; needs Close
marcjson MARC-in-JSON (pymarc layout) Writer emits a JSON array; needs Close
mrk MARCMaker mnemonic text human-readable, line-based

Every codec exposes the same surface:

NewReader(io.Reader) *Reader        Decode([]byte) (*codex.Record, …)
NewWriter(io.Writer) *Writer        Encode(*codex.Record) ([]byte, error)
ReadFile(path) ([]*codex.Record, …) WriteFile(path, []*codex.Record) error
(*Reader).All() iter.Seq2[*codex.Record, error]

Reading

Readers stream one record at a time. The API is identical for every format — just pick the package:

f, _ := os.Open("catalog.mrc")
defer f.Close()

r := iso2709.NewReader(f) // or marcxml.NewReader, marcjson.NewReader, mrk.NewReader
for rec, err := range r.All() {
	if err != nil {
		log.Println("bad record:", err) // iteration stops at the first error
		break
	}
	title := rec.SubfieldValue("245", 'a')
	author := rec.SubfieldValue("100", 'a')
	fmt.Printf("%s — %s (id %s)\n", title, author, rec.ControlField("001"))
	for _, subject := range rec.SubfieldValues("650", 'a') {
		fmt.Println("  subject:", subject)
	}
}

Convenience helpers per format: iso2709.ReadFile(path) and iso2709.Decode(raw) (the binary Decode also returns a lossy bool — see Character encoding).

Writing

Build a record from scratch and serialize it. Builders are chainable:

rec := codex.NewRecord().
	AddField(codex.NewControlField("001", "qll-00042")).
	AddField(codex.NewDataField("245", '1', '0',
		codex.NewSubfield('a', "Stone butch blues :"),
		codex.NewSubfield('b', "a novel"),
		codex.NewSubfield('c', "Leslie Feinberg."))).
	AddField(codex.NewDataField("650", ' ', '0',
		codex.NewSubfield('a', "Lesbians")))

raw, err := iso2709.Encode(rec) // or marcxml.Encode, marcjson.Encode, mrk.Encode

The marcxml and marcjson writers buffer a wrapper element, so call Close when finished:

w := marcxml.NewWriter(out)
for _, rec := range recs {
	w.Write(rec)
}
w.Close() // emits </collection>

Converting between formats

Because the codecs share interfaces, codex.Convert pipes any reader into any writer — that is the whole conversion engine:

// Binary MARC on stdin → MARCXML on stdout.
w := marcxml.NewWriter(os.Stdout)
if err := codex.Convert(iso2709.NewReader(os.Stdin), w); err != nil {
	log.Fatal(err)
}
w.Close()

Any of the 4 × 4 source/target combinations works and preserves the model.

Command-line tool (libcodex)

The same codecs are wrapped in a small CLI for shell use — inspecting, transcoding, validating and profiling records without writing Go:

go install github.com/freeeve/libcodex/cmd/libcodex@latest
libcodex cat       [-i fmt] [-t tags] [-n N] [--json] [file...]   readable dump
libcodex convert   [-i fmt] -o fmt [file...]                      transcode
libcodex validate  [-i fmt] [file...]                             structural check
libcodex stats     [-i fmt] [file...]                             field/leader report
libcodex skos      [-o fmt] [-n N] [file...]                      SKOS vocab view / to MARC authority

The input format is auto-detected from the leading bytes when -i is omitted, and with no file arguments each subcommand reads stdin, so commands compose in a pipe:

libcodex cat -t 084,650 catalog.mrc          # dump only classification/subject fields
libcodex convert -o bibframe catalog.mrc     # MARC → BIBFRAME RDF/XML on stdout
libcodex convert -o marcjson catalog.mrc | libcodex stats   # transcode, then profile

convert targets every registered output format (the four MARC serializations plus bibframe, mods, dublincore, schemaorg, ris and bibtex). See cmd/libcodex/README.md for the full format table and per-subcommand detail.

Export converters

Beyond the four round-trip serializations, the library exports to formats that use a different, lossy model — a documented MARC→target crosswalk, not a codec (bibframe also reads its graph back, see below). Their Writers implement codex.RecordWriter, so they also work with codex.Convert:

Package Target
mods MODS (LoC XML, near-lossless)
dublincore Dublin Core — oai_dc XML and DC-JSON
citation RIS and BibTeX (reference managers)
bibframe BIBFRAME 2.0 — RDF/XML, JSON-LD, Turtle, N-Triples (reads + writes)
schemaorg schema.org JSON-LD (Book, with a11y)
// Binary MARC → BibTeX for a reference manager.
codex.Convert(iso2709.NewReader(in), citation.NewBibTeXWriter(out))
// Or a single record: b, _ := mods.Encode(rec) / dublincore.Encode(rec) / citation.RIS(rec)

bibframe is the one that changes data model, not just serialization: each MARC record becomes a small RDF graph of a bf:Work (intellectual content) and a bf:Instance (a publication of it), linked by bf:instanceOf/bf:hasInstance. It reads and writes four RDF serializations — RDF/XML, JSON-LD, Turtle and N-Triples — all hand-written with the standard library, including the RDF parsers (no RDF dependency):

// Binary MARC → BIBFRAME, in any serialization.
b, _ := bibframe.Encode(rec)         // RDF/XML (canonical)
b, _ := bibframe.EncodeJSONLD(rec)   // JSON-LD
b, _ := bibframe.EncodeTurtle(rec)   // Turtle
b, _ := bibframe.EncodeNTriples(rec) // N-Triples
// Streaming collections: bibframe.NewWriter / NewJSONLDWriter / NewTurtleWriter /
// NewNTriplesWriter (the XML and JSON-LD writers wrap a container, so close them).

Unlike the other converters, bibframe also reads — a dependency-free RDF parser (all four serializations, autodetected) plus a BIBFRAME→MARC 21 reverse crosswalk turn a BIBFRAME graph back into records. It reads the vocabulary this library emits and the common shape of LoC marc2bibframe2 output:

recs, _ := bibframe.Decode(b)       // []*codex.Record; the format is detected
recs, _ := bibframe.ReadFile(path)
// As a Convert source: codex.Convert(bibframe.NewReader(in), iso2709.NewWriter(out))

BIBFRAME is a lossier model than MARC, so a decoded record carries the crosswalked fields rather than the original byte-for-byte; re-encoding it yields an equivalent graph. The other four converters (mods, dublincore, citation, schemaorg) are export-only and carry only the common fields; each package documents its crosswalk.

RDF toolkit (rdf)

The RDF machinery behind BIBFRAME is a standalone, dependency-free package you can use directly: the triple model (Term, Triple, Graph) and parsers and serializers for RDF/XML, JSON-LD, Turtle and N-Triples. In benchmarks it parses several times faster than the common third-party Go RDF libraries.

g, _ := rdf.ParseNTriples(data)          // also ParseTurtle / ParseRDFXML / ParseJSONLD
out := g.Turtle(map[string]string{...})  // serialize: NTriples() / Turtle(prefixes)

For inputs too large to hold in memory — multi-gigabyte dumps like the Library of Congress authority files — a streaming Decoder reads N-Triples, N-Quads, RDF/XML or Turtle from an io.Reader one triple at a time in constant memory:

d := rdf.NewDecoder(file, rdf.NTriples) // or rdf.RDFXML / rdf.Turtle / rdf.NQuads
for tr := range d.All() {               // or d.Decode() (rdf.Triple, error)
    // process tr; the whole graph is never materialized
}

It streams the 3.3 GB LCSH N-Triples file (23M triples) at ~800 MB/s with a live heap of a few megabytes, and holds RDF/XML and Turtle to a few MB regardless of file size. (JSON-LD is whole-document only — its tree must be materialized.)

Fetching records over SRU (sru)

SRU (Search/Retrieve via URL) is the HTTP search protocol library catalogs expose for copy-cataloging and discovery — the web successor to Z39.50. The sru client runs a searchRetrieve over net/http, parses the response, and hands the embedded records to the decoders above. Its Reader implements codex.RecordReader, so a catalog search is a Convert source. This is the one package that reaches the network; it remains standard library only.

c := sru.NewClient("http://lx2.loc.gov:210/lcdb")
rd := c.NewReader(ctx, `dc.title = `+sru.Quote("moby dick")) // CQL; pages automatically
codex.Convert(rd, marcjson.NewWriter(os.Stdout))            // stream hits into any format

// Or one page at a time, with counts and diagnostics:
resp, _ := c.SearchRetrieve(ctx, sru.Request{Query: `bath.isbn = "9780142437247"`})
rec, _ := resp.Records[0].Decode() // MARCXML -> *codex.Record

MARCXML records decode to *codex.Record; records in other schemas (MODS, Dublin Core) are returned as raw XML in Record.Data (those crosswalks are export-only). The client targets SRU 1.1/1.2.

Fetching records over Z39.50 (z3950)

For the many targets that predate SRU — legacy OPACs and classic ILS installs — the z3950 client speaks the original Z39.50 protocol (ISO 23950): BER-encoded APDUs over TCP, implemented from the published standard in pure Go. It runs Initialize/Search/Present sessions with Type-1 RPN queries over the bib-1 attribute set, built with a small query builder:

c := z3950.NewClient("lx2.loc.gov:210/LCDB") // host:port/database
rd := c.NewReader(ctx, z3950.Term("title", "moby dick"))
codex.Convert(rd, marcjson.NewWriter(os.Stdout)) // same pipeline as sru

// Or session-level control:
conn, _ := c.Connect(ctx)
defer conn.Close()
res, _ := conn.Search(ctx, z3950.And(z3950.Term("author", "melville"), z3950.Term("any", "whale")))
recs, _ := conn.Present(ctx, 1, 10) // fetch records 1-10

Records decode by their record syntax: MARC21 via iso2709 (including MARC-8 transcoding), UNIMARC via unimarc, MARCXML via marcxml; SUTRS text is exposed raw. Requesting Syntax: "opac" returns each bib record with its holdings (location, call number, circulation availability) attached as Record.Holdings -- the interlibrary-loan "who holds this" query. Query access points: any, title, author, subject, isbn, issn, lccn, id, combined with And/Or/AndNot. Multi-word terms automatically search as phrases, a trailing * right-truncates ("mob*" finds moby), and .Exact()/.Phrase()/.Word()/.Truncated() refine a term for strict servers. Guarded targets authenticate via Client.User/Password/Group (idPass) or Client.AuthOpen. Interop is tested against YAZ's yaz-ztest (skipped when YAZ is not installed) and, opt-in via Z3950_LIVE_TARGET, any live target.

Reading UNIMARC

UNIMARC (IFLA, used widely in Europe) shares the ISO 2709 container with MARC 21 but uses a different data dictionary and declares its character set in field 100, not the leader. The unimarc package reads it, selecting the encoding (UTF-8, or legacy ISO 5426 transcoded to UTF-8 like MARC-8) and mapping it to the MARC 21 model so it flows into every exporter above:

recs, _ := unimarc.ReadFile("catalog.unimarc")
title := unimarc.Title(recs[0])     // 200 $a, non-sort markers stripped
authors := unimarc.Authors(recs[0]) // 700/701/710 …

m := unimarc.ToMARC21(recs[0])      // re-tag 200→245, 010→020, 700→100, …
b, _ := schemaorg.Encode(m)         // …then any exporter accepts it

Reading SKOS vocabularies (skos)

Controlled vocabularies — subject thesauri like homosaurus, LCSH or FAST — are published as SKOS concept schemes in RDF, and they are the authority side of the subject headings the crosswalk reads in a bib record. The skos package parses a concept scheme (any RDF serialization, autodetected via the rdf package) and crosswalks each skos:Concept to a MARC 21 authority record:

concepts, _ := skos.Parse(data)     // []skos.Concept, broader/narrower resolved
rec := concepts[0].Record()         // MARC authority: 150 heading, 450/550 tracings, 024 URI
recs := skos.Records(concepts)      // …then any exporter accepts them

skos:prefLabel (English-preferred) becomes the 150 established heading, other labels 450 see-from tracings, broader/narrower/related the 550 see-also tracings ($w g/$w h for the hierarchy, with the target heading and $0 IRI), and the concept IRI a 024 … $2 uri. The libcodex skos command prints a concept view (with a summary header that surfaces IRI/scheme version drift) or converts to MARC authority records in any output format.

Accessors

On *Record:

  • Leader() Leader, Encoding() byte, Fields() []Field
  • ControlField(tag string) string
  • DataField(tag string) (Field, bool), DataFields(tag string) []Field
  • SubfieldValue(tag string, code byte) string, SubfieldValues(tag string, code byte) []string
  • Building/editing (chainable): AddField, InsertField (tag-ordered), ReplaceField, RemoveFields(tag), SetLeader
  • Validate() error — structural checks (24-byte leader, 3-byte tags, data fields have subfields)

On Field: IsControl(), Indicators() (byte, byte), Subfield(code), SubfieldValue(code), SubfieldValues(code).

On Leader: RecordStatus() (byte 5), RecordType() (byte 6), Encoding() (byte 9), IsUnicode(), RecordLength() ([0:5]), BaseAddress() ([12:17]).

Fixed fields, multilingual linkage and accessibility

Higher-level accessors interpret the harder-to-parse parts of a record:

  • Control008() — typed access to the positional 008 fixed field: Date1, Date2, Language, Place, and the material-aware FormOfItem (with IsLargePrint / IsBraille).

  • Vernacular (alternate-script) linkage — MARC pairs a romanized field with its original-script 880 via subfield $6. Field.Link() parses that linkage (tag, occurrence, script code, right-to-left), Record.AlternateGraphic(field) returns the linked partner in either direction, and Record.Vernacular(tag, code) reads the original-script value directly:

    title := rec.SubfieldValue("245", 'a')   // romanized
    original := rec.Vernacular("245", 'a')   // e.g. the Cyrillic or CJK form
    
  • Accessibility() — gathers the record's accessibility metadata (008 form of item, 007 tactile category, the 341 Accessibility Content and 532 Accessibility Note fields). The schemaorg exporter maps it to schema.org accessMode / accessibilityFeature / accessibilitySummary so reading systems and search engines can surface large-print, braille and captioned editions.

Performance

Encoding hand-builds output into a reusable buffer, so writers stream at roughly zero allocations per record. Decoding cost tracks the wire format. Indicative numbers for a ~10-field record (Apple M3 Max):

Format Decode (allocs / MB·s) Encode (allocs) Streaming write
iso2709 4 / 864 1 ~0 allocs/record
mrk 35 / 200 7 ~0 allocs/record
marcxml 374 / 60 9 ~0 allocs/record
marcjson 566 / 40 7 ~0 allocs/record

The binary codec is the fast path for bulk work; the XML/JSON decoders are bound by the standard library's encoding/xml and encoding/json tokenizers (a deliberate correctness-over-speed, zero-dependency choice). Run go test -bench=. in any subpackage to reproduce.

Character encoding

MARC 21 with UTF-8 (leader byte 9 == 'a') is the primary, preferred form. The text formats (MARCXML, MARC-in-JSON, .mrk) are UTF-8 throughout.

For older MARC-8 binary records (leader byte 9 == blank), iso2709 transcodes values to UTF-8 on read, so every value the API exposes is a UTF-8 Go string regardless of source encoding. Every MARC-8 graphic character set is supported:

  • Basic Latin (ASCII) and ANSEL Extended Latin, including spacing graphics and combining diacritics.
  • Basic and Extended Cyrillic, Basic and Extended Arabic, Basic Hebrew, Basic Greek, Greek Symbols, Subscripts and Superscripts.
  • the multibyte East Asian (CJK) set, EACC — all ~15,700 ideographs.

MARC-8 follows ISO 2022: a primary set in G0 governs bytes 0x21–0x7E and an extension set in G1 governs 0xA1–0xFE, with escape sequences re-designating either; combining marks are stored before their base (the reverse of Unicode), and the decoder reorders them, composing common Latin pairs to NFC (e.g. combining acute + eé). The non-Latin and EACC tables are generated directly from the LoC MARC-8 code tables (go generate ./internal/marc8); the hand-maintained ANSEL table is verified against them, including the 2005 alif remapping and the euro/eszett additions.

iso2709 also writes legacy MARC-8 (leader byte 9 = blank) via iso2709.EncodeMARC8, the inverse of the read path across all sets — emitting the ISO 2022 escape sequences to switch sets and returning to the defaults at the end of each value. It returns an error only if a value contains a character no MARC-8 set can represent (e.g. an emoji), so you never get a record that claims MARC-8 but holds untranscodable data.

A decode still never crashes on malformed input: an unrecognized set designation or an unmapped EACC triple passes through best-effort, and iso2709.Decode returns a lossy bool (and iso2709.Reader.Lossy() reports the last read) so callers can detect it and avoid re-serializing mojibake as clean UTF-8. Precomposed accented non-Latin text (e.g. NFC Greek ά) has no single MARC-8 code; supply it in decomposed (NFD) form to encode, which is exactly what decoding produces.

UNIMARC records use ISO 5426 (extended Latin) rather than MARC-8; the unimarc reader transcodes it the same way (combining mark before base, composed to NFC), selecting it from field 100. See Reading UNIMARC.

What each format rejects

A record is rejected on encode when a format cannot represent it, rather than producing corrupt output:

  • iso2709: a value, indicator or subfield code containing a reserved delimiter byte (0x1d/0x1e/0x1f); a field over 9999 bytes or a record over 99999.
  • marcxml: any character XML 1.0 forbids (control characters such as NUL).
  • mrk: a line break in any datum, or $ used as a subfield code.
  • marcjson: nothing — JSON can represent every character.

Compression

Compression composes through io; it is not built in. Wrap the stream:

gz, _ := gzip.NewReader(f)              // compress/gzip, stdlib
for rec, err := range iso2709.NewReader(gz).All() { … }

Adding your own format

Implement codex.RecordReader and/or codex.RecordWriter over *codex.Record in your own package — no changes to this module required. Your type then works with codex.Convert, codex.All, and everything else built on the interfaces. The four bundled codecs are the reference implementations.

Tolerance of malformed input

  • Decoders never panic on arbitrary bytes (verified by sustained fuzzing of every format).
  • iso2709.Reader.Read returns a non-EOF error for a malformed record but advances past it using the declared length, so reading can continue. Directory entries pointing outside the field data are skipped rather than failing the record.
  • The text readers skip blank/separator lines and unknown elements/keys.
  • ReadFile stops at the first malformed record and returns the records parsed so far together with the error.

License

MIT — see LICENSE. Copyright Eve Freeman.

Documentation

Overview

Package codex is a format-agnostic in-memory model for MARC 21 bibliographic records, shared by the serialization subpackages.

A Record is a 24-byte Leader plus an ordered list of Fields. A control field (tag below "010") holds raw data; a data field carries two indicators and zero or more Subfields. Every MARC serialization maps onto this one model, so the same record round-trips through any format.

The model is domain-agnostic: it exposes leaders, fields, subfields and indicators, and leaves interpretation of specific tags to the caller.

Serialization lives in subpackages, each implementing RecordReader and RecordWriter:

  • iso2709 — the binary ISO 2709 interchange format (.mrc)
  • marcxml — the Library of Congress MARCXML slim schema (planned)
  • marcjson — the MARC-in-JSON structure (planned)
  • mrk — the MARCMaker mnemonic text format (planned)

All values exposed by this package are UTF-8 Go strings; each codec is responsible for transcoding its wire encoding to and from UTF-8.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func All

func All(r RecordReader) iter.Seq2[*Record, error]

All returns an iterator over the records produced by r, for use as

for rec, err := range codex.All(reader) { ... }

It stops at the first error, yielding (nil, err) once; io.EOF ends iteration cleanly without yielding. It works with any RecordReader, so every format shares one iterator.

func Close added in v0.6.0

func Close(w RecordWriter) error

Close finalizes w when it needs it: if w has a Close method (a writer that buffers a wrapper, such as a marcxml <collection> or a JSON array), Close calls it and returns its error; otherwise Close is a no-op returning nil. Use it after Convert so a wrapper-buffering target completes its output.

func Convert

func Convert(r RecordReader, w RecordWriter) error

Convert reads every record from r and writes it to w, returning the first error encountered (io.EOF is the normal end and is not returned). Because it is written against the interfaces, it converts between any two formats. A writer that buffers a wrapper (e.g. a marcxml <collection> or a marcjson array) still needs finalizing afterward with Close to complete the output.

Example

ExampleConvert converts a binary ISO 2709 record to MARCMaker text using only the format-agnostic RecordReader and RecordWriter interfaces.

rec := codex.NewRecord().
	AddField(codex.NewControlField("001", "ex-1")).
	AddField(codex.NewDataField("245", '0', '0', codex.NewSubfield('a', "Example record")))

var binary bytes.Buffer
if err := iso2709.NewWriter(&binary).Write(rec); err != nil {
	log.Fatal(err)
}

if err := codex.Convert(iso2709.NewReader(&binary), mrk.NewWriter(os.Stdout)); err != nil {
	log.Fatal(err)
}
Output:
=LDR  00074nam a2200049   4500
=001  ex-1
=245  00$aExample record

func WriteAll added in v0.6.0

func WriteAll(w RecordWriter, records []*Record) error

WriteAll writes every record to w and then finalizes it with Close, so a wrapper-buffering writer completes its output. It is the loop each codec's WriteFile shares.

Types

type Accessibility added in v0.2.0

type Accessibility struct {
	LargePrint  bool     // 008 form of item 'd'
	Braille     bool     // 008 form of item 'f'
	Tactile     bool     // 007 category of material 'f' (tactile material)
	AccessModes []string // 341 $a content access modes (textual, visual, auditory, tactile)
	Features    []string // 341 $b-$e assistive features (textual, visual, auditory, tactile)
	Notes       []string // 532 $a accessibility notes
}

Accessibility summarizes the accessibility metadata a record carries: the 008 form of item, the 007 physical-description category, the 341 Accessibility Content field and the 532 Accessibility Note field. It is the MARC-side view a crosswalk (e.g. to schema.org accessibility properties) can map from.

func (Accessibility) Empty added in v0.2.0

func (a Accessibility) Empty() bool

Empty reports whether the record carried no accessibility metadata at all.

type Control008 added in v0.2.0

type Control008 struct {
	// contains filtered or unexported fields
}

Control008 provides typed access to the 008 fixed-length data elements. The header positions (00-17) and the trailing positions (35-39) are common to every material type; the material-specific block (18-34) is interpreted using the record's leader, which is why FormOfItem needs it.

MARC 21 fixed fields are positional, so an accessor returns the empty string or a zero byte when the underlying 008 is too short to contain the position, rather than panicking.

func (Control008) CatalogingSource added in v0.2.0

func (c Control008) CatalogingSource() byte

CatalogingSource returns position 39, the cataloging source code.

func (Control008) Date1 added in v0.2.0

func (c Control008) Date1() string

Date1 returns positions 07-10, the first date (often the publication year).

func (Control008) Date2 added in v0.2.0

func (c Control008) Date2() string

Date2 returns positions 11-14, the second date.

func (Control008) DateEntered added in v0.2.0

func (c Control008) DateEntered() string

DateEntered returns positions 00-05, the date the record was created (yymmdd).

func (Control008) DateType added in v0.2.0

func (c Control008) DateType() byte

DateType returns position 06, the type of date / publication status code.

func (Control008) FormOfItem added in v0.2.0

func (c Control008) FormOfItem() byte

FormOfItem returns the form-of-item code from the material-specific block: the byte at position 23 for most materials, or 29 for cartographic and visual materials. Codes include 'd' (large print), 'f' (braille), 'o' (online), 'q' (direct electronic) and 's' (electronic); see Control008.IsLargePrint and Control008.IsBraille for the accessibility-relevant ones.

func (Control008) IsBraille added in v0.2.0

func (c Control008) IsBraille() bool

IsBraille reports whether the form of item is braille (code 'f').

func (Control008) IsLargePrint added in v0.2.0

func (c Control008) IsLargePrint() bool

IsLargePrint reports whether the form of item is large print (code 'd').

func (Control008) Language added in v0.2.0

func (c Control008) Language() string

Language returns positions 35-37, the language code (MARC Code List for Languages / ISO 639-2/B), or "" if absent.

func (Control008) Place added in v0.2.0

func (c Control008) Place() string

Place returns positions 15-17, the place of publication, production or execution code.

func (Control008) String added in v0.2.0

func (c Control008) String() string

String returns the raw 008 value.

type Field

type Field struct {
	Tag       string
	Ind1      byte
	Ind2      byte
	Value     string
	Subfields []Subfield
}

Field is a single MARC field. A control field (Tag < "010") carries raw data in Value and has no indicators or subfields. A data field (Tag >= "010") carries two indicators and zero or more subfields. The conventional blank indicator is the ASCII space (' '); an unset (zero) data-field indicator is treated as a blank space when the field is serialized.

func NewControlField

func NewControlField(tag, value string) Field

NewControlField constructs a control field (e.g. 001, 003, 008) holding raw data. The tag is not validated here; values below "010" are treated as control fields by the reader and writer.

func NewDataField

func NewDataField(tag string, ind1, ind2 byte, subfields ...Subfield) Field

NewDataField constructs a data field with the given tag, two indicators and subfields. A blank indicator is conventionally the ASCII space (' ').

func (Field) Indicators

func (f Field) Indicators() (byte, byte)

Indicators returns the field's two indicator bytes. For control fields both are zero.

func (Field) IsControl

func (f Field) IsControl() bool

IsControl reports whether the field is a control field (tag below "010").

func (f Field) Link() (Linkage, bool)

Link parses the field's subfield $6 linkage, returning false if it has none or the linkage is malformed. The reference must be exactly "TAG-OO": a 3-character tag, a hyphen, and two occurrence digits, optionally followed by "/" segments carrying a script code and orientation.

func (Field) Subfield

func (f Field) Subfield(code byte) (string, bool)

Subfield returns the value of the first subfield with the given code and reports whether one was found.

func (Field) SubfieldValue

func (f Field) SubfieldValue(code byte) string

SubfieldValue returns the value of the first subfield with the given code, or the empty string if none is present.

func (Field) SubfieldValues

func (f Field) SubfieldValues(code byte) []string

SubfieldValues returns the values of every subfield with the given code, in order, or nil when none match. It sizes the result to the match count so a field with several matching subfields allocates once with no regrowth.

type Leader

type Leader string

Leader is the 24-byte MARC record leader. Helper methods decode the fields this package needs; the raw bytes are available via String.

func (Leader) BaseAddress

func (l Leader) BaseAddress() int

BaseAddress returns the base address of data declared in leader bytes [12:17]. It returns 0 if those bytes are not a valid number.

func (Leader) BibLevel

func (l Leader) BibLevel() byte

BibLevel returns leader byte 7 (the bibliographic level: 'm' monograph, 's' serial, 'a' monographic component part, etc.), or 0 if the leader is malformed.

func (Leader) Encoding

func (l Leader) Encoding() byte

Encoding returns leader byte 9, the character coding scheme: 'a' for UTF-8 (Unicode) or blank for MARC-8.

func (Leader) IsUnicode

func (l Leader) IsUnicode() bool

IsUnicode reports whether the leader declares UTF-8 (Unicode) encoding.

func (Leader) RecordLength

func (l Leader) RecordLength() int

RecordLength returns the total record length declared in leader bytes [0:5]. It returns 0 if those bytes are not a valid number.

func (Leader) RecordStatus

func (l Leader) RecordStatus() byte

RecordStatus returns leader byte 5 (the record status), or 0 if the leader is malformed.

func (Leader) RecordType

func (l Leader) RecordType() byte

RecordType returns leader byte 6 (the type of record), or 0 if the leader is malformed.

func (Leader) String

func (l Leader) String() string

String returns the leader as a 24-byte string.

type Linkage added in v0.2.0

type Linkage struct {
	Tag         string // the linked field's tag (e.g. "880" in a regular field)
	Occurrence  string // two-digit occurrence number; "00" means no linked field
	Script      string // script identification code (e.g. "(N" Cyrillic, "$1" CJK), or ""
	RightToLeft bool   // field orientation code 'r'
}

Linkage is the parsed content of a subfield $6, which ties a field to its alternate-script equivalent in field 880. In a regular field $6 points at the 880 occurrence ("880-01"); in the 880 field it points back at the regular tag ("245-01/(N/r"), optionally carrying a script identification code and a right-to-left field orientation.

func (Linkage) Linked added in v0.2.0

func (l Linkage) Linked() bool

Linked reports whether the linkage refers to an actual partner field (a nonzero occurrence number).

func (Linkage) ScriptName added in v0.2.0

func (l Linkage) ScriptName() string

ScriptName returns a human-readable name for the $6 script identification code, or "" if absent or unrecognized. The codes are the MARC-8 set designations.

type Record

type Record struct {
	// contains filtered or unexported fields
}

Record is a parsed MARC record: its leader and ordered fields.

func NewRecord

func NewRecord() *Record

NewRecord creates an empty record with a syntactically valid default leader (UTF-8 encoding). Fields are added with AddField.

func NewRecordCap

func NewRecordCap(n int) *Record

NewRecordCap creates an empty record like NewRecord but with space preallocated for n fields. Codecs that know the field count up front use it to avoid reallocating the field slice while appending.

func ReadAll added in v0.6.0

func ReadAll(r RecordReader) ([]*Record, error)

ReadAll reads every record from r until io.EOF and returns them in order, or the records read so far together with the first error. It is the loop each codec's ReadFile shares.

func (*Record) Accessibility added in v0.2.0

func (r *Record) Accessibility() Accessibility

Accessibility gathers the record's accessibility metadata from the 008, 007, 341 and 532 fields. The result is always valid (its zero value means none was found); see Accessibility.Empty.

func (*Record) AddField

func (r *Record) AddField(f Field) *Record

AddField appends a field to the record and returns the record for chaining.

func (*Record) AlternateGraphic added in v0.2.0

func (r *Record) AlternateGraphic(f Field) (Field, bool)

AlternateGraphic returns the field linked to f through subfield $6: the 880 field when f is a regular field, or the regular field when f is an 880. It matches on the tag and occurrence number and returns false when f carries no linkage or no partner is present.

func (*Record) Control008 added in v0.2.0

func (r *Record) Control008() (Control008, bool)

Control008 returns the parsed 008 control field, or false if the record has no 008 or one too short to hold the common header (18 characters).

func (*Record) ControlField

func (r *Record) ControlField(tag string) string

ControlField returns the raw value of the first control field with the given tag, or the empty string if none is present.

func (*Record) DataField

func (r *Record) DataField(tag string) (Field, bool)

DataField returns the first data field with the given tag and reports whether one was found.

func (*Record) DataFields

func (r *Record) DataFields(tag string) []Field

DataFields returns every data field with the given tag, in order.

func (*Record) Encoding

func (r *Record) Encoding() byte

Encoding returns the record's declared character encoding (leader byte 9).

func (*Record) Fields

func (r *Record) Fields() []Field

Fields returns all fields in record order. The result is a live view of the record's internal slice: it is valid to read until the next mutating call (AddField, RemoveFields, ReplaceField, InsertField), which may reorder, overwrite, or reallocate the backing array. Retain it across a mutation only after copying.

func (*Record) InsertField

func (r *Record) InsertField(f Field) *Record

InsertField inserts f keeping the record ordered by ascending tag: f is placed after any existing fields with a tag less than or equal to f's. It returns the record. Insert into an already tag-ordered record to keep it ordered.

func (*Record) Leader

func (r *Record) Leader() Leader

Leader returns the record's leader.

func (*Record) RemoveFields

func (r *Record) RemoveFields(tag string) *Record

RemoveFields removes every field with the given tag and returns the record. It compacts the field slice in place; the dropped tail is cleared so removed fields are not retained for the lifetime of the backing array.

func (*Record) ReplaceField

func (r *Record) ReplaceField(f Field) *Record

ReplaceField replaces the first field that shares f's tag with f, or appends f when no field has that tag. It returns the record.

func (*Record) SetLeader

func (r *Record) SetLeader(l Leader) *Record

SetLeader replaces the record's leader.

func (*Record) SubfieldValue

func (r *Record) SubfieldValue(tag string, code byte) string

SubfieldValue returns the value of the first subfield with the given code in the first data field with the given tag, or the empty string.

func (*Record) SubfieldValues

func (r *Record) SubfieldValues(tag string, code byte) []string

SubfieldValues returns the values of every subfield with the given code across all data fields with the given tag, in order, or nil when none match. It sizes the result to the total match count in one pass, so the aggregate allocates once with no regrowth and no per-field intermediate slices.

func (*Record) Validate

func (r *Record) Validate() error

Validate reports the first structural problem with the record: a leader that is not 24 bytes, a field tag that is not 3 bytes, a data field with no subfields, a control field carrying subfields, or a data field carrying a raw Value. The last two would be silently dropped by every codec on write, so a record that fails them round-trips lossily. Validate returns nil when the record is structurally well-formed. It does not check tag semantics, indicator values, or character encoding.

func (*Record) Vernacular added in v0.2.0

func (r *Record) Vernacular(tag string, code byte) string

Vernacular returns the given subfield from the 880 field linked to the first field with the given tag that has a linked alternate-script (880) partner, or "" if none does. It is a shortcut for the common case of reading an original-script title or name.

type RecordCounter added in v0.23.0

type RecordCounter interface {
	Total() int
}

RecordCounter is the optional interface a RecordReader implements when its source announces the size of the result set up front, as the SRU and Z39.50 search readers do. Test for it with a type assertion:

if rc, ok := r.(codex.RecordCounter); ok && rc.Total() >= 0 {
	log.Printf("%d hits", rc.Total())
}

Total reports the number of records the source says its result set holds, or -1 when that is not known: before the first successful fetch, and for the lifetime of a stream whose server never reports a count. Zero is a real answer, meaning the result set is empty.

type RecordReader

type RecordReader interface {
	Read() (*Record, error)
}

RecordReader reads MARC records one at a time from an underlying stream. Each format subpackage provides an implementation; Read returns io.EOF when the stream is exhausted.

type RecordWriter

type RecordWriter interface {
	Write(*Record) error
}

RecordWriter serializes MARC records to an underlying stream. Each format subpackage provides an implementation.

type Subfield

type Subfield struct {
	Code  byte
	Value string
}

Subfield is a single subfield of a MARC data field: a one-byte code and its UTF-8 value.

func NewSubfield

func NewSubfield(code byte, value string) Subfield

NewSubfield constructs a subfield with the given code and value.

Directories

Path Synopsis
Package bibframe converts MARC 21 records to BIBFRAME 2.0, the Library of Congress linked-data model that replaces the flat MARC record with an RDF graph of related resources: a bf:Work (the intellectual content) and a bf:Instance (a particular publication of it), linked by bf:instanceOf / bf:hasInstance.
Package bibframe converts MARC 21 records to BIBFRAME 2.0, the Library of Congress linked-data model that replaces the flat MARC record with an RDF graph of related resources: a bf:Work (the intellectual content) and a bf:Instance (a particular publication of it), linked by bf:instanceOf / bf:hasInstance.
Package citation converts MARC 21 records to the RIS and BibTeX citation formats used by reference managers (Zotero, EndNote, Mendeley) and LaTeX.
Package citation converts MARC 21 records to the RIS and BibTeX citation formats used by reference managers (Zotero, EndNote, Mendeley) and LaTeX.
cmd
libcodex command
Command libcodex is a small toolkit for inspecting and converting the bibliographic records the libcodex library reads and writes.
Command libcodex is a small toolkit for inspecting and converting the bibliographic records the libcodex library reads and writes.
Package dublincore converts MARC 21 records to Dublin Core (DCMI), the lowest-common-denominator metadata used by OAI-PMH and most repository software.
Package dublincore converts MARC 21 records to Dublin Core (DCMI), the lowest-common-denominator metadata used by OAI-PMH and most repository software.
internal
crosswalk
Package crosswalk holds the small MARC-field helpers shared by the derivative export codecs (mods, dublincore, citation, schemaorg, unimarc): ISBD punctuation trimming, subfield joining, subject assembly, year extraction, and JSON string escaping.
Package crosswalk holds the small MARC-field helpers shared by the derivative export codecs (mods, dublincore, citation, schemaorg, unimarc): ISBD punctuation trimming, subfield joining, subject assembly, year extraction, and JSON string escaping.
iso5426
Package iso5426 transcodes between the ISO 5426 character set — the extended Latin character set used by UNIMARC records — and UTF-8.
Package iso5426 transcodes between the ISO 5426 character set — the extended Latin character set used by UNIMARC records — and UTF-8.
iso5426/gen command
Command gen produces tables_gen.go from the ISO 5426 .ucm mapping (the ICU Unicode character map for ISO 5426, the UNIMARC extended-Latin character set).
Command gen produces tables_gen.go from the ISO 5426 .ucm mapping (the ICU Unicode character map for ISO 5426, the UNIMARC extended-Latin character set).
marc8
Package marc8 transcodes between MARC-8 byte sequences and UTF-8, supporting every MARC-8 graphic character set:
Package marc8 transcodes between MARC-8 byte sequences and UTF-8, supporting every MARC-8 graphic character set:
marc8/gen command
Command gen produces tables_gen.go from the Library of Congress MARC-8 code tables (codetables.xml), the authoritative mapping between MARC-8 and Unicode.
Command gen produces tables_gen.go from the Library of Congress MARC-8 code tables (codetables.xml), the authoritative mapping between MARC-8 and Unicode.
Package iso2709 reads and writes MARC 21 records in the binary ISO 2709 interchange format (.mrc), implementing codex.RecordReader and codex.RecordWriter.
Package iso2709 reads and writes MARC 21 records in the binary ISO 2709 interchange format (.mrc), implementing codex.RecordReader and codex.RecordWriter.
Package marcjson reads and writes MARC 21 records in the de-facto "MARC-in-JSON" structure (the pymarc/ruby-marc/marc4j layout), implementing codex.RecordReader and codex.RecordWriter with a hand-rolled tokenizer (see scan.go) rather than encoding/json, so both directions avoid reflection and most per-token allocation.
Package marcjson reads and writes MARC 21 records in the de-facto "MARC-in-JSON" structure (the pymarc/ruby-marc/marc4j layout), implementing codex.RecordReader and codex.RecordWriter with a hand-rolled tokenizer (see scan.go) rather than encoding/json, so both directions avoid reflection and most per-token allocation.
Package marcxml reads and writes MARC 21 records in the Library of Congress MARCXML "slim" serialization (namespace http://www.loc.gov/MARC21/slim), implementing codex.RecordReader and codex.RecordWriter using only encoding/xml.
Package marcxml reads and writes MARC 21 records in the Library of Congress MARCXML "slim" serialization (namespace http://www.loc.gov/MARC21/slim), implementing codex.RecordReader and codex.RecordWriter using only encoding/xml.
Package mods converts MARC 21 records to MODS (Metadata Object Description Schema), the Library of Congress XML standard that is richer than MARCXML and near-lossless from MARC.
Package mods converts MARC 21 records to MODS (Metadata Object Description Schema), the Library of Congress XML standard that is richer than MARCXML and near-lossless from MARC.
Package mrk reads and writes MARC 21 records in the MARCMaker / MARCBreaker mnemonic line format (".mrk"), implementing codex.RecordReader and codex.RecordWriter using only the standard library.
Package mrk reads and writes MARC 21 records in the MARCMaker / MARCBreaker mnemonic line format (".mrk"), implementing codex.RecordReader and codex.RecordWriter using only the standard library.
Package rdf is a small, fast, dependency-free RDF toolkit.
Package rdf is a small, fast, dependency-free RDF toolkit.
Package schemaorg converts MARC 21 records to schema.org JSON-LD — the vocabulary search engines and reading systems consume on the web.
Package schemaorg converts MARC 21 records to schema.org JSON-LD — the vocabulary search engines and reading systems consume on the web.
Package skos reads a SKOS concept scheme (a controlled vocabulary such as homosaurus or LCSH published as RDF) and crosswalks each skos:Concept to a MARC authority record.
Package skos reads a SKOS concept scheme (a controlled vocabulary such as homosaurus or LCSH published as RDF) and crosswalks each skos:Concept to a MARC authority record.
Package sru is a client for the SRU (Search/Retrieve via URL) protocol, the modern HTTP successor to Z39.50 used by library catalogs for search and retrieval.
Package sru is a client for the SRU (Search/Retrieve via URL) protocol, the modern HTTP successor to Z39.50 used by library catalogs for search and retrieval.
Package unimarc reads UNIMARC bibliographic records and maps them to the MARC 21 model (the codex.Record), so a UNIMARC record flows through every libcodex exporter (mods, dublincore, citation, bibframe, schemaorg).
Package unimarc reads UNIMARC bibliographic records and maps them to the MARC 21 model (the codex.Record), so a UNIMARC record flows through every libcodex exporter (mods, dublincore, citation, bibframe, schemaorg).
Package z3950 is a client for the Z39.50 information-retrieval protocol (ANSI/NISO Z39.50 / ISO 23950), the classic library search protocol that SRU succeeded.
Package z3950 is a client for the Z39.50 information-retrieval protocol (ANSI/NISO Z39.50 / ISO 23950), the classic library search protocol that SRU succeeded.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL