mrrc 0.7.6

A Rust library for reading, writing, and manipulating MARC bibliographic records in ISO 2709 binary format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#!/usr/bin/env python3
"""
Creating MARC records (Python/pymarc-compatible API)

This example demonstrates how to create MARC records from scratch
using the pymarc-compatible API in mrrc. All patterns shown here
work identically to pymarc code.

The pymarc-compatible API is the most ergonomic for Python users.
Use the method-chaining approach for complex records.
"""

import sys
from pathlib import Path

# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

try:
    from mrrc import MARCReader, MARCWriter, Record, Field, Leader, Subfield
except ImportError:
    print("Error: mrrc not installed")
    print("Install with: pip install mrrc")
    sys.exit(1)


def simple_record():
    """
    Create a simple bibliographic record.
    
    Demonstrates basic record creation with:
    - Control fields (001, 008)
    - Title field (245)
    - Author field (100)
    - Subject headings (650)
    """
    print("\n" + "=" * 70)
    print("1. SIMPLE BIBLIOGRAPHIC RECORD")
    print("=" * 70 + "\n")
    
    # Create a leader for a bibliographic record
    leader = Leader(
        record_type='a',           # 'a' = language material
        bibliographic_level='m',   # 'm' = monograph
        character_coding=' ',      # ' ' = MARC-8, 'a' = UTF-8
    )

    # Build record with inline fields using subfields= and indicators= kwargs
    record = Record(leader, fields=[
        Field('245', indicators=['1', '0'], subfields=[
            Subfield('a', 'To Kill a Mockingbird /'),
            Subfield('c', 'Harper Lee.'),
        ]),
        Field('100', '1', ' ', subfields=[
            Subfield('a', 'Lee, Harper,'),
            Subfield('d', '1926-2016,'),
            Subfield('e', 'author.'),
        ]),
    ])

    # Add control fields
    record.add_control_field('001', '9780061120084')
    record.add_control_field('008', '051029s2005    xxu||||||||||||||||eng||')

    # Subject headings via loop (a natural use of add_subfield)
    for subject in ['Psychological fiction.', 'Legal stories.']:
        subject_field = Field('650', ' ', '0')
        subject_field.add_subfield('a', subject)
        record.add_field(subject_field)
    
    # Display results
    print(f"Record Type:    {record.leader.record_type}")
    print(f"Control Number: {record.get_control_field('001')}")
    print(f"Title:          {record.title}")
    print(f"Author:         {record.author}")
    print(f"Subjects:       {', '.join(record.subjects)}")
    print()


def record_with_complex_fields():
    """
    Create a record with more complex field structures.
    
    Demonstrates:
    - Multiple subfields in one field
    - Subdivisions in subject fields (x, y, z)
    - Publication information (260)
    - Physical description (300)
    - ISBN field (020)
    """
    print("\n" + "=" * 70)
    print("2. RECORD WITH COMPLEX FIELD STRUCTURES")
    print("=" * 70 + "\n")
    
    leader = Leader(
        record_type='a',
        bibliographic_level='m',
        character_coding='a',  # UTF-8
    )
    
    record = Record(leader)
    
    # Control fields
    record.add_control_field('001', '12345678')
    record.add_control_field('005', '20051229123456.0')
    record.add_control_field('008', '051229s2005    xxu||||||||||||||||eng||')
    
    # ISBN
    isbn = Field('020', ' ', ' ')
    isbn.add_subfield('a', '9780596004957')
    record.add_field(isbn)
    
    # Title
    title = Field('245', '1', '0')
    title.add_subfield('a', 'Introduction to quantum mechanics /')
    title.add_subfield('c', 'David J. Griffiths.')
    record.add_field(title)
    
    # Author
    author = Field('100', '1', ' ')
    author.add_subfield('a', 'Griffiths, David J.,')
    author.add_subfield('d', '1942-')
    author.add_subfield('e', 'author.')
    record.add_field(author)
    
    # Publication information
    publication = Field('260', ' ', ' ')
    publication.add_subfield('a', 'Boston :')
    publication.add_subfield('b', 'Pearson,')
    publication.add_subfield('c', '2005.')
    record.add_field(publication)
    
    # Physical description
    physical = Field('300', ' ', ' ')
    physical.add_subfield('a', 'xvii, 468 pages :')
    physical.add_subfield('b', 'color illustrations ;')
    physical.add_subfield('c', '26 cm')
    record.add_field(physical)
    
    # Subjects with subdivisions
    subject1 = Field('650', ' ', '0')
    subject1.add_subfield('a', 'Quantum mechanics')
    subject1.add_subfield('v', 'Textbooks.')
    record.add_field(subject1)
    
    subject2 = Field('650', ' ', '0')
    subject2.add_subfield('a', 'Physics')
    subject2.add_subfield('x', 'Study and teaching')
    subject2.add_subfield('z', 'Higher.')
    record.add_field(subject2)
    
    # Display results
    print(f"Title:        {record.title}")
    print(f"Author:       {record.author}")
    print(f"ISBN:         {', '.join(record.isbns())}")
    
    if record.publication_info():
        pub = record.publication_info()
        print(f"Published:    {pub.date} in {pub.place}")
        if pub.publisher:
            print(f"Publisher:    {pub.publisher}")
    
    print(f"\nSubjects:")
    for subject in record.subjects:
        print(f"  - {subject}")
    print()


def record_with_multiple_entries():
    """
    Create a record with multiple authors/contributors and subject fields.
    
    Demonstrates:
    - Main entry (100)
    - Added entries (700)
    - Multiple subject headings with different sources
    - Genre/form information (655)
    """
    print("\n" + "=" * 70)
    print("3. RECORD WITH MULTIPLE ENTRIES AND CONTRIBUTORS")
    print("=" * 70 + "\n")
    
    leader = Leader(
        record_type='a',
        bibliographic_level='m',
    )
    
    record = Record(leader)
    
    # Control fields
    record.add_control_field('001', 'ocm00123456')
    record.add_control_field('008', '051229s2005    xxu||||||||||||||||eng||')
    
    # Main author
    main_author = Field('100', '1', ' ')
    main_author.add_subfield('a', 'Doe, John,')
    main_author.add_subfield('d', '1950-')
    main_author.add_subfield('e', 'author.')
    record.add_field(main_author)
    
    # Title
    title = Field('245', '1', '4')
    title.add_subfield('a', 'The guide to advanced Rust programming /')
    title.add_subfield('c', 'John Doe.')
    record.add_field(title)
    
    # Added entry - editor
    editor = Field('700', '1', ' ')
    editor.add_subfield('a', 'Smith, Jane,')
    editor.add_subfield('d', '1960-')
    editor.add_subfield('e', 'editor.')
    record.add_field(editor)
    
    # Added entry - contributor
    contributor = Field('700', '1', ' ')
    contributor.add_subfield('a', 'Jones, Bob,')
    contributor.add_subfield('d', '1970-')
    contributor.add_subfield('e', 'contributor.')
    record.add_field(contributor)
    
    # Subject headings from different sources
    subject1 = Field('650', ' ', '0')  # LCSH
    subject1.add_subfield('a', 'Rust (Computer program language)')
    record.add_field(subject1)
    
    subject2 = Field('650', ' ', '0')  # LCSH
    subject2.add_subfield('a', 'Systems programming')
    record.add_field(subject2)
    
    subject3 = Field('650', ' ', '7')  # Other source
    subject3.add_subfield('a', 'Performance optimization')
    subject3.add_subfield('2', 'local')
    record.add_field(subject3)
    
    # Genre/form
    genre = Field('655', ' ', '7')
    genre.add_subfield('a', 'Handbooks and manuals.')
    genre.add_subfield('2', 'lcgft')
    record.add_field(genre)
    
    # Display results
    print(f"Main Author:     {record.author}")
    
    print(f"\nAll Authors and Contributors:")
    all_authors = record.authors()
    for author in all_authors:
        print(f"  - {author}")
    
    print(f"\nSubjects:")
    for subject in record.subjects:
        print(f"  - {subject}")

    print(f"\nGenre/Form:")
    if '655' in record:
        for field in record.get_fields('655'):
            if field.get_subfield('a'):
                print(f"  - {field.get_subfield('a')}")
    print()


def writing_records():
    """
    Demonstrate writing records to a MARC file.
    
    Shows how to:
    - Create multiple records
    - Write them to a file
    - Read them back to verify
    """
    print("\n" + "=" * 70)
    print("4. WRITING RECORDS TO FILE")
    print("=" * 70 + "\n")
    
    # Create sample records
    records = []
    
    for i in range(3):
        leader = Leader(record_type='a', bibliographic_level='m')
        record = Record(leader)
        
        record.add_control_field('001', f'test{i:05d}')
        record.add_control_field('008', '200101s2020    xxu||||||||||||||||eng||')
        
        title = Field('245', '1', '0')
        title.add_subfield('a', f'Sample Record {i + 1} /')
        title.add_subfield('c', f'Author {i + 1}.')
        record.add_field(title)
        
        author = Field('100', '1', ' ')
        author.add_subfield('a', f'Author {i + 1},')
        record.add_field(author)
        
        records.append(record)
    
    # Write to a temporary file
    import tempfile
    import os
    
    temp_file = tempfile.mktemp(suffix='.mrc')

    # Pass path string so mrrc uses Rust I/O with GIL released
    writer = MARCWriter(temp_file)
    for record in records:
        writer.write_record(record)
    writer.close()

    try:
        # Read back and verify
        print(f"Wrote {len(records)} records to {temp_file}")

        reader = MARCReader(temp_file)
        read_count = 0
        print("\nRecords read back:")
        for record in reader:
            print(f"  {read_count + 1}. {record.title}")
            read_count += 1
        
        print(f"\nVerification: {read_count}/{len(records)} records successfully round-tripped")
        
    finally:
        # Clean up
        os.unlink(temp_file)
    
    print()


def format_conversions():
    """
    Demonstrate format conversion when creating records.
    
    Shows how to convert a newly created record to various formats.
    """
    print("\n" + "=" * 70)
    print("5. FORMAT CONVERSIONS")
    print("=" * 70 + "\n")
    
    leader = Leader(record_type='a', bibliographic_level='m')
    record = Record(leader)
    
    record.add_control_field('001', 'test123')
    record.add_control_field('008', '200101s2020    xxu||||||||||||||||eng||')
    
    title = Field('245', '1', '0')
    title.add_subfield('a', 'Test Record /')
    title.add_subfield('c', 'Test Author.')
    record.add_field(title)
    
    author = Field('100', '1', ' ')
    author.add_subfield('a', 'Author, Test')
    record.add_field(author)
    
    subject = Field('650', ' ', '0')
    subject.add_subfield('a', 'Test subject')
    record.add_field(subject)
    
    # Convert to various formats
    print("Original record:")
    print(f"  Title: {record.title}")
    print(f"  Author: {record.author}")
    print()
    
    # JSON
    try:
        json_str = record.to_json()
        print("JSON (first 150 chars):")
        print(f"  {json_str[:150]}...")
    except Exception as e:
        print(f"JSON conversion failed: {e}")
    
    print()
    
    # MARCJSON
    try:
        marcjson_str = record.to_marcjson()
        print("MARCJSON (first 150 chars):")
        print(f"  {marcjson_str[:150]}...")
    except Exception as e:
        print(f"MARCJSON conversion failed: {e}")
    
    print()
    
    # MARCXML
    try:
        xml_str = record.to_xml()
        print("MARCXML (first 150 chars):")
        print(f"  {xml_str[:150]}...")
    except Exception as e:
        print(f"MARCXML conversion failed: {e}")
    
    print()


def main():
    """Main example runner."""
    
    print("\n" + "=" * 70)
    print("MRRC: Creating MARC Records (Python/pymarc-compatible)")
    print("=" * 70)
    
    simple_record()
    record_with_complex_fields()
    record_with_multiple_entries()
    writing_records()
    format_conversions()
    
    print("=" * 70)
    print("TIPS FOR CREATING RECORDS")
    print("=" * 70)
    print("""
1. FIELD TAGS AND INDICATORS:
   - First indicator: context-specific (check MARC21 standards)
   - Second indicator: also context-specific
   - Examples:
     * '245' (title): indicators are '1', '0' for normal title
     * '100' (author): indicator '1' for personal name
     * '650' (subject): indicators ' ', '0' for LCSH

2. COMMON CONTROL FIELDS:
   - '001': Control number (ISBN, OCLC number, etc.)
   - '005': Date/time of latest transaction
   - '008': Fixed-length data elements (publication date, language, etc.)

3. SUBFIELD CODES:
   - 'a': Main part of heading
   - 'x', 'y', 'z': Subdivisions (topical, chronological, geographic)
   - 'e': Relator term (author, editor, translator, etc.)
   - 'd': Date (birth/death dates, publication date)

4. RECOMMENDED PATTERN:
   ```python
   leader = Leader(record_type='a', bibliographic_level='m')
   record = Record(leader, fields=[
       Field('245', indicators=['1', '0'], subfields=[
           Subfield('a', 'main text'),
       ]),
   ])
   record.add_control_field('001', 'my-control-number')

   # Write — pass path string for Rust I/O (releases GIL)
   writer = MARCWriter('output.mrc')
   writer.write_record(record)
   writer.close()
   ```

5. PYMARC COMPATIBILITY:
   All patterns work identically in pymarc and mrrc.
   Just swap the import and you're done!
    """)
    print()


if __name__ == '__main__':
    main()