1use serde_json::Value as JsonValue;
12use url::Url;
13
14use super::classes::class_dispatch;
15use super::contributor::{Contributor, ContributorEntry, ContributorList, ContributorRole};
16use super::date::DateValue;
17use super::types::common::{
18 FieldLanguageMap, HasNumbering, LangID, MultilingualString, NumOrStr, NumberingType, Publisher,
19 RefID, RichText, Title,
20};
21use super::types::legal::{Brief, Hearing, LegalCase, Regulation, Statute, Treaty};
22use super::types::specialized::{
23 AudioVisualType, AudioVisualWork, Classic, Dataset, Event, Patent, Software, Standard,
24};
25use super::types::structural::{
26 Collection, CollectionComponent, CollectionType, Monograph, MonographComponentType,
27 MonographType, Serial, SerialComponent, SerialType,
28};
29use super::{
30 ClassExtension, EMPTY_FIELD_LANGUAGES, IdentifierName, InputReference, ReferenceClass,
31 SupplementaryIdentifiers, UnknownClassData, WorkRelation,
32};
33
34impl InputReference {
35 #[must_use]
37 pub fn identifiers(&self) -> &SupplementaryIdentifiers {
38 &self.identifiers
39 }
40
41 #[must_use]
43 pub fn identifier(&self, name: &str) -> Option<&str> {
44 self.identifiers.get(name)
45 }
46
47 pub fn insert_identifier(
49 &mut self,
50 name: IdentifierName,
51 value: impl Into<String>,
52 ) -> Option<String> {
53 self.identifiers.insert(name, value)
54 }
55
56 #[must_use]
58 pub fn class(&self) -> ReferenceClass {
59 self.extension.reference_class()
60 }
61
62 #[must_use]
64 pub fn extension(&self) -> &ClassExtension {
65 &self.extension
66 }
67
68 #[must_use]
70 pub fn extension_mut(&mut self) -> &mut ClassExtension {
71 &mut self.extension
72 }
73
74 #[must_use]
76 pub fn as_monograph(&self) -> Option<&Monograph> {
77 match &self.extension {
78 ClassExtension::Monograph(reference) => Some(reference.as_ref()),
79 _ => None,
80 }
81 }
82
83 #[must_use]
85 pub fn as_collection_component(&self) -> Option<&CollectionComponent> {
86 match &self.extension {
87 ClassExtension::CollectionComponent(reference) => Some(reference.as_ref()),
88 _ => None,
89 }
90 }
91
92 #[must_use]
94 pub fn as_serial_component(&self) -> Option<&SerialComponent> {
95 match &self.extension {
96 ClassExtension::SerialComponent(reference) => Some(reference.as_ref()),
97 _ => None,
98 }
99 }
100
101 #[must_use]
103 pub fn as_collection(&self) -> Option<&Collection> {
104 match &self.extension {
105 ClassExtension::Collection(reference) => Some(reference.as_ref()),
106 _ => None,
107 }
108 }
109
110 #[must_use]
112 pub fn as_serial(&self) -> Option<&Serial> {
113 match &self.extension {
114 ClassExtension::Serial(reference) => Some(reference.as_ref()),
115 _ => None,
116 }
117 }
118
119 #[must_use]
121 pub fn as_legal_case(&self) -> Option<&LegalCase> {
122 match &self.extension {
123 ClassExtension::LegalCase(reference) => Some(reference.as_ref()),
124 _ => None,
125 }
126 }
127
128 #[must_use]
130 pub fn as_statute(&self) -> Option<&Statute> {
131 match &self.extension {
132 ClassExtension::Statute(reference) => Some(reference.as_ref()),
133 _ => None,
134 }
135 }
136
137 #[must_use]
139 pub fn as_treaty(&self) -> Option<&Treaty> {
140 match &self.extension {
141 ClassExtension::Treaty(reference) => Some(reference.as_ref()),
142 _ => None,
143 }
144 }
145
146 #[must_use]
148 pub fn as_hearing(&self) -> Option<&Hearing> {
149 match &self.extension {
150 ClassExtension::Hearing(reference) => Some(reference.as_ref()),
151 _ => None,
152 }
153 }
154
155 #[must_use]
157 pub fn as_regulation(&self) -> Option<&Regulation> {
158 match &self.extension {
159 ClassExtension::Regulation(reference) => Some(reference.as_ref()),
160 _ => None,
161 }
162 }
163
164 #[must_use]
166 pub fn as_brief(&self) -> Option<&Brief> {
167 match &self.extension {
168 ClassExtension::Brief(reference) => Some(reference.as_ref()),
169 _ => None,
170 }
171 }
172
173 #[must_use]
175 pub fn as_classic(&self) -> Option<&Classic> {
176 match &self.extension {
177 ClassExtension::Classic(reference) => Some(reference.as_ref()),
178 _ => None,
179 }
180 }
181
182 #[must_use]
184 pub fn as_patent(&self) -> Option<&Patent> {
185 match &self.extension {
186 ClassExtension::Patent(reference) => Some(reference.as_ref()),
187 _ => None,
188 }
189 }
190
191 #[must_use]
193 pub fn as_dataset(&self) -> Option<&Dataset> {
194 match &self.extension {
195 ClassExtension::Dataset(reference) => Some(reference.as_ref()),
196 _ => None,
197 }
198 }
199
200 #[must_use]
202 pub fn as_standard(&self) -> Option<&Standard> {
203 match &self.extension {
204 ClassExtension::Standard(reference) => Some(reference.as_ref()),
205 _ => None,
206 }
207 }
208
209 #[must_use]
211 pub fn as_software(&self) -> Option<&Software> {
212 match &self.extension {
213 ClassExtension::Software(reference) => Some(reference.as_ref()),
214 _ => None,
215 }
216 }
217
218 #[must_use]
220 pub fn as_event(&self) -> Option<&Event> {
221 match &self.extension {
222 ClassExtension::Event(reference) => Some(reference.as_ref()),
223 _ => None,
224 }
225 }
226
227 #[must_use]
229 pub fn as_audio_visual(&self) -> Option<&AudioVisualWork> {
230 match &self.extension {
231 ClassExtension::AudioVisual(reference) => Some(reference.as_ref()),
232 _ => None,
233 }
234 }
235
236 #[must_use]
238 pub fn unknown_class(&self) -> Option<&UnknownClassData> {
239 match &self.extension {
240 ClassExtension::Unknown(data) => Some(data),
241 _ => None,
242 }
243 }
244
245 fn numbered(&self) -> Option<&dyn HasNumbering> {
246 match &self.extension {
247 ClassExtension::Monograph(reference) => Some(reference.as_ref()),
248 ClassExtension::Collection(reference) => Some(reference.as_ref()),
249 ClassExtension::CollectionComponent(reference) => Some(reference.as_ref()),
250 ClassExtension::SerialComponent(reference) => Some(reference.as_ref()),
251 ClassExtension::Classic(reference) => Some(reference.as_ref()),
252 ClassExtension::AudioVisual(reference) => Some(reference.as_ref()),
253 _ => None,
254 }
255 }
256
257 fn find_numbering(&self, numbering_type: NumberingType) -> Option<String> {
259 self.numbered()
260 .and_then(|reference| reference.find_numbering(numbering_type))
261 }
262
263 pub fn numbering_value(&self, numbering_type: &NumberingType) -> Option<String> {
265 self.find_numbering(numbering_type.clone())
266 }
267
268 pub fn id(&self) -> Option<RefID> {
270 class_dispatch!(&self.extension, |r| r.id.clone(), unknown(data) => data
271 .fields
272 .get("id")
273 .and_then(JsonValue::as_str)
274 .map(|id| RefID(id.to_string())))
275 }
276
277 pub fn author(&self) -> Option<Contributor> {
279 use crate::reference::contributor::ContributorRole as DataRole;
280
281 match &self.extension {
282 ClassExtension::Monograph(r) => {
283 collect_contributors_by_role(&r.contributors, &DataRole::Author)
284 .or_else(|| r.author.clone())
285 }
286 ClassExtension::CollectionComponent(r) => {
287 collect_contributors_by_role(&r.contributors, &DataRole::Author)
288 .or_else(|| r.author.clone())
289 }
290 ClassExtension::SerialComponent(r) => {
291 let explicit_author =
292 collect_contributors_by_role(&r.contributors, &DataRole::Author)
293 .or_else(|| r.author.clone());
294
295 explicit_author.or_else(|| {
296 let av_like = r
297 .medium
298 .as_deref()
299 .map(|value| {
300 let lowered = value.to_ascii_lowercase();
301 lowered.contains("podcast")
302 || lowered.contains("tv")
303 || lowered.contains("film")
304 || lowered.contains("video")
305 })
306 .unwrap_or(false)
307 || r.genre
308 .as_deref()
309 .map(|value| {
310 let lowered = value.to_ascii_lowercase();
311 lowered.contains("broadcast") || lowered.contains("film")
312 })
313 .unwrap_or(false);
314
315 if av_like {
316 collect_contributors_by_role(&r.contributors, &DataRole::Producer).or_else(
317 || collect_contributors_by_role(&r.contributors, &DataRole::Host),
318 )
319 } else {
320 None
321 }
322 })
323 }
324 ClassExtension::Treaty(r) => r.author.clone(),
325 ClassExtension::Brief(r) => r.author.clone(),
326 ClassExtension::Classic(r) => r.author.clone(),
327 ClassExtension::Patent(r) => r.author.clone(),
328 ClassExtension::Dataset(r) => r.author.clone(),
329 ClassExtension::Software(r) => r.author.clone(),
330 ClassExtension::Event(r) => {
331 collect_contributors_by_role(&r.contributors, &DataRole::Performer)
332 .or_else(|| {
333 collect_contributors_by_role(
334 &r.contributors,
335 &DataRole::Unknown("organizer".to_string()),
336 )
337 })
338 .or_else(|| collect_contributors_by_role(&r.contributors, &DataRole::Author))
339 }
340 ClassExtension::AudioVisual(r) => {
341 let explicit_author =
342 collect_contributors_by_role(&r.core.contributors, &DataRole::Author);
343
344 match r.r#type {
345 AudioVisualType::Film | AudioVisualType::Episode => {
346 explicit_author.or_else(|| {
347 collect_contributors_by_role(&r.core.contributors, &DataRole::Director)
348 })
349 }
350 AudioVisualType::Recording => explicit_author.or_else(|| {
351 collect_contributors_by_role(&r.core.contributors, &DataRole::Composer)
352 .or_else(|| {
353 collect_contributors_by_role(
354 &r.core.contributors,
355 &DataRole::Performer,
356 )
357 })
358 }),
359 AudioVisualType::Broadcast => explicit_author
360 .or_else(|| {
361 collect_contributors_by_role(&r.core.contributors, &DataRole::Director)
362 })
363 .or_else(|| {
364 collect_contributors_by_role(&r.core.contributors, &DataRole::Producer)
365 }),
366 }
367 }
368 _ => None,
369 }
370 }
371
372 pub fn editor(&self) -> Option<Contributor> {
373 match &self.extension {
374 ClassExtension::Monograph(r) => {
375 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
376 .or_else(|| r.editor.clone())
377 }
378 ClassExtension::Collection(r) => {
379 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
380 .or_else(|| r.editor.clone())
381 }
382 ClassExtension::CollectionComponent(r) => r
383 .container
384 .as_ref()
385 .and_then(|c| match c {
386 WorkRelation::Embedded(p) => p.editor(),
387 WorkRelation::Id(_) => None,
388 })
389 .or_else(|| {
390 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
391 }),
392 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
393 WorkRelation::Embedded(p) => p.editor(),
394 WorkRelation::Id(_) => None,
395 }),
396 ClassExtension::Serial(r) => {
397 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
398 .or_else(|| r.editor.clone())
399 }
400 ClassExtension::Classic(r) => r.editor.clone(),
401 ClassExtension::AudioVisual(_) => None,
402 _ => None,
403 }
404 }
405
406 pub fn local_editor(&self) -> Option<Contributor> {
408 match &self.extension {
409 ClassExtension::Monograph(r) => {
410 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
411 .or_else(|| r.editor.clone())
412 }
413 ClassExtension::Collection(r) => {
414 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
415 .or_else(|| r.editor.clone())
416 }
417 ClassExtension::CollectionComponent(r) => {
418 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
419 }
420 ClassExtension::Serial(r) => {
421 collect_contributors_by_role(&r.contributors, &ContributorRole::Editor)
422 .or_else(|| r.editor.clone())
423 }
424 ClassExtension::Classic(r) => r.editor.clone(),
425 _ => None,
426 }
427 }
428
429 pub fn translator(&self) -> Option<Contributor> {
431 match &self.extension {
432 ClassExtension::Monograph(r) => {
433 collect_contributors_by_role(&r.contributors, &ContributorRole::Translator)
434 .or_else(|| r.translator.clone())
435 }
436 ClassExtension::CollectionComponent(r) => {
437 collect_contributors_by_role(&r.contributors, &ContributorRole::Translator)
438 .or_else(|| r.translator.clone())
439 }
440 ClassExtension::SerialComponent(r) => {
441 collect_contributors_by_role(&r.contributors, &ContributorRole::Translator)
442 .or_else(|| r.translator.clone())
443 }
444 ClassExtension::Collection(r) => {
445 collect_contributors_by_role(&r.contributors, &ContributorRole::Translator)
446 .or_else(|| r.translator.clone())
447 }
448 ClassExtension::Classic(r) => r.translator.clone(),
449 ClassExtension::AudioVisual(r) => {
450 collect_contributors_by_role(&r.core.contributors, &ContributorRole::Translator)
451 }
452 _ => None,
453 }
454 }
455
456 pub fn publisher(&self) -> Option<Publisher> {
458 match &self.extension {
459 ClassExtension::Monograph(r) => r.publisher.clone(),
460 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
461 WorkRelation::Embedded(p) => p.publisher(),
462 WorkRelation::Id(_) => None,
463 }),
464 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
465 WorkRelation::Embedded(p) => p.publisher(),
466 WorkRelation::Id(_) => None,
467 }),
468 ClassExtension::Collection(r) => r.publisher.clone(),
469 ClassExtension::Serial(r) => r.publisher.clone(),
470 ClassExtension::Classic(r) => r.publisher.clone(),
471 ClassExtension::Dataset(r) => r.publisher.clone(),
472 ClassExtension::Standard(r) => r.publisher.clone(),
473 ClassExtension::Software(r) => r.publisher.clone(),
474 ClassExtension::AudioVisual(r) => r.publisher.clone(),
475 _ => None,
476 }
477 }
478
479 pub fn contributor(&self, role: ContributorRole) -> Option<Contributor> {
485 let entries = self.all_contributor_entries();
486 collect_contributors_by_role(entries, &role)
487 }
488
489 pub fn contributor_entries(&self, role: &ContributorRole) -> Vec<&ContributorEntry> {
491 self.all_contributor_entries()
492 .iter()
493 .filter(|entry| entry.roles.contains(role))
494 .collect()
495 }
496
497 pub fn all_contributor_entries(&self) -> &[ContributorEntry] {
499 match &self.extension {
500 ClassExtension::Monograph(r) => &r.contributors,
501 ClassExtension::Collection(r) => &r.contributors,
502 ClassExtension::CollectionComponent(r) => &r.contributors,
503 ClassExtension::Serial(r) => &r.contributors,
504 ClassExtension::SerialComponent(r) => &r.contributors,
505 ClassExtension::Event(r) => &r.contributors,
506 ClassExtension::AudioVisual(r) => &r.core.contributors,
507 _ => &[],
508 }
509 }
510
511 pub fn title(&self) -> Option<Title> {
513 match &self.extension {
514 ClassExtension::Monograph(r) => match (&r.title, &r.short_title) {
515 (Some(Title::Single(long)), Some(short)) => {
516 Some(Title::Shorthand(short.clone(), long.clone()))
517 }
518 _ => r.title.clone(),
519 },
520 ClassExtension::CollectionComponent(r) => r.title.clone(),
521 ClassExtension::SerialComponent(r) => r.title.clone(),
522 ClassExtension::Collection(r) => match (&r.title, &r.short_title) {
523 (Some(Title::Single(long)), Some(short)) => {
524 Some(Title::Shorthand(short.clone(), long.clone()))
525 }
526 _ => r.title.clone(),
527 },
528 ClassExtension::Serial(r) => match (&r.title, &r.short_title) {
529 (Some(Title::Single(long)), Some(short)) => {
530 Some(Title::Shorthand(short.clone(), long.clone()))
531 }
532 _ => r.title.clone(),
533 },
534 ClassExtension::LegalCase(r) => r.title.clone(),
535 ClassExtension::Statute(r) => r.title.clone(),
536 ClassExtension::Treaty(r) => r.title.clone(),
537 ClassExtension::Hearing(r) => r.title.clone(),
538 ClassExtension::Regulation(r) => r.title.clone(),
539 ClassExtension::Brief(r) => r.title.clone(),
540 ClassExtension::Classic(r) => r.title.clone(),
541 ClassExtension::Patent(r) => r.title.clone(),
542 ClassExtension::Dataset(r) => r.title.clone(),
543 ClassExtension::Standard(r) => r.title.clone(),
544 ClassExtension::Software(r) => r.title.clone(),
545 ClassExtension::Event(r) => r.title.clone(),
546 ClassExtension::AudioVisual(r) => match (&r.core.title, &r.core.short_title) {
547 (Some(Title::Single(long)), Some(short)) => {
548 Some(Title::Shorthand(short.clone(), long.clone()))
549 }
550 _ => r.core.title.clone(),
551 },
552 ClassExtension::Unknown(data) => data
553 .fields
554 .get("title")
555 .and_then(JsonValue::as_str)
556 .map(|title| Title::Single(title.to_string())),
557 }
558 }
559
560 pub fn volume_title(&self) -> Option<String> {
562 match &self.extension {
563 ClassExtension::Monograph(r) => r.volume_title.clone(),
564 _ => None,
565 }
566 }
567
568 fn non_empty_date(date: DateValue) -> Option<DateValue> {
569 if date.is_empty() { None } else { Some(date) }
570 }
571
572 pub fn created(&self) -> Option<DateValue> {
574 match &self.extension {
575 ClassExtension::Monograph(r) => Self::non_empty_date(r.created.clone()),
576 ClassExtension::CollectionComponent(r) => Self::non_empty_date(r.created.clone()),
577 ClassExtension::SerialComponent(r) => Self::non_empty_date(r.created.clone()),
578 ClassExtension::Collection(r) => Self::non_empty_date(r.created.clone()),
579 ClassExtension::Serial(_) => None,
580 ClassExtension::LegalCase(r) => Self::non_empty_date(r.created.clone()),
581 ClassExtension::Statute(r) => Self::non_empty_date(r.created.clone()),
582 ClassExtension::Treaty(r) => Self::non_empty_date(r.created.clone()),
583 ClassExtension::Hearing(r) => Self::non_empty_date(r.created.clone()),
584 ClassExtension::Regulation(r) => Self::non_empty_date(r.created.clone()),
585 ClassExtension::Brief(r) => Self::non_empty_date(r.created.clone()),
586 ClassExtension::Classic(r) => Self::non_empty_date(r.created.clone()),
587 ClassExtension::Patent(r) => Self::non_empty_date(r.created.clone()),
588 ClassExtension::Dataset(r) => Self::non_empty_date(r.created.clone()),
589 ClassExtension::Standard(r) => Self::non_empty_date(r.created.clone()),
590 ClassExtension::Software(r) => Self::non_empty_date(r.created.clone()),
591 ClassExtension::Event(_) => None,
592 ClassExtension::AudioVisual(r) => Self::non_empty_date(r.core.created.clone()),
593 ClassExtension::Unknown(_) => None,
594 }
595 }
596
597 pub fn issued(&self) -> Option<DateValue> {
599 match &self.extension {
600 ClassExtension::Monograph(r) => Self::non_empty_date(r.issued.clone()),
601 ClassExtension::CollectionComponent(r) => Self::non_empty_date(r.issued.clone()),
602 ClassExtension::SerialComponent(r) => Self::non_empty_date(r.issued.clone()),
603 ClassExtension::Collection(r) => Self::non_empty_date(r.issued.clone()),
604 ClassExtension::Serial(_) => None,
605 ClassExtension::LegalCase(r) => Self::non_empty_date(r.issued.clone()),
606 ClassExtension::Statute(r) => Self::non_empty_date(r.issued.clone()),
607 ClassExtension::Treaty(r) => Self::non_empty_date(r.issued.clone()),
608 ClassExtension::Hearing(r) => Self::non_empty_date(r.issued.clone()),
609 ClassExtension::Regulation(r) => Self::non_empty_date(r.issued.clone()),
610 ClassExtension::Brief(r) => Self::non_empty_date(r.issued.clone()),
611 ClassExtension::Classic(r) => Self::non_empty_date(r.issued.clone()),
612 ClassExtension::Patent(r) => Self::non_empty_date(r.issued.clone()),
613 ClassExtension::Dataset(r) => Self::non_empty_date(r.issued.clone()),
614 ClassExtension::Standard(r) => Self::non_empty_date(r.issued.clone()),
615 ClassExtension::Software(r) => Self::non_empty_date(r.issued.clone()),
616 ClassExtension::Event(r) => r.date.clone(),
617 ClassExtension::AudioVisual(r) => Self::non_empty_date(r.core.issued.clone()),
618 ClassExtension::Unknown(_) => None,
619 }
620 }
621
622 pub fn effective_issued_date(&self) -> Option<DateValue> {
625 self.issued().or_else(|| self.created())
626 }
627
628 pub fn copyright(&self) -> Option<DateValue> {
631 match &self.extension {
632 ClassExtension::Monograph(r) => r.copyright.clone().and_then(Self::non_empty_date),
633 _ => None,
634 }
635 }
636
637 pub fn printing(&self) -> Option<DateValue> {
640 match &self.extension {
641 ClassExtension::Monograph(r) => r.printing.clone().and_then(Self::non_empty_date),
642 _ => None,
643 }
644 }
645
646 pub fn doi(&self) -> Option<String> {
648 match &self.extension {
649 ClassExtension::Monograph(r) => r.doi.clone(),
650 ClassExtension::CollectionComponent(r) => r.doi.clone(),
651 ClassExtension::SerialComponent(r) => r.doi.clone(),
652 ClassExtension::LegalCase(r) => r.doi.clone(),
653 ClassExtension::Dataset(r) => r.doi.clone(),
654 ClassExtension::Standard(r) => r.doi.clone(),
655 ClassExtension::Software(r) => r.doi.clone(),
656 ClassExtension::AudioVisual(_) => None,
657 _ => None,
658 }
659 }
660
661 pub fn ads_bibcode(&self) -> Option<String> {
663 match &self.extension {
664 ClassExtension::Monograph(r) => r.ads_bibcode.clone(),
665 ClassExtension::SerialComponent(r) => r.ads_bibcode.clone(),
666 ClassExtension::AudioVisual(_) => None,
667 _ => None,
668 }
669 }
670
671 pub fn note(&self) -> Option<RichText> {
673 match &self.extension {
674 ClassExtension::Monograph(r) => r.note.clone(),
675 ClassExtension::CollectionComponent(r) => r.note.clone(),
676 ClassExtension::SerialComponent(r) => r.note.clone(),
677 ClassExtension::Collection(r) => r.note.clone(),
678 ClassExtension::Serial(r) => r.note.clone(),
679 ClassExtension::LegalCase(r) => r.note.clone(),
680 ClassExtension::Statute(r) => r.note.clone(),
681 ClassExtension::Treaty(r) => r.note.clone(),
682 ClassExtension::Standard(r) => r.note.clone(),
683 ClassExtension::Event(r) => r.note.clone(),
684 ClassExtension::AudioVisual(r) => r.note.clone(),
685 _ => None,
686 }
687 }
688
689 pub fn url(&self) -> Option<Url> {
691 match &self.extension {
692 ClassExtension::Monograph(r) => r.url.clone(),
693 ClassExtension::CollectionComponent(r) => r.url.clone(),
694 ClassExtension::SerialComponent(r) => r.url.clone(),
695 ClassExtension::Collection(r) => r.url.clone(),
696 ClassExtension::Serial(r) => r.url.clone(),
697 ClassExtension::LegalCase(r) => r.url.clone(),
698 ClassExtension::Statute(r) => r.url.clone(),
699 ClassExtension::Treaty(r) => r.url.clone(),
700 ClassExtension::Hearing(r) => r.url.clone(),
701 ClassExtension::Regulation(r) => r.url.clone(),
702 ClassExtension::Brief(r) => r.url.clone(),
703 ClassExtension::Classic(r) => r.url.clone(),
704 ClassExtension::Patent(r) => r.url.clone(),
705 ClassExtension::Dataset(r) => r.url.clone(),
706 ClassExtension::Standard(r) => r.url.clone(),
707 ClassExtension::Software(r) => r.url.clone(),
708 ClassExtension::Event(r) => r.url.clone(),
709 ClassExtension::AudioVisual(r) => r.url.clone(),
710 ClassExtension::Unknown(_) => None,
711 }
712 }
713
714 pub fn publisher_place(&self) -> Option<String> {
716 match &self.extension {
717 ClassExtension::Monograph(r) => r
718 .publisher
719 .as_ref()
720 .and_then(|p| p.place.clone().map(Into::into))
721 .or_else(|| {
722 r.container.as_ref().and_then(|c| match c {
723 WorkRelation::Embedded(p) => p.publisher_place(),
724 _ => None,
725 })
726 }),
727 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
728 WorkRelation::Embedded(p) => p.publisher_place(),
729 _ => None,
730 }),
731 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
732 WorkRelation::Embedded(p) => p.publisher_place(),
733 _ => None,
734 }),
735 ClassExtension::Collection(r) => r
736 .publisher
737 .as_ref()
738 .and_then(|p| p.place.clone().map(Into::into))
739 .or_else(|| {
740 r.container.as_ref().and_then(|c| match c {
741 WorkRelation::Embedded(p) => p.publisher_place(),
742 _ => None,
743 })
744 }),
745 ClassExtension::Serial(_) => None,
746 ClassExtension::Classic(r) => r
747 .publisher
748 .as_ref()
749 .and_then(|p| p.place.clone().map(Into::into)),
750 ClassExtension::Dataset(r) => r
751 .publisher
752 .as_ref()
753 .and_then(|p| p.place.clone().map(Into::into)),
754 ClassExtension::Standard(r) => r
755 .publisher
756 .as_ref()
757 .and_then(|p| p.place.clone().map(Into::into)),
758 ClassExtension::Software(r) => r
759 .publisher
760 .as_ref()
761 .and_then(|p| p.place.clone().map(Into::into)),
762 ClassExtension::Event(r) => r.location.clone(),
763 ClassExtension::AudioVisual(r) => r
764 .publisher
765 .as_ref()
766 .and_then(|p| p.place.clone().map(Into::into)),
767 _ => None,
768 }
769 }
770
771 pub fn publisher_str(&self) -> Option<String> {
773 match &self.extension {
774 ClassExtension::Monograph(r) => r
775 .publisher
776 .as_ref()
777 .map(|p| p.name.to_string())
778 .or_else(|| {
779 r.container.as_ref().and_then(|c| match c {
780 WorkRelation::Embedded(p) => p.publisher_str(),
781 _ => None,
782 })
783 }),
784 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
785 WorkRelation::Embedded(p) => p.publisher_str(),
786 _ => None,
787 }),
788 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
789 WorkRelation::Embedded(p) => p.publisher_str(),
790 _ => None,
791 }),
792 ClassExtension::Collection(r) => r
793 .publisher
794 .as_ref()
795 .map(|p| p.name.to_string())
796 .or_else(|| {
797 r.container.as_ref().and_then(|c| match c {
798 WorkRelation::Embedded(p) => p.publisher_str(),
799 _ => None,
800 })
801 }),
802 ClassExtension::Serial(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
803 ClassExtension::Classic(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
804 ClassExtension::Dataset(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
805 ClassExtension::Standard(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
806 ClassExtension::Software(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
807 ClassExtension::Event(r) => r.network.clone(),
808 ClassExtension::AudioVisual(r) => r.publisher.as_ref().map(|p| p.name.to_string()),
809 _ => None,
810 }
811 }
812
813 pub(super) fn normalize_genre_medium(s: &str) -> String {
817 let lower = s.to_ascii_lowercase();
818 lower
819 .split(|c: char| c.is_whitespace() || c == '_')
820 .filter(|p| !p.is_empty())
821 .collect::<Vec<_>>()
822 .join("-")
823 }
824
825 pub fn genre(&self) -> Option<String> {
827 match &self.extension {
828 ClassExtension::Monograph(r) => {
829 r.genre.as_ref().map(|g| Self::normalize_genre_medium(g))
830 }
831 ClassExtension::CollectionComponent(r) => {
832 r.genre.as_ref().map(|g| Self::normalize_genre_medium(g))
833 }
834 ClassExtension::SerialComponent(r) => {
835 r.genre.as_ref().map(|g| Self::normalize_genre_medium(g))
836 }
837 ClassExtension::Event(r) => r.genre.as_ref().map(|g| Self::normalize_genre_medium(g)),
838 ClassExtension::AudioVisual(r) => r
839 .core
840 .genre
841 .as_ref()
842 .map(|g| Self::normalize_genre_medium(g)),
843 ClassExtension::Dataset(r) => r.genre.as_ref().map(|g| Self::normalize_genre_medium(g)),
844 _ => None,
845 }
846 }
847
848 pub fn archive(&self) -> Option<String> {
850 match &self.extension {
851 ClassExtension::Monograph(r) => r.archive.clone(),
852 _ => None,
853 }
854 }
855
856 pub fn archive_location(&self) -> Option<String> {
858 match &self.extension {
859 ClassExtension::Monograph(r) => r
860 .archive_info
861 .as_ref()
862 .and_then(|info| info.location.clone())
863 .or_else(|| r.archive_location.clone()),
864 ClassExtension::CollectionComponent(r) => {
865 r.archive_info.as_ref().and_then(|i| i.location.clone())
866 }
867 ClassExtension::SerialComponent(r) => {
868 r.archive_info.as_ref().and_then(|i| i.location.clone())
869 }
870 _ => None,
871 }
872 }
873
874 pub fn archive_name(&self) -> Option<MultilingualString> {
876 match &self.extension {
877 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.name.clone()),
878 ClassExtension::CollectionComponent(r) => {
879 r.archive_info.as_ref().and_then(|i| i.name.clone())
880 }
881 ClassExtension::SerialComponent(r) => {
882 r.archive_info.as_ref().and_then(|i| i.name.clone())
883 }
884 _ => None,
885 }
886 }
887
888 pub fn archive_place(&self) -> Option<String> {
890 match &self.extension {
891 ClassExtension::Monograph(r) => r
892 .archive_info
893 .as_ref()
894 .and_then(|i| i.place.clone().map(Into::into)),
895 ClassExtension::CollectionComponent(r) => r
896 .archive_info
897 .as_ref()
898 .and_then(|i| i.place.clone().map(Into::into)),
899 ClassExtension::SerialComponent(r) => r
900 .archive_info
901 .as_ref()
902 .and_then(|i| i.place.clone().map(Into::into)),
903 _ => None,
904 }
905 }
906
907 pub fn archive_collection(&self) -> Option<String> {
909 match &self.extension {
910 ClassExtension::Monograph(r) => {
911 r.archive_info.as_ref().and_then(|i| i.collection.clone())
912 }
913 ClassExtension::CollectionComponent(r) => {
914 r.archive_info.as_ref().and_then(|i| i.collection.clone())
915 }
916 ClassExtension::SerialComponent(r) => {
917 r.archive_info.as_ref().and_then(|i| i.collection.clone())
918 }
919 _ => None,
920 }
921 }
922
923 pub fn archive_collection_id(&self) -> Option<String> {
925 match &self.extension {
926 ClassExtension::Monograph(r) => r
927 .archive_info
928 .as_ref()
929 .and_then(|i| i.collection_id.clone()),
930 ClassExtension::CollectionComponent(r) => r
931 .archive_info
932 .as_ref()
933 .and_then(|i| i.collection_id.clone()),
934 ClassExtension::SerialComponent(r) => r
935 .archive_info
936 .as_ref()
937 .and_then(|i| i.collection_id.clone()),
938 _ => None,
939 }
940 }
941
942 pub fn archive_series(&self) -> Option<String> {
944 match &self.extension {
945 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.series.clone()),
946 ClassExtension::CollectionComponent(r) => {
947 r.archive_info.as_ref().and_then(|i| i.series.clone())
948 }
949 ClassExtension::SerialComponent(r) => {
950 r.archive_info.as_ref().and_then(|i| i.series.clone())
951 }
952 _ => None,
953 }
954 }
955
956 pub fn archive_box(&self) -> Option<String> {
958 match &self.extension {
959 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.r#box.clone()),
960 ClassExtension::CollectionComponent(r) => {
961 r.archive_info.as_ref().and_then(|i| i.r#box.clone())
962 }
963 ClassExtension::SerialComponent(r) => {
964 r.archive_info.as_ref().and_then(|i| i.r#box.clone())
965 }
966 _ => None,
967 }
968 }
969
970 pub fn archive_folder(&self) -> Option<String> {
972 match &self.extension {
973 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.folder.clone()),
974 ClassExtension::CollectionComponent(r) => {
975 r.archive_info.as_ref().and_then(|i| i.folder.clone())
976 }
977 ClassExtension::SerialComponent(r) => {
978 r.archive_info.as_ref().and_then(|i| i.folder.clone())
979 }
980 _ => None,
981 }
982 }
983
984 pub fn archive_item(&self) -> Option<String> {
986 match &self.extension {
987 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.item.clone()),
988 ClassExtension::CollectionComponent(r) => {
989 r.archive_info.as_ref().and_then(|i| i.item.clone())
990 }
991 ClassExtension::SerialComponent(r) => {
992 r.archive_info.as_ref().and_then(|i| i.item.clone())
993 }
994 _ => None,
995 }
996 }
997
998 pub fn archive_url(&self) -> Option<Url> {
1000 match &self.extension {
1001 ClassExtension::Monograph(r) => r.archive_info.as_ref().and_then(|i| i.url.clone()),
1002 ClassExtension::CollectionComponent(r) => {
1003 r.archive_info.as_ref().and_then(|i| i.url.clone())
1004 }
1005 ClassExtension::SerialComponent(r) => {
1006 r.archive_info.as_ref().and_then(|i| i.url.clone())
1007 }
1008 _ => None,
1009 }
1010 }
1011
1012 pub fn status(&self) -> Option<String> {
1014 match &self.extension {
1015 ClassExtension::Monograph(r) => r.status.clone(),
1016 ClassExtension::CollectionComponent(r) => r.status.clone(),
1017 ClassExtension::SerialComponent(r) => r.status.clone(),
1018 ClassExtension::Standard(r) => r.status.clone(),
1019 _ => None,
1020 }
1021 }
1022
1023 pub fn eprint_id(&self) -> Option<String> {
1025 match &self.extension {
1026 ClassExtension::Monograph(r) => r.eprint.as_ref().map(|e| e.id.clone()),
1027 ClassExtension::CollectionComponent(r) => r.eprint.as_ref().map(|e| e.id.clone()),
1028 ClassExtension::SerialComponent(r) => r.eprint.as_ref().map(|e| e.id.clone()),
1029 _ => None,
1030 }
1031 }
1032
1033 pub fn eprint_server(&self) -> Option<String> {
1035 match &self.extension {
1036 ClassExtension::Monograph(r) => r.eprint.as_ref().map(|e| e.server.clone()),
1037 ClassExtension::CollectionComponent(r) => r.eprint.as_ref().map(|e| e.server.clone()),
1038 ClassExtension::SerialComponent(r) => r.eprint.as_ref().map(|e| e.server.clone()),
1039 _ => None,
1040 }
1041 }
1042
1043 pub fn eprint_class(&self) -> Option<String> {
1045 match &self.extension {
1046 ClassExtension::Monograph(r) => r.eprint.as_ref().and_then(|e| e.class.clone()),
1047 ClassExtension::CollectionComponent(r) => {
1048 r.eprint.as_ref().and_then(|e| e.class.clone())
1049 }
1050 ClassExtension::SerialComponent(r) => r.eprint.as_ref().and_then(|e| e.class.clone()),
1051 _ => None,
1052 }
1053 }
1054
1055 pub fn medium(&self) -> Option<String> {
1057 match &self.extension {
1058 ClassExtension::Monograph(r) => {
1059 r.medium.as_ref().map(|m| Self::normalize_genre_medium(m))
1060 }
1061 ClassExtension::CollectionComponent(r) => {
1062 r.medium.as_ref().map(|m| Self::normalize_genre_medium(m))
1063 }
1064 ClassExtension::SerialComponent(r) => {
1065 r.medium.as_ref().map(|m| Self::normalize_genre_medium(m))
1066 }
1067 ClassExtension::AudioVisual(r) => {
1068 r.medium.as_ref().map(|m| Self::normalize_genre_medium(m))
1069 }
1070 ClassExtension::Software(r) => r.platform.clone(),
1075 _ => None,
1076 }
1077 }
1078
1079 pub fn version(&self) -> Option<String> {
1081 match &self.extension {
1082 ClassExtension::Monograph(r) => r.version.clone(),
1083 ClassExtension::Dataset(r) => r.version.clone(),
1084 ClassExtension::Software(r) => r.version.clone(),
1085 _ => None,
1086 }
1087 }
1088
1089 pub fn scale(&self) -> Option<String> {
1091 match &self.extension {
1092 ClassExtension::Monograph(r) => r.scale.clone(),
1093 _ => None,
1094 }
1095 }
1096
1097 pub fn abstract_text(&self) -> Option<RichText> {
1099 match &self.extension {
1100 ClassExtension::Monograph(r) => r.abstract_text.clone(),
1101 ClassExtension::CollectionComponent(r) => r.abstract_text.clone(),
1102 ClassExtension::SerialComponent(r) => r.abstract_text.clone(),
1103 _ => None,
1104 }
1105 }
1106
1107 pub fn container_title(&self) -> Option<Title> {
1109 match &self.extension {
1110 ClassExtension::Monograph(r) => r.container.as_ref().and_then(|c| match c {
1111 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1112 WorkRelation::Id(_) => None,
1113 }),
1114 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
1115 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1116 WorkRelation::Id(_) => None,
1117 }),
1118 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
1119 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1120 WorkRelation::Id(_) => None,
1121 }),
1122 ClassExtension::Serial(r) => r.container.as_ref().and_then(|c| match c {
1123 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1124 WorkRelation::Id(_) => None,
1125 }),
1126 ClassExtension::LegalCase(r) => r.reporter.clone().map(Title::Single),
1127 ClassExtension::Statute(r) => r.code.clone().map(Title::Single),
1128 ClassExtension::Regulation(r) => r.code.clone().map(Title::Single),
1129 ClassExtension::Treaty(r) => r.reporter.clone().map(Title::Single),
1130 ClassExtension::Event(r) => r.container.as_ref().and_then(|c| match c {
1131 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1132 WorkRelation::Id(_) => None,
1133 }),
1134 ClassExtension::AudioVisual(r) => r.container.as_ref().and_then(|c| match c {
1135 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1136 WorkRelation::Id(_) => None,
1137 }),
1138 _ => None,
1139 }
1140 }
1141
1142 pub fn collection_title(&self) -> Option<Title> {
1144 fn nested_collection_title(relation: &WorkRelation) -> Option<Title> {
1145 let WorkRelation::Embedded(parent) = relation else {
1146 return None;
1147 };
1148 let container = match parent.extension() {
1149 ClassExtension::Monograph(r) => r.container.as_ref(),
1150 ClassExtension::Collection(r) => r.container.as_ref(),
1151 ClassExtension::Serial(r) => r.container.as_ref(),
1152 _ => None,
1153 }?;
1154 match container {
1155 WorkRelation::Embedded(collection) => collection.title(),
1156 WorkRelation::Id(_) => None,
1157 }
1158 }
1159
1160 match &self.extension {
1161 ClassExtension::Monograph(r) => r.container.as_ref().and_then(nested_collection_title),
1162 ClassExtension::Collection(r) => {
1163 r.container.as_ref().and_then(|relation| match relation {
1164 WorkRelation::Embedded(collection) => collection.title(),
1165 WorkRelation::Id(_) => None,
1166 })
1167 }
1168 ClassExtension::CollectionComponent(r) => {
1169 r.container.as_ref().and_then(nested_collection_title)
1170 }
1171 ClassExtension::SerialComponent(r) => {
1172 r.container.as_ref().and_then(nested_collection_title)
1173 }
1174 _ => None,
1175 }
1176 }
1177
1178 pub fn volume(&self) -> Option<NumOrStr> {
1180 match &self.extension {
1181 ClassExtension::Monograph(r) => r
1182 .volume
1183 .clone()
1184 .or_else(|| self.find_numbering(NumberingType::Volume))
1185 .map(NumOrStr::Str),
1186 ClassExtension::Collection(r) => r
1187 .volume
1188 .clone()
1189 .or_else(|| self.find_numbering(NumberingType::Volume))
1190 .map(NumOrStr::Str),
1191 ClassExtension::CollectionComponent(r) => r
1192 .volume
1193 .clone()
1194 .or_else(|| self.find_numbering(NumberingType::Volume))
1195 .or_else(|| {
1196 r.container.as_ref().and_then(|container| match container {
1197 WorkRelation::Embedded(parent) => {
1198 parent.volume().map(|volume| volume.to_string())
1199 }
1200 WorkRelation::Id(_) => None,
1201 })
1202 })
1203 .map(NumOrStr::Str),
1204 ClassExtension::SerialComponent(r) => r
1205 .volume
1206 .clone()
1207 .or_else(|| self.find_numbering(NumberingType::Volume))
1208 .map(NumOrStr::Str),
1209 ClassExtension::Classic(r) => r
1210 .volume
1211 .clone()
1212 .or_else(|| self.find_numbering(NumberingType::Volume))
1213 .map(NumOrStr::Str),
1214 ClassExtension::LegalCase(r) => r.volume.clone().map(NumOrStr::Str),
1215 ClassExtension::Statute(r) => r.volume.clone().map(NumOrStr::Str),
1216 ClassExtension::Treaty(r) => r.volume.clone().map(NumOrStr::Str),
1217 ClassExtension::Regulation(r) => r.volume.clone().map(NumOrStr::Str),
1218 _ => self
1219 .find_numbering(NumberingType::Volume)
1220 .map(NumOrStr::Str),
1221 }
1222 }
1223
1224 pub fn collection_number(&self) -> Option<String> {
1226 match &self.extension {
1227 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
1228 WorkRelation::Embedded(p) => p.collection_number(),
1229 WorkRelation::Id(_) => None,
1230 }),
1231 _ => self.find_numbering(NumberingType::Volume),
1232 }
1233 }
1234
1235 pub fn issue(&self) -> Option<NumOrStr> {
1237 match &self.extension {
1238 ClassExtension::Monograph(r) => r
1239 .issue
1240 .clone()
1241 .or_else(|| self.find_numbering(NumberingType::Issue))
1242 .map(NumOrStr::Str),
1243 ClassExtension::Collection(r) => r
1244 .issue
1245 .clone()
1246 .or_else(|| self.find_numbering(NumberingType::Issue))
1247 .map(NumOrStr::Str),
1248 ClassExtension::CollectionComponent(r) => r
1249 .issue
1250 .clone()
1251 .or_else(|| self.find_numbering(NumberingType::Issue))
1252 .map(NumOrStr::Str),
1253 ClassExtension::SerialComponent(r) => r
1254 .issue
1255 .clone()
1256 .or_else(|| self.find_numbering(NumberingType::Issue))
1257 .map(NumOrStr::Str),
1258 ClassExtension::Classic(r) => r
1259 .issue
1260 .clone()
1261 .or_else(|| self.find_numbering(NumberingType::Issue))
1262 .map(NumOrStr::Str),
1263 _ => self.find_numbering(NumberingType::Issue).map(NumOrStr::Str),
1264 }
1265 }
1266
1267 pub fn pages(&self) -> Option<NumOrStr> {
1269 match &self.extension {
1270 ClassExtension::Monograph(r) => r.pages.clone(),
1271 ClassExtension::CollectionComponent(r) => r.pages.clone(),
1272 ClassExtension::SerialComponent(r) => r.pages.clone().map(NumOrStr::Str),
1273 ClassExtension::LegalCase(r) => r.page.clone().map(NumOrStr::Str),
1274 ClassExtension::Statute(r) => r.page.clone().map(NumOrStr::Str),
1275 ClassExtension::Treaty(r) => r.page.clone().map(NumOrStr::Str),
1276 ClassExtension::Patent(r) => r.pages.clone().map(NumOrStr::Str),
1277 _ => None,
1278 }
1279 }
1280
1281 pub fn authority(&self) -> Option<String> {
1283 match &self.extension {
1284 ClassExtension::LegalCase(r) => r.authority.clone(),
1285 ClassExtension::Statute(r) => r.authority.clone(),
1286 ClassExtension::Hearing(r) => r.authority.clone(),
1287 ClassExtension::Regulation(r) => r.authority.clone(),
1288 ClassExtension::Brief(r) => r.authority.clone(),
1289 ClassExtension::Patent(r) => r.authority.clone(),
1290 ClassExtension::Standard(r) => r.authority.clone(),
1291 _ => None,
1292 }
1293 }
1294
1295 pub fn reporter(&self) -> Option<String> {
1297 match &self.extension {
1298 ClassExtension::LegalCase(r) => r.reporter.clone(),
1299 ClassExtension::Treaty(r) => r.reporter.clone(),
1300 _ => None,
1301 }
1302 }
1303
1304 pub fn code(&self) -> Option<String> {
1306 match &self.extension {
1307 ClassExtension::Statute(r) => r.code.clone(),
1308 ClassExtension::Regulation(r) => r.code.clone(),
1309 _ => None,
1310 }
1311 }
1312
1313 pub fn section(&self) -> Option<String> {
1315 match &self.extension {
1316 ClassExtension::Statute(r) => r.section.clone(),
1317 ClassExtension::Regulation(r) => r.section.clone(),
1318 ClassExtension::Classic(_) => self.find_numbering(NumberingType::Section),
1319 ClassExtension::SerialComponent(r) => r.section.clone(),
1320 _ => None,
1321 }
1322 }
1323
1324 pub fn number(&self) -> Option<String> {
1326 match &self.extension {
1327 ClassExtension::Monograph(r) => r
1328 .number
1329 .clone()
1330 .or_else(|| self.find_numbering(NumberingType::Number)),
1331 ClassExtension::Statute(r) => r.number.clone(),
1332 ClassExtension::Hearing(r) => r.session_number.clone(),
1333 ClassExtension::Collection(r) => r
1334 .number
1335 .clone()
1336 .or_else(|| self.find_numbering(NumberingType::Number)),
1337 ClassExtension::CollectionComponent(r) => r
1338 .number
1339 .clone()
1340 .or_else(|| self.find_numbering(NumberingType::Number)),
1341 ClassExtension::SerialComponent(r) => r
1342 .number
1343 .clone()
1344 .or_else(|| self.find_numbering(NumberingType::Number)),
1345 ClassExtension::Classic(r) => r
1346 .number
1347 .clone()
1348 .or_else(|| self.find_numbering(NumberingType::Number)),
1349 _ => self.find_numbering(NumberingType::Number),
1350 }
1351 }
1352
1353 pub fn report_number(&self) -> Option<String> {
1355 match &self.extension {
1356 ClassExtension::Monograph(_) => self.find_numbering(NumberingType::Report),
1357 _ => None,
1358 }
1359 }
1360
1361 pub fn edition(&self) -> Option<String> {
1363 match &self.extension {
1364 ClassExtension::Monograph(r) => r
1365 .edition
1366 .clone()
1367 .or_else(|| self.find_numbering(NumberingType::Edition)),
1368 ClassExtension::Collection(r) => r
1369 .edition
1370 .clone()
1371 .or_else(|| self.find_numbering(NumberingType::Edition)),
1372 ClassExtension::CollectionComponent(r) => r
1373 .edition
1374 .clone()
1375 .or_else(|| self.find_numbering(NumberingType::Edition)),
1376 ClassExtension::SerialComponent(r) => r
1377 .edition
1378 .clone()
1379 .or_else(|| self.find_numbering(NumberingType::Edition)),
1380 ClassExtension::Classic(r) => r
1381 .edition
1382 .clone()
1383 .or_else(|| self.find_numbering(NumberingType::Edition)),
1384 _ => self.find_numbering(NumberingType::Edition),
1385 }
1386 }
1387
1388 pub fn accessed(&self) -> Option<DateValue> {
1390 class_dispatch!(&self.extension, |r| r.accessed.clone(), unknown(_) => None)
1391 }
1392
1393 pub fn unknown_fields(&self) -> Option<&std::collections::BTreeMap<String, JsonValue>> {
1399 class_dispatch!(&self.extension, |r| Some(&r.unknown_fields), unknown(_) => None)
1400 }
1401
1402 fn original_embedded(&self) -> Option<&InputReference> {
1409 let relation = match &self.extension {
1410 ClassExtension::Monograph(r) => r.original.as_ref(),
1411 ClassExtension::CollectionComponent(r) => r.original.as_ref(),
1412 ClassExtension::SerialComponent(r) => r.original.as_ref(),
1413 ClassExtension::LegalCase(r) => r.original.as_ref(),
1414 ClassExtension::Statute(r) => r.original.as_ref(),
1415 ClassExtension::Treaty(r) => r.original.as_ref(),
1416 ClassExtension::Hearing(r) => r.original.as_ref(),
1417 ClassExtension::Regulation(r) => r.original.as_ref(),
1418 ClassExtension::Brief(r) => r.original.as_ref(),
1419 ClassExtension::Classic(r) => r.original.as_ref(),
1420 ClassExtension::Patent(r) => r.original.as_ref(),
1421 ClassExtension::Dataset(r) => r.original.as_ref(),
1422 ClassExtension::Standard(r) => r.original.as_ref(),
1423 ClassExtension::Software(r) => r.original.as_ref(),
1424 ClassExtension::Event(r) => r.original.as_ref(),
1425 ClassExtension::AudioVisual(r) => r.core.original.as_ref(),
1426 _ => None,
1427 }?;
1428 match relation {
1429 WorkRelation::Embedded(p) => Some(p),
1430 WorkRelation::Id(_) => None,
1431 }
1432 }
1433
1434 pub fn original_date(&self) -> Option<DateValue> {
1436 self.original_embedded()?.effective_issued_date()
1437 }
1438
1439 pub fn original_title(&self) -> Option<Title> {
1441 self.original_embedded()?.title()
1442 }
1443
1444 pub fn original_publisher_str(&self) -> Option<String> {
1446 self.original_embedded()?
1447 .publisher_str()
1448 .filter(|value| !value.is_empty())
1449 }
1450
1451 pub fn original_publisher_place(&self) -> Option<String> {
1453 self.original_embedded()?.publisher_place()
1454 }
1455
1456 pub fn isbn(&self) -> Option<String> {
1458 match &self.extension {
1459 ClassExtension::Monograph(r) => r.isbn.clone(),
1460 _ => None,
1461 }
1462 }
1463
1464 pub fn issn(&self) -> Option<String> {
1466 match &self.extension {
1467 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
1468 WorkRelation::Embedded(s) => s.issn(),
1469 WorkRelation::Id(_) => None,
1470 }),
1471 ClassExtension::Serial(r) => r.issn.clone(),
1472 _ => None,
1473 }
1474 }
1475
1476 pub fn keywords(&self) -> Option<Vec<String>> {
1478 match &self.extension {
1479 ClassExtension::Monograph(r) => r.keywords.clone(),
1480 ClassExtension::CollectionComponent(r) => r.keywords.clone(),
1481 ClassExtension::SerialComponent(r) => r.keywords.clone(),
1482 ClassExtension::Collection(r) => r.keywords.clone(),
1483 ClassExtension::Serial(_) => None,
1484 ClassExtension::LegalCase(r) => r.keywords.clone(),
1485 ClassExtension::Statute(r) => r.keywords.clone(),
1486 ClassExtension::Treaty(r) => r.keywords.clone(),
1487 ClassExtension::Hearing(r) => r.keywords.clone(),
1488 ClassExtension::Regulation(r) => r.keywords.clone(),
1489 ClassExtension::Brief(r) => r.keywords.clone(),
1490 ClassExtension::Classic(r) => r.keywords.clone(),
1491 ClassExtension::Patent(r) => r.keywords.clone(),
1492 ClassExtension::Dataset(r) => r.keywords.clone(),
1493 ClassExtension::Standard(r) => r.keywords.clone(),
1494 ClassExtension::Software(r) => r.keywords.clone(),
1495 ClassExtension::Event(_) => None,
1496 ClassExtension::AudioVisual(_) => None,
1497 ClassExtension::Unknown(_) => None,
1498 }
1499 }
1500
1501 pub fn language(&self) -> Option<LangID> {
1503 class_dispatch!(
1504 &self.extension,
1505 |r| r.language.clone(),
1506 audio_visual(r) => r.core.language.clone(),
1507 unknown(_) => None
1508 )
1509 }
1510
1511 pub fn field_languages(&self) -> &FieldLanguageMap {
1513 class_dispatch!(
1514 &self.extension,
1515 |r| &r.field_languages,
1516 unknown(_) => &EMPTY_FIELD_LANGUAGES
1517 )
1518 }
1519
1520 pub fn set_id(&mut self, id: impl Into<RefID>) {
1526 let id = id.into();
1527 class_dispatch!(&mut self.extension, |r| r.id = Some(id.clone()), unknown(data) => {
1528 data.fields
1529 .insert("id".to_string(), JsonValue::String(id.to_string()));
1530 });
1531 }
1532
1533 pub fn ref_type(&self) -> String {
1535 match &self.extension {
1536 ClassExtension::Monograph(r) => self.monograph_ref_type(r),
1537 ClassExtension::CollectionComponent(r) => collection_component_ref_type(r),
1538 ClassExtension::SerialComponent(r) => serial_component_ref_type(r),
1539 ClassExtension::Collection(r) => match r.r#type {
1540 CollectionType::EditedBook => "book",
1541 _ => "collection",
1542 }
1543 .to_string(),
1544 ClassExtension::Serial(r) => match r.r#type {
1545 SerialType::AcademicJournal => "article-journal",
1546 SerialType::Magazine => "article-magazine",
1547 SerialType::Newspaper => "article-newspaper",
1548 SerialType::BroadcastProgram => "broadcast",
1549 _ => "serial",
1550 }
1551 .to_string(),
1552 ClassExtension::LegalCase(_) => "legal-case".to_string(),
1553 ClassExtension::Statute(_) => "statute".to_string(),
1554 ClassExtension::Treaty(_) => "treaty".to_string(),
1555 ClassExtension::Hearing(_) => "hearing".to_string(),
1556 ClassExtension::Regulation(_) => "regulation".to_string(),
1557 ClassExtension::Brief(_) => "brief".to_string(),
1558 ClassExtension::Classic(_) => "classic".to_string(),
1559 ClassExtension::Patent(_) => "patent".to_string(),
1560 ClassExtension::Dataset(_) => "dataset".to_string(),
1561 ClassExtension::Standard(_) => "standard".to_string(),
1562 ClassExtension::Software(_) => "software".to_string(),
1563 ClassExtension::Event(r) => event_ref_type(r).to_string(),
1564 ClassExtension::AudioVisual(r) => audio_visual_ref_type(&r.r#type).to_string(),
1565 ClassExtension::Unknown(data) => {
1566 debug_assert!(
1576 !ReferenceClass::KNOWN.contains(&data.class.as_str()),
1577 "ClassExtension::Unknown should never wrap a known class string (got `{}`)",
1578 data.class
1579 );
1580 data.class.clone()
1581 }
1582 }
1583 }
1584
1585 fn monograph_ref_type(&self, r: &Monograph) -> String {
1586 match r.r#type {
1587 MonographType::Book => {
1588 if r.genre
1589 .as_deref()
1590 .is_some_and(|genre| genre.eq_ignore_ascii_case("musical_score"))
1591 {
1592 return "musical-score".to_string();
1593 }
1594 if r.genre.as_deref().is_some_and(|genre| genre == "pamphlet") {
1595 return "pamphlet".to_string();
1596 }
1597 if r.medium
1598 .as_deref()
1599 .is_some_and(|m| m.to_ascii_lowercase().contains("interview"))
1600 {
1601 "interview"
1602 } else {
1603 "book"
1604 }
1605 .to_string()
1606 }
1607 MonographType::Manual => "manual".to_string(),
1608 MonographType::Report => "report".to_string(),
1609 MonographType::Thesis => "thesis".to_string(),
1610 MonographType::Webpage => "webpage".to_string(),
1611 MonographType::Post => if r.genre.as_deref() == Some("post-weblog") {
1612 "post-weblog"
1613 } else {
1614 "post"
1615 }
1616 .to_string(),
1617 MonographType::Interview => "interview".to_string(),
1618 MonographType::Manuscript => "manuscript".to_string(),
1619 MonographType::Preprint => "preprint".to_string(),
1620 MonographType::PersonalCommunication => "personal-communication".to_string(),
1621 MonographType::Document => {
1622 if let Some(genre) = r.genre.as_deref() {
1627 let lowered = genre.to_ascii_lowercase();
1628 if matches!(
1629 lowered.as_str(),
1630 "map" | "figure" | "graphic" | "periodical" | "collection"
1631 ) {
1632 return lowered;
1633 }
1634 }
1635 if let Some(genre) = r.genre.as_deref()
1636 && matches!(genre, "bill-proceeding" | "bill-record")
1637 {
1638 return genre.to_string();
1639 }
1640 if self.genre().as_deref() == Some("conference-paper") {
1641 return "paper-conference".to_string();
1642 }
1643 if r.medium
1644 .as_deref()
1645 .is_some_and(|m| m.to_ascii_lowercase().contains("interview"))
1646 {
1647 "interview"
1648 } else {
1649 "document"
1650 }
1651 .to_string()
1652 }
1653 _ => r.r#type.as_str().to_string(),
1654 }
1655 }
1656
1657 pub fn normalize_contributors(&mut self) {
1671 let container = match &mut self.extension {
1672 ClassExtension::Monograph(r) => {
1673 r.normalize_contributors();
1674 &mut r.container
1675 }
1676 ClassExtension::Collection(r) => {
1677 r.normalize_contributors();
1678 &mut r.container
1679 }
1680 ClassExtension::Serial(r) => {
1681 r.normalize_contributors();
1682 &mut r.container
1683 }
1684 ClassExtension::CollectionComponent(r) => {
1685 r.normalize_contributors();
1686 &mut r.container
1687 }
1688 ClassExtension::SerialComponent(r) => {
1689 r.normalize_contributors();
1690 &mut r.container
1691 }
1692 _ => return,
1693 };
1694 if let Some(WorkRelation::Embedded(container)) = container {
1695 container.normalize_contributors();
1696 }
1697 }
1698}
1699
1700fn collection_component_ref_type(r: &CollectionComponent) -> String {
1701 match r.r#type {
1702 MonographComponentType::Chapter => match r.genre.as_deref() {
1703 Some("entry-dictionary") => "entry-dictionary",
1704 Some("entry-encyclopedia") => "entry-encyclopedia",
1705 Some("entry") => "entry",
1706 _ => "chapter",
1707 }
1708 .to_string(),
1709 MonographComponentType::Document => "paper-conference".to_string(),
1710 _ => r.r#type.as_str().to_string(),
1711 }
1712}
1713
1714fn serial_component_ref_type(r: &SerialComponent) -> String {
1715 if r.genre.as_deref() == Some("entry-encyclopedia") {
1716 return "entry-encyclopedia".to_string();
1717 }
1718 if let Some(genre) = r.genre.as_deref()
1719 && matches!(genre, "review" | "review-book")
1720 {
1721 return genre.to_string();
1722 }
1723 let container_type = r.container.as_ref().and_then(|c| match c {
1724 WorkRelation::Embedded(p) => Some(p.ref_type()),
1725 WorkRelation::Id(_) => None,
1726 });
1727 match container_type.as_deref() {
1728 Some("article-magazine") => "article-magazine".to_string(),
1729 Some("article-newspaper") => "article-newspaper".to_string(),
1730 Some("broadcast") => if r
1731 .genre
1732 .as_deref()
1733 .is_some_and(|g| g.to_ascii_lowercase().contains("film"))
1734 {
1735 "motion-picture"
1736 } else {
1737 "broadcast"
1738 }
1739 .to_string(),
1740 _ => "article-journal".to_string(),
1741 }
1742}
1743
1744fn event_ref_type(r: &Event) -> &'static str {
1745 let lowered = r.genre.as_deref().map(str::to_ascii_lowercase);
1746 match lowered.as_deref() {
1747 Some(g) if g.contains("conference") || g.contains("paper") => "paper-conference",
1748 Some(g) if g.contains("broadcast") => "broadcast",
1749 Some(g) if g.contains("performance") => "performance",
1750 Some(g) if g.contains("talk") || g.contains("speech") => "speech",
1751 _ => "event",
1752 }
1753}
1754
1755fn audio_visual_ref_type(kind: &AudioVisualType) -> &'static str {
1756 match kind {
1757 AudioVisualType::Film => "motion-picture",
1758 AudioVisualType::Episode | AudioVisualType::Broadcast => "broadcast",
1759 AudioVisualType::Recording => "song",
1760 }
1761}
1762
1763fn collect_contributors_by_role(
1768 entries: &[ContributorEntry],
1769 role: &ContributorRole,
1770) -> Option<Contributor> {
1771 let mut matches = entries
1772 .iter()
1773 .filter(|entry| entry.roles.contains(role))
1774 .map(|e| &e.contributor);
1775 let first = matches.next()?;
1776 let Some(second) = matches.next() else {
1777 return Some(first.clone());
1778 };
1779 let list = std::iter::once(first)
1780 .chain(std::iter::once(second))
1781 .chain(matches)
1782 .cloned()
1783 .collect();
1784 Some(Contributor::ContributorList(ContributorList(list)))
1785}
1786
1787#[cfg(test)]
1788#[allow(
1789 clippy::unwrap_used,
1790 clippy::expect_used,
1791 clippy::panic,
1792 clippy::indexing_slicing,
1793 reason = "Panicking is acceptable and often desired in tests."
1794)]
1795mod tests {
1796 use super::*;
1797 use crate::reference::contributor::SimpleName;
1798
1799 #[test]
1800 fn given_monograph_with_embedded_container_when_normalized_then_container_contributors_fold_too()
1801 {
1802 let container_monograph = Monograph {
1808 r#type: MonographType::Book,
1809 editor: Some(Contributor::SimpleName(SimpleName {
1810 name: "Series Editor".into(),
1811 location: None,
1812 short_name: None,
1813 })),
1814 ..Default::default()
1815 };
1816 let outer_monograph = Monograph {
1817 r#type: MonographType::Book,
1818 container: Some(WorkRelation::Embedded(Box::new(InputReference::Monograph(
1819 Box::new(container_monograph),
1820 )))),
1821 ..Default::default()
1822 };
1823 let mut reference = InputReference::Monograph(Box::new(outer_monograph));
1824
1825 reference.normalize_contributors();
1826
1827 let outer = reference.as_monograph().expect("expected a Monograph");
1828 let container = match outer.container.as_ref().expect("expected a container") {
1829 WorkRelation::Embedded(inner) => inner.as_monograph().expect("expected a Monograph"),
1830 WorkRelation::Id(_) => panic!("expected an embedded container, not an id reference"),
1831 };
1832 assert_eq!(container.contributors.len(), 1);
1833 assert!(
1834 container.contributors[0]
1835 .roles
1836 .contains(&ContributorRole::Editor)
1837 );
1838 }
1839}