diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e9dec71..fa8a6726c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + - [538](https://github.com/thoth-pub/thoth/issues/538) - Update Project MUSE ONIX 3.0 export to reflect new specifications provided by Project MUSE. ## [[0.12.6]](https://github.com/thoth-pub/thoth/releases/tag/v0.12.6) - 2024-06-17 ### Fixed diff --git a/thoth-export-server/src/xml/onix3_project_muse.rs b/thoth-export-server/src/xml/onix3_project_muse.rs index fabbac12c..669f71287 100644 --- a/thoth-export-server/src/xml/onix3_project_muse.rs +++ b/thoth-export-server/src/xml/onix3_project_muse.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::io::Write; use thoth_client::{ ContributionType, LanguageRelation, PublicationType, SubjectType, Work, WorkContributions, - WorkLanguages, WorkPublications, WorkStatus, WorkType, + WorkFundings, WorkIssues, WorkLanguages, WorkPublications, WorkStatus, WorkType, }; use xml::writer::{EventWriter, XmlEvent}; @@ -158,6 +158,9 @@ impl XmlElementBlock for Work { }) })?; } + for issue in &self.issues { + XmlElementBlock::::xml_element(issue, w).ok(); + } write_element_block("TitleDetail", w, |w| { // 01 Distinctive title (book) write_element_block("TitleType", w, |w| { @@ -184,6 +187,29 @@ impl XmlElementBlock for Work { for contribution in &self.contributions { XmlElementBlock::::xml_element(contribution, w).ok(); } + if let Some(edition) = &self.edition { + if let Some(edition_statement) = match edition { + // Spell out commonest ordinals + 1 => Some("First edition.".to_string()), + 2 => Some("Second edition.".to_string()), + 3 => Some("Third edition.".to_string()), + _ => { + let suffix = match edition % 10 { + 1 if edition % 100 != 11 => "st", + 2 if edition % 100 != 12 => "nd", + 3 if edition % 100 != 13 => "rd", + _ => "th", + }; + Some(format!("{}{} edition.", edition, suffix)) + } + } { + write_element_block("EditionStatement", w, |w| { + w.write(XmlEvent::Characters(&edition_statement)) + .map_err(|e| e.into()) + })?; + } + } + for language in &self.languages { XmlElementBlock::::xml_element(language, w).ok(); } @@ -218,6 +244,16 @@ impl XmlElementBlock for Work { })?; } } + write_element_block("Audience", w, |w| { + // 01 ONIX audience codes + write_element_block("AudienceCodeType", w, |w| { + w.write(XmlEvent::Characters("01")).map_err(|e| e.into()) + })?; + // 06 Professional and scholarly + write_element_block("AudienceCodeValue", w, |w| { + w.write(XmlEvent::Characters("06")).map_err(|e| e.into()) + }) + })?; Ok(()) })?; write_element_block("CollateralDetail", w, |w| { @@ -289,6 +325,9 @@ impl XmlElementBlock for Work { .map_err(|e| e.into()) }) })?; + for funding in &self.fundings { + XmlElementBlock::::xml_element(funding, w).ok(); + } if let Some(place) = &self.place { write_element_block("CityOfPublication", w, |w| { w.write(XmlEvent::Characters(place)).map_err(|e| e.into()) @@ -314,6 +353,12 @@ impl XmlElementBlock for Work { }, ) })?; + write_element_block("CopyrightStatement", w, |w| { + write_element_block("CopyrightYear", w, |w| { + w.write(XmlEvent::Characters(&date.format("%Y").to_string())) + .map_err(|e| e.into()) + }) + })?; } if let Some(date) = &self.withdrawn_date { write_element_block("PublishingDate", w, |w| { @@ -372,7 +417,7 @@ impl XmlElementBlock for Work { supplies.insert( landing_page.to_string(), ( - "01".to_string(), + "02".to_string(), "Publisher's website: web shop".to_string(), ), ); @@ -500,6 +545,53 @@ impl XmlElement for LanguageRelation { } } +impl XmlElementBlock for WorkFundings { + fn xml_element(&self, w: &mut EventWriter) -> ThothResult<()> { + write_element_block("Publisher", w, |w| { + // 16 Funding body + write_element_block("PublishingRole", w, |w| { + w.write(XmlEvent::Characters("16")).map_err(|e| e.into()) + })?; + write_element_block("PublisherName", w, |w| { + w.write(XmlEvent::Characters(&self.institution.institution_name)) + .map_err(|e| e.into()) + })?; + Ok(()) + }) + } +} + +impl XmlElementBlock for WorkIssues { + fn xml_element(&self, w: &mut EventWriter) -> ThothResult<()> { + write_element_block("Collection", w, |w| { + // 10 Publisher collection (e.g. series) + write_element_block("CollectionType", w, |w| { + w.write(XmlEvent::Characters("10")).map_err(|e| e.into()) + })?; + write_element_block("TitleDetail", w, |w| { + // 01 Cover title (serial) + write_element_block("TitleType", w, |w| { + w.write(XmlEvent::Characters("01")).map_err(|e| e.into()) + })?; + write_element_block("TitleElement", w, |w| { + // 02 Collection level + write_element_block("TitleElementLevel", w, |w| { + w.write(XmlEvent::Characters("02")).map_err(|e| e.into()) + })?; + write_element_block("SequenceNumber", w, |w| { + w.write(XmlEvent::Characters(&self.issue_ordinal.to_string())) + .map_err(|e| e.into()) + })?; + write_element_block("TitleText", w, |w| { + w.write(XmlEvent::Characters(&self.series.series_name)) + .map_err(|e| e.into()) + }) + }) + }) + }) + } +} + impl XmlElement for ContributionType { const ELEMENT: &'static str = "ContributorRole"; @@ -905,6 +997,7 @@ mod tests { assert!(output.contains(r#" 01"#)); assert!(output.contains(r#" Book Title"#)); assert!(output.contains(r#" Book Subtitle"#)); + assert!(output.contains(r#" First edition."#)); assert!(output.contains(r#" "#)); assert!(output.contains(r#" 00"#)); assert!(output.contains(r#" 334"#)); @@ -920,6 +1013,11 @@ mod tests { assert!(output.contains(r#" JWA"#)); assert!(output.contains(r#" B2"#)); assert!(output.contains(r#" custom1"#)); + assert!(output.contains(r#" 16"#)); + assert!(output.contains(r#" Name of institution"#)); + assert!(output.contains(r#" "#)); + assert!(output.contains(r#" 01"#)); + assert!(output.contains(r#" 06"#)); assert!(output.contains(r#" "#)); assert!(output.contains(r#" "#)); assert!(output.contains(r#" 03"#)); @@ -942,6 +1040,8 @@ mod tests { assert!(output.contains(r#" "#)); assert!(output.contains(r#" 01"#)); assert!(output.contains(r#" 19991231"#)); + assert!(output.contains(r#" "#)); + assert!(output.contains(r#" 1999"#)); assert!(output.contains(r#" "#)); assert!(output.contains(r#" 06"#)); assert!(output.contains(r#" "#)); @@ -953,7 +1053,7 @@ mod tests { assert!(output.contains(r#" 09"#)); assert!(output.contains(r#" OA Editions"#)); assert!(output.contains(r#" "#)); - assert!(output.contains(r#" 01"#)); + assert!(output.contains(r#" 02"#)); assert!(output.contains( r#" Publisher's website: web shop"# )); @@ -966,15 +1066,15 @@ mod tests { assert!(output.contains(r#" Publisher's website: download the title"#)); assert!(output .contains(r#" https://www.book.com/pdf_fulltext"#)); + assert!(output.contains(r#" "#)); + assert!(output.contains(r#" 10"#)); + assert!(output.contains(r#" 02"#)); + assert!(output.contains(r#" 1"#)); + assert!(output.contains(r#" Name of series"#)); // Test that OAPEN-only blocks are not output in Project MUSE format assert!(!output.contains(r#" 20"#)); assert!(!output.contains(r#" keyword1"#)); - assert!(!output.contains(r#" "#)); - assert!(!output.contains(r#" 01"#)); - assert!(!output.contains(r#" 06"#)); - assert!(!output.contains(r#" 16"#)); - assert!(!output.contains(r#" Name of institution"#)); assert!(!output.contains(r#" "#)); assert!(!output.contains(r#" "#)); assert!(!output.contains(r#" 01"#)); @@ -984,11 +1084,6 @@ mod tests { assert!(!output.contains(r#" Name of project"#)); assert!(!output.contains(r#" grantnumber"#)); assert!(!output.contains(r#" Number of grant"#)); - assert!(!output.contains(r#" "#)); - assert!(!output.contains(r#" 10"#)); - assert!(!output.contains(r#" 02"#)); - assert!(!output.contains(r#" 1"#)); - assert!(!output.contains(r#" Name of series"#)); // Add withdrawn_date test_work.withdrawn_date = chrono::NaiveDate::from_ymd_opt(2020, 12, 31); @@ -1051,7 +1146,7 @@ mod tests { assert!(!output.contains(r#" 01"#)); assert!(!output.contains(r#" 19991231"#)); // No landing page supplied: only one SupplyDetail block, linking to PDF download - assert!(!output.contains(r#" 01"#)); + assert!(!output.contains(r#" 02"#)); assert!(!output.contains( r#" Publisher's website: web shop"# ));