@@ -27,6 +27,41 @@ pub type InlineCiteRenderer = Box<dyn Fn(&str, Option<Markup>) -> Markup + Send
2727
2828pub 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+
3065pub 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
130167fn 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
138175fn 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
146183fn 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+
158223fn 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 {
194260fn 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 {
919997fn 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