Skip to content

Commit 84f9f03

Browse files
committed
Merge branch 'release/v1.0.4'
2 parents 76f1870 + 73e1e15 commit 84f9f03

10 files changed

Lines changed: 110 additions & 62 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ db/
33
target/
44
machinekey/
55
coverage/
6+
reports/
7+
scripts/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [[1.0.4]](https://github.com/thoth-pub/thoth/releases/tag/v1.0.4) - 2026-04-13
10+
### Fixed
11+
- Aggregate Crossref Crossmark updates into a single `<updates>` block
12+
913
## [[1.0.3]](https://github.com/thoth-pub/thoth/releases/tag/v1.0.3) - 2026-04-07
1014
### Fixed
1115
- [741](https://github.com/thoth-pub/thoth/pull/741) - Harden JATS rich-text handling by rejecting malformed or nested markup and abstract line breaks on write, and normalise Crossref abstract output to avoid invalid nested `jats:p` and `jats:break` elements

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"
@@ -15,10 +15,10 @@ maintenance = { status = "actively-developed" }
1515
members = ["thoth-api", "thoth-api-server", "thoth-client", "thoth-errors", "thoth-export-server"]
1616

1717
[dependencies]
18-
thoth-api = { version = "=1.0.3", path = "thoth-api", features = ["backend"] }
19-
thoth-api-server = { version = "=1.0.3", path = "thoth-api-server" }
20-
thoth-errors = { version = "=1.0.3", path = "thoth-errors" }
21-
thoth-export-server = { version = "=1.0.3", path = "thoth-export-server" }
18+
thoth-api = { version = "=1.0.4", path = "thoth-api", features = ["backend"] }
19+
thoth-api-server = { version = "=1.0.4", path = "thoth-api-server" }
20+
thoth-errors = { version = "=1.0.4", path = "thoth-errors" }
21+
thoth-export-server = { version = "=1.0.4", path = "thoth-export-server" }
2222
base64 = "0.22.1"
2323
clap = { version = "4.5.32", features = ["cargo", "env"] }
2424
dialoguer = { version = "0.11.0", features = ["password"] }

thoth-api-server/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth-api-server"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"
@@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth"
99
readme = "README.md"
1010

1111
[dependencies]
12-
thoth-api = { version = "=1.0.3", path = "../thoth-api", features = ["backend"] }
13-
thoth-errors = { version = "=1.0.3", path = "../thoth-errors" }
12+
thoth-api = { version = "=1.0.4", path = "../thoth-api", features = ["backend"] }
13+
thoth-errors = { version = "=1.0.4", path = "../thoth-errors" }
1414
actix-web = "4.10"
1515
actix-cors = "0.7.1"
1616
actix-http = "3.10.0"

thoth-api/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth-api"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"
@@ -31,7 +31,7 @@ backend = [
3131
]
3232

3333
[dependencies]
34-
thoth-errors = { version = "=1.0.3", path = "../thoth-errors" }
34+
thoth-errors = { version = "=1.0.4", path = "../thoth-errors" }
3535
actix-web = { version = "4.10", optional = true }
3636
isbn = "0.6.0"
3737
chrono = { version = "0.4.40", features = ["serde"] }

thoth-client/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth-client"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"
@@ -10,8 +10,8 @@ readme = "README.md"
1010
build = "build.rs"
1111

1212
[dependencies]
13-
thoth-api = {version = "=1.0.3", path = "../thoth-api" }
14-
thoth-errors = {version = "=1.0.3", path = "../thoth-errors" }
13+
thoth-api = {version = "=1.0.4", path = "../thoth-api" }
14+
thoth-errors = {version = "=1.0.4", path = "../thoth-errors" }
1515
graphql_client = "0.14.0"
1616
chrono = { version = "0.4.40", features = ["serde"] }
1717
reqwest = { version = "0.12", features = ["json"] }
@@ -22,4 +22,4 @@ serde_json = "1.0"
2222
uuid = { version = "1.16.0", features = ["serde"] }
2323

2424
[build-dependencies]
25-
thoth-api = { version = "=1.0.3", path = "../thoth-api", features = ["backend"] }
25+
thoth-api = { version = "=1.0.4", path = "../thoth-api", features = ["backend"] }

thoth-errors/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth-errors"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"

thoth-export-server/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "thoth-export-server"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
authors = ["Javier Arias <javi@thoth.pub>", "Ross Higman <ross@thoth.pub>"]
55
edition = "2021"
66
license = "Apache-2.0"
@@ -10,9 +10,9 @@ readme = "README.md"
1010
build = "build.rs"
1111

1212
[dependencies]
13-
thoth-api = { version = "=1.0.3", path = "../thoth-api", features = ["backend"] }
14-
thoth-errors = { version = "=1.0.3", path = "../thoth-errors" }
15-
thoth-client = { version = "=1.0.3", path = "../thoth-client" }
13+
thoth-api = { version = "=1.0.4", path = "../thoth-api", features = ["backend"] }
14+
thoth-errors = { version = "=1.0.4", path = "../thoth-errors" }
15+
thoth-client = { version = "=1.0.4", path = "../thoth-client" }
1616
actix-web = "4.10"
1717
actix-cors = "0.7.1"
1818
cc_license = "0.1.0"

thoth-export-server/src/xml/doideposit_crossref.rs

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -575,55 +575,49 @@ fn write_crossmark_funding_access<W: Write>(
575575
w.write(XmlEvent::Characters(&crossmark_doi.to_string()))
576576
.map_err(|e| e.into())
577577
})?;
578+
579+
let mut updates: Vec<(&str, String, String)> = Vec::new();
578580
if update_type == "new_edition" {
579581
if let Some(publication_date) = &work.publication_date {
580-
for relation in work.relations.iter().filter(|r| {
581-
r.relation_type == RelationType::REPLACES && r.related_work.doi.is_some()
582-
}) {
583-
// only output crossmark update if there's a DOI for the Superseded Work and publication date for the Active Work
584-
// metadata is output on the Active Work, rather than the Superseded one, see
585-
// https://community.crossref.org/t/appropriate-doi-to-use-in-crossmark-new-edition-and-withdrawal-update-types/6189/2
586-
let doi = relation.related_work.doi.as_ref().unwrap();
587-
588-
write_element_block("updates", w, |w| {
589-
write_full_element_block(
590-
"update",
591-
Some(vec![
592-
("type", update_type),
593-
("date", &publication_date.to_string()),
594-
]),
595-
w,
596-
|w| {
597-
w.write(XmlEvent::Characters(&doi.to_string()))
598-
.map_err(|e| e.into())
599-
},
600-
)
601-
})?;
602-
}
582+
let publication_date = publication_date.to_string();
583+
updates.extend(work.relations.iter().filter_map(|relation| {
584+
if relation.relation_type == RelationType::REPLACES {
585+
relation.related_work.doi.as_ref().map(|doi| {
586+
// only output crossmark update if there's a DOI for the Superseded Work
587+
// and publication date for the Active Work. Metadata is output on the
588+
// Active Work, rather than the Superseded one, see
589+
// https://community.crossref.org/t/appropriate-doi-to-use-in-crossmark-new-edition-and-withdrawal-update-types/6189/2
590+
(update_type, publication_date.clone(), doi.to_string())
591+
})
592+
} else {
593+
None
594+
}
595+
}));
603596
}
604597
} else if update_type == "withdrawal" {
605598
// for a withdrawal, only output crossmark update
606599
// if there's a withdrawn date and DOI for the Withdrawn work.
607600
if let Some(withdrawn_date) = &work.withdrawn_date {
608601
if let Some(doi) = &work.doi {
609-
write_element_block("updates", w, |w| {
610-
write_full_element_block(
611-
"update",
612-
Some(vec![
613-
("type", update_type),
614-
("date", &withdrawn_date.to_string()),
615-
]),
616-
w,
617-
|w| {
618-
w.write(XmlEvent::Characters(&doi.to_string()))
619-
.map_err(|e| e.into())
620-
},
621-
)
622-
})?;
602+
updates.push((update_type, withdrawn_date.to_string(), doi.to_string()));
623603
}
624604
}
625605
}
626606

607+
if !updates.is_empty() {
608+
write_element_block("updates", w, |w| {
609+
for (update_type, update_date, doi) in &updates {
610+
write_full_element_block(
611+
"update",
612+
Some(vec![("type", *update_type), ("date", update_date.as_str())]),
613+
w,
614+
|w| w.write(XmlEvent::Characters(doi)).map_err(|e| e.into()),
615+
)?;
616+
}
617+
Ok(())
618+
})?;
619+
}
620+
627621
// If crossmark metadata is included, funding and access data must be inside the <crossmark> element
628622
// within <custom_metadata> tag. If no funding or access data exist, don't include <custom_metadata> tag.
629623
if work.license.is_some() || !work.fundings.is_empty() {
@@ -2189,19 +2183,67 @@ mod tests {
21892183
fundings: vec![],
21902184
languages: vec![],
21912185
},
2186+
},
2187+
WorkRelations {
2188+
relation_type: RelationType::REPLACES,
2189+
relation_ordinal: 3,
2190+
related_work: WorkRelationsRelatedWork {
2191+
work_status: WorkStatus::SUPERSEDED,
2192+
titles: vec![thoth_client::WorkRelationsRelatedWorkTitles {
2193+
title_id: Uuid::from_str("00000000-0000-0000-CCCC-000000000003").unwrap(),
2194+
locale_code: thoth_client::LocaleCode::EN,
2195+
full_title: "Book Title: Book Subtitle: 2nd Edition".to_string(),
2196+
title: "Part".to_string(),
2197+
subtitle: Some("Two".to_string()),
2198+
canonical: true,
2199+
}],
2200+
abstracts: vec![],
2201+
edition: None,
2202+
doi: Some(Doi::from_str("https://doi.org/10.00003/older_edition").unwrap()),
2203+
publication_date: chrono::NaiveDate::from_ymd_opt(1996, 2, 28),
2204+
withdrawn_date: chrono::NaiveDate::from_ymd_opt(1997, 2, 28),
2205+
license: None,
2206+
copyright_holder: None,
2207+
general_note: None,
2208+
place: Some("Older Place".to_string()),
2209+
first_page: None,
2210+
last_page: None,
2211+
page_count: None,
2212+
page_interval: None,
2213+
landing_page: Some("https://www.book.com/part_two".to_string()),
2214+
imprint: WorkRelationsRelatedWorkImprint {
2215+
crossmark_doi: None,
2216+
publisher: WorkRelationsRelatedWorkImprintPublisher {
2217+
publisher_name: "Part Two Publisher".to_string(),
2218+
},
2219+
},
2220+
contributions: vec![],
2221+
publications: vec![],
2222+
references: vec![],
2223+
fundings: vec![],
2224+
languages: vec![],
2225+
},
21922226
}];
21932227
let output = generate_test_output(true, &test_work);
21942228
assert!(output.contains(r#" <crossmark>"#));
21952229
assert!(output.contains(r#" <crossmark_version>2</crossmark_version>"#));
21962230
assert!(output
21972231
.contains(r#" <crossmark_policy>10.00001/crossmark_policy</crossmark_policy>"#));
21982232
assert!(output.contains(r#" <updates>"#));
2233+
assert_eq!(output.matches(r#" <updates>"#).count(), 1);
21992234
assert!(output.contains(
22002235
r#" <update type="new_edition" date="1999-12-31">10.00002/old_edition</update>"#
22012236
));
2237+
assert!(output.contains(
2238+
r#" <update type="new_edition" date="1999-12-31">10.00003/older_edition</update>"#
2239+
));
22022240
assert!(output.contains(r#" </updates>"#));
22032241
assert!(output.contains(r#" <custom_metadata>"#));
22042242
assert!(output.contains(r#" </custom_metadata>"#));
2243+
assert!(
2244+
output.find(r#" </updates>"#).unwrap()
2245+
< output.find(r#" <custom_metadata>"#).unwrap()
2246+
);
22052247
assert!(output.contains(r#" </crossmark>"#));
22062248

22072249
// Remove/change some values to test variations/non-output of optional blocks

0 commit comments

Comments
 (0)