use crate::leader::Leader;
use crate::marc_record::MarcRecord;
use crate::record::Field;
use crate::record_helpers::control_field_char_at;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthorityRecord {
pub leader: Leader,
pub control_fields: IndexMap<String, Vec<String>>,
pub fields: IndexMap<String, Vec<Field>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HeadingType {
PersonalName,
CorporateName,
MeetingName,
UniformTitle,
ChronologicalTerm,
TopicalTerm,
GeographicName,
GenreFormTerm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KindOfRecord {
EstablishedHeading,
ReferenceUntracted,
ReferenceTraced,
Subdivision,
EstablishedHeadingAndSubdivision,
ReferenceAndSubdivision,
NodeLabel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LevelOfEstablishment {
FullyEstablished,
Memorandum,
Provisional,
Preliminary,
NotApplicable,
}
impl AuthorityRecord {
#[must_use]
pub fn new(leader: Leader) -> Self {
AuthorityRecord {
leader,
control_fields: IndexMap::new(),
fields: IndexMap::new(),
}
}
#[must_use]
pub fn builder(leader: Leader) -> AuthorityRecordBuilder {
AuthorityRecordBuilder {
record: AuthorityRecord::new(leader),
}
}
pub fn add_control_field(&mut self, tag: String, value: String) {
self.control_fields.entry(tag).or_default().push(value);
}
#[must_use]
pub fn get_control_field(&self, tag: &str) -> Option<&str> {
self.control_fields
.get(tag)
.and_then(|v| v.first())
.map(String::as_str)
}
pub fn set_heading(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn heading(&self) -> Option<&Field> {
for tag in &["100", "110", "111", "130", "148", "150", "151", "155"] {
if let Some(fields) = self.fields.get(*tag) {
if let Some(field) = fields.first() {
return Some(field);
}
}
}
None
}
#[must_use]
pub fn heading_type(&self) -> Option<HeadingType> {
self.heading().and_then(|f| match f.tag.as_str() {
"100" => Some(HeadingType::PersonalName),
"110" => Some(HeadingType::CorporateName),
"111" => Some(HeadingType::MeetingName),
"130" => Some(HeadingType::UniformTitle),
"148" => Some(HeadingType::ChronologicalTerm),
"150" => Some(HeadingType::TopicalTerm),
"151" => Some(HeadingType::GeographicName),
"155" => Some(HeadingType::GenreFormTerm),
_ => None,
})
}
pub fn add_see_from_tracing(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn see_from_tracings(&self) -> Vec<&Field> {
self.fields
.iter()
.filter(|(tag, _)| tag.starts_with('4'))
.flat_map(|(_, fields)| fields.iter())
.collect()
}
pub fn add_see_also_tracing(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn see_also_tracings(&self) -> Vec<&Field> {
self.fields
.iter()
.filter(|(tag, _)| tag.starts_with('5'))
.flat_map(|(_, fields)| fields.iter())
.collect()
}
pub fn add_note(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn notes(&self) -> Vec<&Field> {
self.fields
.iter()
.filter(|(tag, _)| {
tag.starts_with('6') && (tag != &"650" && tag != &"651" && tag != &"655")
})
.flat_map(|(_, fields)| fields.iter())
.collect()
}
#[must_use]
pub fn source_data_found(&self) -> Vec<&Field> {
self.fields
.get("670")
.map(|fields| fields.iter().collect())
.unwrap_or_default()
}
#[must_use]
pub fn source_data_not_found(&self) -> Vec<&Field> {
self.fields
.get("671")
.map(|fields| fields.iter().collect())
.unwrap_or_default()
}
pub fn add_linking_entry(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn linking_entries(&self) -> Vec<&Field> {
self.fields
.iter()
.filter(|(tag, _)| tag.starts_with('7'))
.flat_map(|(_, fields)| fields.iter())
.collect()
}
pub fn add_field(&mut self, field: Field) {
self.fields
.entry(field.tag.clone())
.or_default()
.push(field);
}
#[must_use]
pub fn get_fields(&self, tag: &str) -> Option<&[Field]> {
self.fields.get(tag).map(Vec::as_slice)
}
#[must_use]
pub fn kind_of_record(&self) -> Option<KindOfRecord> {
match control_field_char_at(self, "008", 9)? {
'a' => Some(KindOfRecord::EstablishedHeading),
'b' => Some(KindOfRecord::ReferenceUntracted),
'c' => Some(KindOfRecord::ReferenceTraced),
'd' => Some(KindOfRecord::Subdivision),
'e' => Some(KindOfRecord::NodeLabel),
'f' => Some(KindOfRecord::EstablishedHeadingAndSubdivision),
'g' => Some(KindOfRecord::ReferenceAndSubdivision),
_ => None,
}
}
#[must_use]
pub fn level_of_establishment(&self) -> Option<LevelOfEstablishment> {
match control_field_char_at(self, "008", 33)? {
'a' => Some(LevelOfEstablishment::FullyEstablished),
'b' => Some(LevelOfEstablishment::Memorandum),
'c' => Some(LevelOfEstablishment::Provisional),
'd' => Some(LevelOfEstablishment::Preliminary),
'n' => Some(LevelOfEstablishment::NotApplicable),
_ => None,
}
}
#[must_use]
pub fn is_established(&self) -> bool {
matches!(
self.kind_of_record(),
Some(KindOfRecord::EstablishedHeading | KindOfRecord::EstablishedHeadingAndSubdivision)
)
}
#[must_use]
pub fn is_reference(&self) -> bool {
matches!(
self.kind_of_record(),
Some(
KindOfRecord::ReferenceUntracted
| KindOfRecord::ReferenceTraced
| KindOfRecord::ReferenceAndSubdivision
)
)
}
}
impl MarcRecord for AuthorityRecord {
fn leader(&self) -> &Leader {
&self.leader
}
fn leader_mut(&mut self) -> &mut Leader {
&mut self.leader
}
fn add_control_field(&mut self, tag: impl Into<String>, value: impl Into<String>) {
self.control_fields
.entry(tag.into())
.or_default()
.push(value.into());
}
fn get_control_field(&self, tag: &str) -> Option<&str> {
self.control_fields
.get(tag)
.and_then(|v| v.first())
.map(String::as_str)
}
fn control_fields_iter(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
Box::new(self.control_fields.iter().flat_map(|(tag, values)| {
values
.iter()
.map(move |value| (tag.as_str(), value.as_str()))
}))
}
fn get_fields(&self, tag: &str) -> Option<&[Field]> {
self.fields.get(tag).map(std::vec::Vec::as_slice)
}
fn get_field(&self, tag: &str) -> Option<&Field> {
self.fields.get(tag).and_then(|v| v.first())
}
}
#[derive(Debug)]
pub struct AuthorityRecordBuilder {
record: AuthorityRecord,
}
impl AuthorityRecordBuilder {
#[must_use]
pub fn control_field(mut self, tag: String, value: String) -> Self {
self.record.add_control_field(tag, value);
self
}
#[must_use]
pub fn heading(mut self, field: Field) -> Self {
self.record.set_heading(field);
self
}
#[must_use]
pub fn add_see_from(mut self, field: Field) -> Self {
self.record.add_see_from_tracing(field);
self
}
#[must_use]
pub fn add_see_also(mut self, field: Field) -> Self {
self.record.add_see_also_tracing(field);
self
}
#[must_use]
pub fn add_note(mut self, field: Field) -> Self {
self.record.add_note(field);
self
}
#[must_use]
pub fn add_linking_entry(mut self, field: Field) -> Self {
self.record.add_linking_entry(field);
self
}
#[must_use]
pub fn add_field(mut self, field: Field) -> Self {
self.record.add_field(field);
self
}
#[must_use]
pub fn build(self) -> AuthorityRecord {
self.record
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::record::Subfield;
fn create_test_leader() -> Leader {
Leader {
record_length: 1024,
record_status: 'n',
record_type: 'z',
bibliographic_level: '|',
control_record_type: ' ',
character_coding: ' ',
indicator_count: 2,
subfield_code_count: 2,
data_base_address: 300,
encoding_level: 'n',
cataloging_form: 'a',
multipart_level: ' ',
reserved: "4500".to_string(),
}
}
#[test]
fn test_create_authority_record() {
let leader = create_test_leader();
let record = AuthorityRecord::new(leader);
assert!(record.heading().is_none());
assert!(record.see_from_tracings().is_empty());
assert!(record.see_also_tracings().is_empty());
}
#[test]
fn test_authority_record_builder() {
let leader = create_test_leader();
let record = AuthorityRecord::builder(leader)
.control_field(
"008".to_string(),
"850101n| a azannaabn |a aaa ".to_string(),
)
.build();
assert_eq!(
record.get_control_field("008"),
Some("850101n| a azannaabn |a aaa ")
);
}
#[test]
fn test_heading_type_detection() {
let leader = create_test_leader();
let field = Field {
tag: "100".to_string(),
indicator1: '1',
indicator2: ' ',
subfields: smallvec::smallvec![],
};
let record = AuthorityRecord::builder(leader).heading(field).build();
assert_eq!(record.heading_type(), Some(HeadingType::PersonalName));
let field = Field {
tag: "150".to_string(),
indicator1: ' ',
indicator2: '0',
subfields: smallvec::smallvec![],
};
let record = AuthorityRecord::builder(create_test_leader())
.heading(field)
.build();
assert_eq!(record.heading_type(), Some(HeadingType::TopicalTerm));
}
#[test]
fn test_kind_of_record_parsing() {
let leader = create_test_leader();
let record = AuthorityRecord::builder(leader)
.control_field(
"008".to_string(),
"850101n| a azannaabn |a aaa ".to_string(),
)
.build();
assert_eq!(
record.kind_of_record(),
Some(KindOfRecord::EstablishedHeading)
);
let record = AuthorityRecord::builder(create_test_leader())
.control_field(
"008".to_string(),
"850101n| b azannaabn |a aaa ".to_string(),
)
.build();
assert_eq!(
record.kind_of_record(),
Some(KindOfRecord::ReferenceUntracted)
);
let record = AuthorityRecord::builder(create_test_leader())
.control_field(
"008".to_string(),
"850101n| d azannaabn |a aaa ".to_string(),
)
.build();
assert_eq!(record.kind_of_record(), Some(KindOfRecord::Subdivision));
}
#[test]
fn test_level_of_establishment_parsing() {
let leader = create_test_leader();
let mut field_008 = vec!['8', '5', '0', '1', '0', '1', 'n', '|', ' ', 'a'];
field_008.extend(vec![
'z', 'a', 'n', 'n', 'a', 'a', 'b', 'n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ',
]);
field_008.push('a');
field_008.extend(vec![' ', ' ', ' ', ' ', ' ', ' ']);
let field_008_str: String = field_008.iter().collect();
let record = AuthorityRecord::builder(leader)
.control_field("008".to_string(), field_008_str)
.build();
assert_eq!(
record.level_of_establishment(),
Some(LevelOfEstablishment::FullyEstablished)
);
let mut field_008 = vec!['8', '5', '0', '1', '0', '1', 'n', '|', ' ', 'a'];
field_008.extend(vec![
'z', 'a', 'n', 'n', 'a', 'a', 'b', 'n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ',
]);
field_008.push('d');
field_008.extend(vec![' ', ' ', ' ', ' ', ' ', ' ']);
let field_008_str: String = field_008.iter().collect();
let record = AuthorityRecord::builder(create_test_leader())
.control_field("008".to_string(), field_008_str)
.build();
assert_eq!(
record.level_of_establishment(),
Some(LevelOfEstablishment::Preliminary)
);
}
#[test]
fn test_is_established() {
let leader = create_test_leader();
let record = AuthorityRecord::builder(leader)
.control_field(
"008".to_string(),
"850101n| a azannaabn |a aaa ".to_string(),
)
.build();
assert!(record.is_established());
let record = AuthorityRecord::builder(create_test_leader())
.control_field(
"008".to_string(),
"850101n| b azannaabn |a aaa ".to_string(),
)
.build();
assert!(!record.is_established());
}
#[test]
fn test_is_reference() {
let leader = create_test_leader();
let record = AuthorityRecord::builder(leader)
.control_field(
"008".to_string(),
"850101n| b azannaabn |a aaa ".to_string(),
)
.build();
assert!(record.is_reference());
let record = AuthorityRecord::builder(create_test_leader())
.control_field(
"008".to_string(),
"850101n| a azannaabn |a aaa ".to_string(),
)
.build();
assert!(!record.is_reference());
}
#[test]
fn test_add_tracings() {
let leader = create_test_leader();
let see_from = Field {
tag: "400".to_string(),
indicator1: '1',
indicator2: ' ',
subfields: smallvec::smallvec![Subfield {
code: 'a',
value: "Smith, John".to_string(),
}],
};
let see_also = Field {
tag: "500".to_string(),
indicator1: '1',
indicator2: ' ',
subfields: smallvec::smallvec![Subfield {
code: 'a',
value: "Smith, J. (John)".to_string(),
}],
};
let record = AuthorityRecord::builder(leader)
.add_see_from(see_from)
.add_see_also(see_also)
.build();
assert_eq!(record.see_from_tracings().len(), 1);
assert_eq!(record.see_also_tracings().len(), 1);
}
#[test]
fn test_add_notes() {
let leader = create_test_leader();
let source_note = Field {
tag: "670".to_string(),
indicator1: ' ',
indicator2: ' ',
subfields: smallvec::smallvec![Subfield {
code: 'a',
value: "DNB, 1985".to_string(),
}],
};
let record = AuthorityRecord::builder(leader)
.add_note(source_note)
.build();
assert_eq!(record.notes().len(), 1);
assert_eq!(record.source_data_found().len(), 1);
}
#[test]
fn test_control_field_operations() {
let leader = create_test_leader();
let mut record = AuthorityRecord::new(leader);
record.add_control_field("001".to_string(), "n79021800".to_string());
record.add_control_field("005".to_string(), "19850104".to_string());
assert_eq!(record.get_control_field("001"), Some("n79021800"));
assert_eq!(record.get_control_field("005"), Some("19850104"));
}
}