use crate::error::{MarcError, Result};
use crate::formats::FormatWriter;
use crate::record::Record;
use std::io::Write;
const FIELD_TERMINATOR: u8 = 0x1E;
const SUBFIELD_DELIMITER: u8 = 0x1F;
const RECORD_TERMINATOR: u8 = 0x1D;
#[derive(Debug)]
pub struct MarcWriter<W: Write> {
writer: W,
records_written: usize,
finished: bool,
}
impl<W: Write> MarcWriter<W> {
pub fn new(writer: W) -> Self {
MarcWriter {
writer,
records_written: 0,
finished: false,
}
}
pub fn write_record(&mut self, record: &Record) -> Result<()> {
if self.finished {
return Err(MarcError::InvalidRecord(
"Cannot write to a finished writer".to_string(),
));
}
let mut data_area = Vec::new();
let mut directory = Vec::new();
let mut current_position = 0;
for (tag, values) in &record.control_fields {
if tag.as_str() < "010" {
for value in values {
let field_data = value.as_bytes();
let field_length = field_data.len() + 1;
directory.extend_from_slice(tag.as_bytes());
directory.extend_from_slice(format!("{field_length:04}").as_bytes());
directory.extend_from_slice(format!("{current_position:05}").as_bytes());
data_area.extend_from_slice(field_data);
data_area.push(FIELD_TERMINATOR);
current_position += field_length;
}
}
}
for (tag, fields) in &record.fields {
for field in fields {
let mut field_data = Vec::new();
field_data.push(field.indicator1 as u8);
field_data.push(field.indicator2 as u8);
for subfield in &field.subfields {
field_data.push(SUBFIELD_DELIMITER);
field_data.push(subfield.code as u8);
field_data.extend_from_slice(subfield.value.as_bytes());
}
field_data.push(FIELD_TERMINATOR);
let field_length = field_data.len();
directory.extend_from_slice(tag.as_bytes());
directory.extend_from_slice(format!("{field_length:04}").as_bytes());
directory.extend_from_slice(format!("{current_position:05}").as_bytes());
data_area.extend_from_slice(&field_data);
current_position += field_length;
}
}
directory.push(FIELD_TERMINATOR);
let base_address = 24 + directory.len();
let record_length = base_address + data_area.len() + 1;
let mut leader = record.leader.clone();
leader.record_length = u32::try_from(record_length)
.map_err(|_| MarcError::InvalidRecord("Record length exceeds 4GB limit".to_string()))?;
leader.data_base_address = u32::try_from(base_address)
.map_err(|_| MarcError::InvalidRecord("Base address exceeds 4GB limit".to_string()))?;
let leader_bytes = leader.as_bytes()?;
self.writer.write_all(&leader_bytes)?;
self.writer.write_all(&directory)?;
self.writer.write_all(&data_area)?;
self.writer.write_all(&[RECORD_TERMINATOR])?;
self.records_written += 1;
Ok(())
}
pub fn finish(&mut self) -> Result<()> {
self.writer.flush()?;
self.finished = true;
Ok(())
}
#[must_use]
pub fn records_written(&self) -> usize {
self.records_written
}
}
impl<W: Write + std::fmt::Debug> FormatWriter for MarcWriter<W> {
fn write_record(&mut self, record: &Record) -> Result<()> {
MarcWriter::write_record(self, record)
}
fn finish(&mut self) -> Result<()> {
MarcWriter::finish(self)
}
fn records_written(&self) -> Option<usize> {
Some(self.records_written)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::leader::Leader;
use crate::record::Field;
use std::io::Cursor;
fn make_test_leader() -> Leader {
Leader {
record_length: 0, record_status: 'n',
record_type: 'a',
bibliographic_level: 'm',
control_record_type: ' ',
character_coding: 'a',
indicator_count: 2,
subfield_code_count: 2,
data_base_address: 0, encoding_level: ' ',
cataloging_form: ' ',
multipart_level: ' ',
reserved: "4500".to_string(),
}
}
#[test]
fn test_write_simple_record() {
let mut record = Record::new(make_test_leader());
let mut field = Field::new("245".to_string(), '1', '0');
field.add_subfield('a', "Test title".to_string());
record.add_field(field);
let mut buffer = Vec::new();
let mut writer = MarcWriter::new(&mut buffer);
writer.write_record(&record).unwrap();
assert!(buffer.len() > 24); assert_eq!(&buffer[0..5], b"00053"); assert_eq!(buffer[24], b'2'); }
#[test]
fn test_write_and_read_roundtrip() {
use crate::reader::MarcReader;
let mut record = Record::new(make_test_leader());
let mut field = Field::new("245".to_string(), '1', '0');
field.add_subfield('a', "Test title".to_string());
field.add_subfield('c', "Author".to_string());
record.add_field(field);
record.add_control_field("001".to_string(), "12345".to_string());
let mut buffer = Vec::new();
{
let mut writer = MarcWriter::new(&mut buffer);
writer.write_record(&record).unwrap();
}
let cursor = Cursor::new(buffer);
let mut reader = MarcReader::new(cursor);
let read_record = reader.read_record().unwrap().unwrap();
assert_eq!(read_record.get_control_field("001"), Some("12345"));
let fields = read_record.get_fields("245").unwrap();
assert_eq!(fields[0].indicator1, '1');
assert_eq!(fields[0].indicator2, '0');
assert_eq!(fields[0].get_subfield('a'), Some("Test title"));
assert_eq!(fields[0].get_subfield('c'), Some("Author"));
}
#[test]
fn test_write_multiple_subfields() {
use crate::reader::MarcReader;
let mut record = Record::new(make_test_leader());
let mut field = Field::new("650".to_string(), ' ', '0');
field.add_subfield('a', "Subject 1".to_string());
field.add_subfield('v', "subdivision".to_string());
record.add_field(field);
let mut buffer = Vec::new();
{
let mut writer = MarcWriter::new(&mut buffer);
writer.write_record(&record).unwrap();
}
let cursor = Cursor::new(buffer);
let mut reader = MarcReader::new(cursor);
let read_record = reader.read_record().unwrap().unwrap();
let fields = read_record.get_fields("650").unwrap();
assert_eq!(fields[0].get_subfield('a'), Some("Subject 1"));
assert_eq!(fields[0].get_subfield('v'), Some("subdivision"));
}
#[test]
fn test_write_multiple_fields_same_tag() {
use crate::reader::MarcReader;
let mut record = Record::new(make_test_leader());
for i in 1..=3 {
let mut field = Field::new("650".to_string(), ' ', '0');
field.add_subfield('a', format!("Subject {i}"));
record.add_field(field);
}
let mut buffer = Vec::new();
{
let mut writer = MarcWriter::new(&mut buffer);
writer.write_record(&record).unwrap();
}
let cursor = Cursor::new(buffer);
let mut reader = MarcReader::new(cursor);
let read_record = reader.read_record().unwrap().unwrap();
let fields = read_record.get_fields("650").unwrap();
assert_eq!(fields.len(), 3);
assert_eq!(fields[0].get_subfield('a'), Some("Subject 1"));
assert_eq!(fields[1].get_subfield('a'), Some("Subject 2"));
assert_eq!(fields[2].get_subfield('a'), Some("Subject 3"));
}
#[test]
fn test_format_writer_trait() {
let mut record = Record::new(make_test_leader());
let mut field = Field::new("245".to_string(), '1', '0');
field.add_subfield('a', "Test title".to_string());
record.add_field(field);
let mut buffer = Vec::new();
{
let mut writer = MarcWriter::new(&mut buffer);
assert_eq!(writer.records_written(), 0);
writer.write_record(&record).unwrap();
assert_eq!(writer.records_written(), 1);
writer.write_record(&record).unwrap();
assert_eq!(writer.records_written(), 2);
writer.finish().unwrap();
}
let cursor = Cursor::new(buffer);
let mut reader = crate::reader::MarcReader::new(cursor);
let r1 = reader.read_record().unwrap();
assert!(r1.is_some());
let r2 = reader.read_record().unwrap();
assert!(r2.is_some());
let r3 = reader.read_record().unwrap();
assert!(r3.is_none());
}
#[test]
fn test_format_writer_batch() {
use crate::formats::FormatWriter;
let records: Vec<Record> = (0..3)
.map(|i| {
let mut record = Record::new(make_test_leader());
let mut field = Field::new("245".to_string(), '1', '0');
field.add_subfield('a', format!("Title {i}"));
record.add_field(field);
record
})
.collect();
let mut buffer = Vec::new();
{
let mut writer = MarcWriter::new(&mut buffer);
FormatWriter::write_batch(&mut writer, &records).unwrap();
assert_eq!(writer.records_written(), 3);
writer.finish().unwrap();
}
let cursor = Cursor::new(buffer);
let mut reader = crate::reader::MarcReader::new(cursor);
for i in 0..3 {
let record = reader.read_record().unwrap().unwrap();
let fields = record.get_fields("245").unwrap();
assert_eq!(
fields[0].get_subfield('a'),
Some(format!("Title {i}").as_str())
);
}
assert!(reader.read_record().unwrap().is_none());
}
#[test]
fn test_writer_cannot_write_after_finish() {
let mut record = Record::new(make_test_leader());
let mut field = Field::new("245".to_string(), '1', '0');
field.add_subfield('a', "Test".to_string());
record.add_field(field);
let mut buffer = Vec::new();
let mut writer = MarcWriter::new(&mut buffer);
writer.finish().unwrap();
let result = writer.write_record(&record);
assert!(result.is_err());
}
}