Skip to content

Commit 3705e80

Browse files
committed
Metadata & identifiers
1 parent cff1d72 commit 3705e80

File tree

6 files changed

+240
-82
lines changed

6 files changed

+240
-82
lines changed

build/src/bib_render.rs

Lines changed: 131 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,41 @@ pub type InlineCiteRenderer = Box<dyn Fn(&str, Option<Markup>) -> Markup + Send
2727

2828
pub type RenderedBibliography = BTreeMap<String, RenderedEntry>;
2929

30+
fn backfill_doi(reference: &Reference) -> Cow<'_, Reference> {
31+
if reference.doi().is_none() {
32+
if let Some(url) = reference.common().url.as_deref() {
33+
if let Some(doi) = url.strip_prefix("https://doi.org/") {
34+
let mut r = reference.clone();
35+
r.common_mut().identifiers.insert(format!("doi:{doi}"));
36+
return Cow::Owned(r);
37+
}
38+
}
39+
}
40+
41+
Cow::Borrowed(reference)
42+
}
43+
44+
fn backfill_handle(reference: &Reference) -> Cow<'_, Reference> {
45+
if reference.hdl().is_none() {
46+
if let Some(url) = reference.common().url.as_deref() {
47+
if let Some(hdl) = url.strip_prefix("https://hdl.handle.net/") {
48+
// n2t doesn't handle handles with query strings
49+
if !hdl.contains('?') {
50+
let mut r = reference.clone();
51+
if let Some((_, ark)) = hdl.split_once("ark:") {
52+
r.common_mut().identifiers.insert(format!("ark:{ark}"));
53+
} else {
54+
r.common_mut().identifiers.insert(format!("hdl:{hdl}"));
55+
}
56+
return Cow::Owned(r);
57+
}
58+
}
59+
}
60+
}
61+
62+
Cow::Borrowed(reference)
63+
}
64+
3065
pub fn to_rendered(bib: &Bibliography) -> RenderedBibliography {
3166
let mut result = BTreeMap::new();
3267
for (key, reference) in &bib.references {
@@ -48,7 +83,9 @@ pub fn to_rendered(bib: &Bibliography) -> RenderedBibliography {
4883
.or_else(|| reference.publisher().map(|l| l.value.clone()))
4984
.unwrap_or_default();
5085

51-
let reference = render_ref(key, reference);
86+
let reference = backfill_doi(reference);
87+
let reference = backfill_handle(&reference);
88+
let reference = render_ref(key, &reference);
5289

5390
result.insert(
5491
key.clone(),
@@ -129,36 +166,64 @@ fn inline_cite(reference: &Reference) -> Option<InlineCiteRenderer> {
129166

130167
fn book_item_type(book: &Book) -> &'static str {
131168
if book.volume.is_none() {
132-
"Book"
169+
"Book bibo:Book"
133170
} else {
134-
"Book PublicationVolume"
171+
"Book bibo:Book PublicationVolume"
135172
}
136173
}
137174

138175
fn thesis_item_type(thesis: &Thesis) -> &'static str {
139176
if thesis.volume.is_none() {
140-
"Thesis"
177+
"Thesis bibo:Thesis"
141178
} else {
142-
"Thesis PublicationVolume"
179+
"Thesis bibo:Thesis PublicationVolume"
143180
}
144181
}
145182

146183
fn item_type(reference: &Reference) -> &'static str {
147184
match reference {
148-
Reference::ConferencePaper(_) | Reference::JournalArticle(_) => "ScholarlyArticle",
185+
Reference::ConferencePaper(_) => "ScholarlyArticle bibo:AcademicArticle",
186+
Reference::JournalArticle(_) => "ScholarlyArticle bibo:AcademicArticle",
149187
Reference::NewspaperArticle(_) | Reference::MagazineArticle(_) => "Article",
150188
Reference::Book(b) => book_item_type(b),
151189
Reference::Thesis(t) => thesis_item_type(t),
152-
Reference::Chapter(_) => "Chapter",
153-
Reference::Patent(_) | Reference::Document(_) => "CreativeWork",
154-
Reference::WebPage(_) => "WebPage",
190+
Reference::Chapter(_) => "Chapter bibo:Chapter",
191+
Reference::Patent(_) => "CreativeWork bibo:Patent",
192+
Reference::Document(_) => "CreativeWork bibo:Document",
193+
Reference::WebPage(_) => "WebPage bibo:Webpage",
155194
}
156195
}
157196

197+
fn item_resource(reference: &Reference) -> Option<String> {
198+
if let Some(doi) = reference.doi() {
199+
// DOI can be treated as a URN, since it is registered
200+
return Some(format!("urn:{doi}"));
201+
}
202+
203+
match reference {
204+
// Reference books with ISBNs via URN
205+
Reference::Book(Book {
206+
isbn: Some(isbn), ..
207+
}) => return Some(format!("urn:isbn:{}", isbn.0)),
208+
Reference::JournalArticle(JournalArticle {
209+
common: Common { url: Some(url), .. },
210+
..
211+
}) => {
212+
// use a stable URI as resource identifier
213+
if url.starts_with("https://jstor.org/stable/") {
214+
return Some(url.clone());
215+
}
216+
}
217+
_ => {}
218+
}
219+
220+
None
221+
}
222+
158223
fn render_ref(key: &str, reference: &Reference) -> Markup {
159224
html! {
160225
(render_warnings_and_notes(reference))
161-
p #{"ref-" (key)} property="citation" typeof=(item_type(reference)) {
226+
p property="citation" #{"ref-" (key)} resource=[item_resource(reference)] typeof=(item_type(reference)) {
162227
(render_authors(reference))
163228
" (" (render_date(reference)) "). "
164229
(render_title(reference))
@@ -172,6 +237,7 @@ fn render_ref(key: &str, reference: &Reference) -> Markup {
172237
(render_publisher(key, reference))
173238
(render_isbn(reference))
174239
(render_original(reference))
240+
(render_identifiers(reference))
175241
}
176242
}
177243
}
@@ -194,10 +260,10 @@ fn render_warnings_and_notes(reference: &Reference) -> Markup {
194260
fn render_authors(r: &Reference) -> Markup {
195261
html! {
196262
@if let Ok(authors) = r.authors().try_into() {
197-
(render_people(&authors, true, "author"))
263+
(render_people(&authors, true, "author dcterms:creator"))
198264
}
199265
@else if let Ok(editors) = r.editors().try_into() {
200-
(render_people(&editors, true, "editor"))
266+
(render_people(&editors, true, "editor bibo:editor"))
201267
@if editors.len() > 1 {
202268
" (" abbr title="editors" { "eds." } ")"
203269
} @else {
@@ -242,21 +308,27 @@ pub fn needs_space(lang: Option<&str>) -> bool {
242308
}
243309
}
244310

245-
fn render_people(
246-
people: &OneOrMore<Person>,
247-
reverse_first: bool,
248-
item_prop: &'static str,
249-
) -> Markup {
311+
fn person_resource(person: &Person) -> Option<String> {
312+
// use Wikipedia page or ORCID, etc
313+
if let Some(url) = person.url.as_deref() {
314+
return Some(url.to_string());
315+
}
316+
317+
None
318+
}
319+
320+
fn render_people(people: &OneOrMore<Person>, reverse_first: bool, prop: &'static str) -> Markup {
250321
let total = people.len();
251322
html! {
252323
@for (ix, person) in people.into_iter().enumerate() {
253-
@let given = html! { span property="givenName" { (person.given) } };
254-
@let family = person.family.as_deref().map(|f| html! { span property="familyName" { (f) } });
324+
@let given = html! { span property="givenName foaf:givenName" { (person.given) } };
325+
@let family = person.family.as_deref().map(|f| html! { span property="familyName foaf:familyName" { (f) } });
255326
@let needs_space = needs_space(person.lang.as_deref());
256327
@let family_last = family_last(person.lang.as_deref());
257328
@let is_first = ix == 0;
258329
@let is_last = ix == total - 1;
259330
@let name_sep = if needs_space && family.is_some() { " " } else { "" };
331+
@let resource = person_resource(person);
260332

261333
// separator
262334
@if !is_first {
@@ -266,11 +338,13 @@ fn render_people(
266338
}
267339

268340
span.noun
269-
property=(item_prop) typeof="Person"
341+
property=(prop)
342+
resource=[resource]
343+
typeof="Person foaf:Person"
270344
lang=[person.lang.as_deref()] {
271345

272346
// hidden name
273-
meta property="name" content={
347+
meta property="name foaf:name" content={
274348
@let given_str = person.given.as_str();
275349
@let family_str = person.family.as_deref().unwrap_or("");
276350
@let (first, second) = if family_last {
@@ -345,7 +419,7 @@ fn render_title(r: &Reference) -> Markup {
345419
let additional_suffix = r.common().language.as_ref().map(|lang| {
346420
html! {
347421
"text in " (INTL.english_name(lang).unwrap())
348-
meta property="inLanguage" content=(lang);
422+
meta property="inLanguage dcterms:language" content=(lang);
349423
}
350424
});
351425

@@ -354,7 +428,7 @@ fn render_title(r: &Reference) -> Markup {
354428
" [",
355429
"]",
356430
additional_suffix,
357-
Some("alternateName"),
431+
Some("alternateName dcterms:alternative"),
358432
);
359433

360434
if matches!(r, Reference::Book(_) | Reference::Thesis(_)) {
@@ -386,7 +460,7 @@ fn render_title(r: &Reference) -> Markup {
386460
_ => {}
387461
}
388462

389-
let title = render_lstr_just_cite(&title_lstr, None, Some("name"));
463+
let title = render_lstr_just_cite(&title_lstr, None, Some("name dcterms:title"));
390464

391465
html! {
392466
@if let Some(url) = &r.common().url {
@@ -401,15 +475,15 @@ fn render_title(r: &Reference) -> Markup {
401475
@if let Reference::Book(Book { volume: Some(vol), volume_title, ..}) |
402476
Reference::Thesis(Thesis { volume: Some(vol), volume_title, .. }) = r {
403477
" volume "
404-
span property="volumeNumber" { (vol.to_string(true)) }
478+
span property="volumeNumber bibo:volume" { (vol.to_string(true)) }
405479
@if let Some(vol_title) = &volume_title {
406480
": ‘" (render_lstr(vol_title, None, None, None)) "’"
407481
}
408482
}
409483
@if let Reference::Book(b) = r {
410484
@if let Some(edition) = &b.edition {
411485
" ("
412-
span property="bookEdition" {
486+
span property="bookEdition bibo:edition" {
413487
@match edition {
414488
NumberOrString::Num(n) => {
415489
(ordinal(*n)) " edition"
@@ -424,7 +498,11 @@ fn render_title(r: &Reference) -> Markup {
424498
}
425499
}
426500
} else {
427-
let title = render_lstr_just_span(&r.common().title, Some("noun"), Some("name headline"));
501+
let title = render_lstr_just_span(
502+
&r.common().title,
503+
Some("noun"),
504+
Some("name headline dcterms:title"),
505+
);
428506

429507
html! {
430508
"‘"
@@ -456,7 +534,7 @@ fn render_date(reference: &Reference) -> Markup {
456534

457535
html! {
458536
@if let Some(date) = date {
459-
time property="datePublished" datetime=(date.to_iso()) {
537+
time property="datePublished dcterms:issued" datetime=(date.to_iso()) {
460538
@if date.attr().circa {
461539
abbr title="circa" { "c." }
462540
" "
@@ -672,7 +750,7 @@ fn render_container(key: &str, r: &Reference) -> Markup {
672750
Pagination::Num(n) => n.to_string(),
673751
};
674752

675-
span property="pagination" { (pagination) }
753+
span property="pagination bibo:pages" { (pagination) }
676754
}
677755

678756
". "
@@ -697,22 +775,22 @@ fn render_container(key: &str, r: &Reference) -> Markup {
697775
Pagination::Num(n) => n.to_string(),
698776
};
699777

700-
span property="pagination" { (pagination) }
778+
span property="pagination bibo:pages" { (pagination) }
701779
" in "
702780
} @else {
703781
"In "
704782
}
705783

706-
(render_book(key, book, "isPartOf"))
784+
(render_book(key, book, "isPartOf dcterms:isPartOf"))
707785
}
708786
}
709787
Reference::WebPage(WebPage {
710788
container_title, ..
711789
}) => html! {
712790
@if let Some(title) = container_title {
713791
"On the website "
714-
span property="isPartOf" typeof="WebSite"{
715-
(render_lstr_cite(title, None, Some("name"), Some("alternateName")))
792+
span property="isPartOf dcterms:isPartOf" typeof="WebSite bibo:Website"{
793+
(render_lstr_cite(title, None, Some("name dcterms:title"), Some("alternateName")))
716794
}
717795

718796
@if let Some(archive_url) = r.common().archive_url.as_ref()
@@ -895,9 +973,9 @@ fn render_publisher(key: &str, r: &Reference) -> Markup {
895973
}
896974

897975
html! {
898-
span property="publisher" typeof="Organization" resource={"#"(key)"-publisher"} {
976+
span property="publisher dcterms:publisher" typeof="Organization foaf:Organization" resource={"#"(key)"-publisher"} {
899977
@if let Some(publisher) = publisher {
900-
(render_lstr(publisher, Some("noun"), Some("name"), Some("alternateName")))
978+
(render_lstr(publisher, Some("noun foaf:name"), Some("name"), Some("alternateName")))
901979
@if place.is_none() {
902980
@if !publisher.value.ends_with('.') {
903981
". "
@@ -919,7 +997,7 @@ fn render_publisher(key: &str, r: &Reference) -> Markup {
919997
fn render_genre(r: &Reference) -> Markup {
920998
html! {
921999
@if let Reference::Thesis(Thesis{genre: Some(g), ..}) = r {
922-
(g) ", "
1000+
span property="degree" typeof="bibo:ThesisDegree" { (g) } ", "
9231001
}
9241002
}
9251003
}
@@ -928,10 +1006,24 @@ fn render_isbn(r: &Reference) -> Markup {
9281006
html! {
9291007
@if let Reference::Book(Book{isbn: Some(isbn), ..}) = r {
9301008
" "
931-
@let isbn = isbn.0.hyphenate().unwrap();
1009+
@let nice_isbn = isbn.0.hyphenate().unwrap();
9321010
abbr.initialism { "ISBN" } ": "
933-
a property="" href={"https://www.worldcat.org/isbn/"(isbn)} {
934-
span property="isbn" { (isbn) }
1011+
a.isbn property="sameAs" href={"https://www.worldcat.org/isbn/"(isbn.0)} {
1012+
span property="isbn bibo:isbn" { (nice_isbn) }
1013+
}
1014+
". "
1015+
}
1016+
}
1017+
}
1018+
1019+
fn render_identifiers(r: &Reference) -> Markup {
1020+
html! {
1021+
@for id in &r.common().identifiers {
1022+
" "
1023+
span.id {
1024+
a property="sameAs" href={"https://n2t.net/"(id)} {
1025+
span property="dcterms:identifier" { (id) }
1026+
}
9351027
}
9361028
". "
9371029
}

0 commit comments

Comments
 (0)