iso2709

package
v0.26.0 Latest Latest
Warning

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

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

Documentation

Overview

Package iso2709 reads and writes MARC 21 records in the binary ISO 2709 interchange format (.mrc), implementing codex.RecordReader and codex.RecordWriter.

An ISO 2709 record is a 24-byte leader, a directory of fixed 12-byte entries, and the variable fields, terminated by a record terminator. Fields are either control fields (tags below "010"), which hold raw data, or data fields, which carry two indicators followed by delimited subfields.

Character encoding: MARC 21 with UTF-8 (leader byte 9 == 'a') is the primary, preferred form. Records flagged as MARC-8 (leader byte 9 == blank) are transcoded to UTF-8 on read for the Western subset (Basic Latin plus ANSEL Extended Latin, including combining diacritics); see the internal/marc8 package and the README for scope and limitations. Writing always emits UTF-8 and sets leader byte 9 to 'a'.

Conformance notes: the encoder always emits the MARC 21 fixed leader geometry (2 indicators, 1-character subfield codes, and a 3+4+5-byte directory entry, entry map "4500"); the decoder honors a non-default indicator count and entry map declared in leader bytes 10-11 and 20-22, so well-formed non-standard ISO 2709 directories are not misparsed. The MARC 21 fill character (0x7C, "|") is treated as ordinary data and passed through unchanged; note that a fill in leader byte 9 reads as MARC-8. MARC-8 transcoding composes common Latin base+mark pairs to NFC but does not apply full Unicode normalization, so mixed NFC/NFD output is possible, and UTF-8 input is passed through unchanged.

Index

Constants

View Source
const (
	// SubfieldDelimiter (0x1f) precedes each subfield code within a data field.
	SubfieldDelimiter = 0x1f
	// FieldTerminator (0x1e) ends each variable field and the directory.
	FieldTerminator = 0x1e
	// RecordTerminator (0x1d) ends a complete record.
	RecordTerminator = 0x1d
)

MARC 21 control characters used as field, subfield and record delimiters.

Variables

This section is empty.

Functions

func Decode

func Decode(b []byte) (*codex.Record, bool, error)

Decode parses a single ISO 2709 record from its raw bytes (the leader, directory, fields, and optionally the trailing record terminator). Directory entries that point outside the field data are skipped. If the leader declares MARC-8 encoding, values are transcoded to UTF-8.

The returned bool reports whether decoding fell back to best-effort passthrough of an unrecognized MARC-8 designation or an unmapped character, meaning some values may contain mojibake; it is always false for UTF-8 records. Re-serializing a lossy record as UTF-8 persists the mojibake, so callers that must not corrupt data should check it.

func Encode

func Encode(r *codex.Record) ([]byte, error)

Encode serializes a record to a new ISO 2709 byte slice. It is EncodeInto with a nil destination; use EncodeInto to reuse a buffer across records.

func EncodeInto

func EncodeInto(dst []byte, r *codex.Record) ([]byte, error)

EncodeInto appends the ISO 2709 encoding of r to dst and returns the extended slice, growing dst once when needed. It computes the leader's record length [0:5] and base address [12:17], builds the directory (3-byte tag, 4-digit length, 5-digit offset per field) and emits the subfield, field and record delimiters; the leader geometry is forced to the MARC 21 fixed values (UTF-8, 2 indicators, "4500").

It returns dst unchanged with an error if a tag is not three printable ASCII bytes, a data field carries a raw Value (which the format cannot encode and would silently drop), a field is too long to encode in four digits, the record is too long for five digits, or any field datum contains a reserved structural delimiter byte (0x1d/0x1e/0x1f) that the binary format cannot carry.

func EncodeMARC8

func EncodeMARC8(r *codex.Record) ([]byte, error)

EncodeMARC8 serializes a record to ISO 2709 with MARC-8 encoded values and leader byte 9 set to blank, for systems that require legacy MARC-8 rather than UTF-8. Values are transcoded across all MARC-8 character sets (Latin, Cyrillic, Greek, Hebrew, Arabic, the sub/superscript and Greek-symbol sets, and the multibyte CJK set EACC; see internal/marc8). It returns an error if a value contains a character no MARC-8 set can represent (e.g. an emoji).

func ReadFile

func ReadFile(path string) ([]*codex.Record, error)

ReadFile reads every record from the named file. On the first malformed record it returns the records parsed so far together with the error.

The whole file is read into one buffer and each record's leader, tags and plain-ASCII values are taken as zero-copy views of it (the returned records keep the buffer alive), so a large file is decoded without a per-record copy.

func WriteFile

func WriteFile(path string, records []*codex.Record) error

WriteFile writes every record to the named file in order, creating it or truncating an existing file. A single Writer is used so the encode buffer is reused across records.

Types

type Reader

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

Reader reads ISO 2709 records one at a time from an underlying stream. It does not buffer the whole stream: each record is read using the length declared in its leader (bytes [0:5]), falling back to the record terminator when that length is absent or invalid. This handles records larger than any default buffer.

func NewReader

func NewReader(r io.Reader) *Reader

NewReader returns a Reader that reads records from r.

func (*Reader) All

func (rd *Reader) All() iter.Seq2[*codex.Record, error]

All returns an iterator over the remaining records in the stream, for use as

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

It is shorthand for codex.All(rd) and stops at the first error.

func (*Reader) Lossy

func (rd *Reader) Lossy() bool

Lossy reports whether the record returned by the most recent successful Read decoded out-of-scope MARC-8 best-effort (see Decode), meaning it may contain mojibake. It is false until the first such Read.

func (*Reader) Read

func (rd *Reader) Read() (*codex.Record, error)

Read returns the next record from the stream, or io.EOF when the stream is exhausted. A non-EOF error indicates that the current record was malformed; the leader length is still used to advance past it, so the caller may continue calling Read for subsequent records.

type Writer

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

Writer serializes MARC records to an underlying stream as ISO 2709. Output is always UTF-8 with leader byte 9 set to 'a'.

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter returns a Writer that writes records to w.

func (*Writer) Write

func (wr *Writer) Write(r *codex.Record) error

Write serializes one record and writes it to the stream. It reuses an internal buffer, so writing many records allocates only when that buffer must grow.

Jump to

Keyboard shortcuts

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