Character Encoding¶
MRRC supports both MARC-8 (legacy) and UTF-8 character encodings, with automatic conversion.
Encoding Overview¶
| Encoding | Leader Position 09 | Description |
|---|---|---|
| MARC-8 | (blank/space) | Legacy encoding with escape sequences for non-Latin scripts |
| UTF-8 | a |
Unicode, modern standard |
MRRC handles encoding automatically:
- Chooses each record's encoding from leader position 09, as pymarc does:
ais UTF-8 and any other value is MARC-8 (see Choosing the Encoding to override this) - Converts MARC-8 to UTF-8 when reading
- Stores all strings internally as UTF-8
- Writes UTF-8 (MARC-8 output is not currently supported)
UTF-8 (Modern Standard)¶
UTF-8 is the recommended encoding for new records. It supports all Unicode characters directly without escape sequences.
Reading UTF-8 records:
MARC-8 (Legacy)¶
MARC-8 is a Library of Congress encoding that predates Unicode. It uses escape sequences to switch between character sets.
Supported Character Sets¶
MRRC supports all standard MARC-8 character sets:
| Character Set | Code | Description |
|---|---|---|
| Basic Latin | 42 (B) | ASCII characters |
| Extended Latin (ANSEL) | 45 (E) | Diacritics and extended Latin |
| Basic Hebrew | 32 (2) | Hebrew alphabet |
| Basic Arabic | 33 (3) | Arabic script |
| Extended Arabic | 34 (4) | Extended Arabic variants |
| Basic Cyrillic | 4E (N) | Cyrillic alphabet |
| Extended Cyrillic | 51 (Q) | Extended Cyrillic |
| Basic Greek | 53 (S) | Greek alphabet |
| Subscript | 62 (b) | Mathematical subscripts |
| Superscript | 70 (p) | Mathematical superscripts |
| Greek Symbols | 67 (g) | Greek letters in symbols |
| EACC | 31 (1) | East Asian (CJK, 15,000+ characters) |
Escape Sequences¶
MARC-8 uses escape sequences (starting with 0x1B) to switch character sets:
For example:
- ESC ( B → Switch G0 to Basic Latin
- ESC $ 1 → Switch G0 to EACC (East Asian)
You don't need to handle escape sequences manually - MRRC decodes them automatically.
Combining Marks (Diacritics)¶
MARC-8 represents diacritics as combining marks that precede their base character:
MRRC normalizes these to Unicode combining sequences.
Encoding Detection¶
Check a record's declared encoding via the leader:
use mrrc::encoding::MarcEncoding;
// Leader position 9 (`character_coding`) declares the scheme, by the rule
// the readers use: `a` is UTF-8 and any other value is MARC-8
let encoding = MarcEncoding::declared_by_leader(record.leader.character_coding);
match encoding {
MarcEncoding::Utf8 => println!("UTF-8"),
MarcEncoding::Marc8 => println!("MARC-8"),
}
Choosing the Encoding¶
By default each record is decoded in the encoding its leader position 09
declares, which is pymarc's rule. Files in the wild don't always get position
09 right, most often a UTF-8 record whose leader still says MARC-8, so the
readers take a character_coding option:
character_coding |
Behavior |
|---|---|
"leader" (default) |
Position 09 a is UTF-8; any other value is MARC-8. |
"utf-8" |
Every record is UTF-8, whatever position 09 says. pymarc spells this force_utf8=True, which mrrc also accepts. |
"detect" |
A record whose field data is valid UTF-8 containing non-ASCII bytes is UTF-8; otherwise position 09 decides. Suited to files that mix MARC-8 records with mislabelled UTF-8 ones, since MARC-8 text almost never forms valid multibyte UTF-8. |
AuthorityMARCReader and HoldingsMARCReader (and their Rust counterparts)
take the same option.
A MARC-8 character with no mapping in the active character set, or an escape
sequence cut off by the end of a value, becomes U+FFFD under the default
validation_level="structural" (pymarc substitutes a space). Under
validation_level="strict_marc" it raises
E302 marc8_invalid instead. Invalid UTF-8 is handled
separately, by utf8_handling (pymarc's option), whose default "strict"
raises E301 at every validation level.
Writing¶
MRRC writes UTF-8 and sets leader position 09 to a on output. Records read
from MARC-8 sources are converted to UTF-8 on the way in, so written output is
always UTF-8 regardless of the source encoding. Writing MARC-8 output is not
currently supported.
Mixed Encoding Handling¶
Some legacy records have inconsistent encoding: the leader says MARC-8 but the data is UTF-8, or the other way round.
UTF-8 data labelled MARC-8 is the common case. Read such files with character_coding="detect" (see Choosing the Encoding), which checks each record's bytes before decoding it.
MARC-8 data labelled UTF-8 is decoded as UTF-8. Its diacritics are invalid UTF-8, so they raise E301 under the default utf8_handling="strict", become U+FFFD under "replace", and are dropped under "ignore". Its escape sequences are ASCII, so they survive as literal ESC characters, and in Rust the encoding validator flags such records after reading:
use mrrc::{EncodingAnalysis, EncodingValidator};
match EncodingValidator::analyze_encoding(&record)? {
EncodingAnalysis::Mixed { primary, .. } => {
println!("Warning: MARC-8 escape sequences in a record declared {:?}", primary);
}
_ => {}
}
The validator works on decoded values, so it can't see UTF-8 data labelled MARC-8: by then the reader has decoded it as MARC-8. Use character_coding="detect" for that case. If you encounter other encoding issues, check the leader's character_coding property and compare it with the actual content.
Common Issues¶
Mojibake (Garbled Text)¶
If you see garbled text like é instead of é, the encoding may be misdetected:
- Record declares UTF-8 but contains MARC-8
- Record declares MARC-8 but contains UTF-8
- File was saved with wrong encoding
Solution: Check the leader position 9 and verify it matches the actual data. If the data is UTF-8 but the leader says MARC-8, read with character_coding="utf-8" (pymarc's force_utf8=True), or with character_coding="detect" when only some records are affected.
Missing Characters¶
If characters display as ? or \uFFFD:
- The character may not be in the MARC-8 character tables
- The character may be from an unsupported script
- The data may be corrupted
Under validation_level="strict_marc", an undecodable MARC-8 character raises
E302 rather than becoming \uFFFD.
East Asian Text (CJK)¶
MARC-8 uses EACC (East Asian Character Code) for Chinese, Japanese, and Korean:
- Each character is 3 bytes, after the
ESC $ 1escape sequence - MRRC supports 15,000+ EACC characters
- Modern records should use UTF-8 for CJK
Best Practices¶
-
Use UTF-8 for new records - Simpler, universal character support
-
Expect UTF-8 on round-trip - Reading then re-writing a MARC-8 record normalizes it to UTF-8; MRRC does not reproduce the original MARC-8 bytes
-
Validate encoding before batch processing - Check a sample of records for consistency
-
Handle encoding errors gracefully - Some legacy records have encoding issues
See Also¶
- MARC Primer - Record structure overview
- Library of Congress MARC-8 Specification
- Unicode Character Tables