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 _ => None,
1071 }
1072 }
1073
1074 pub fn version(&self) -> Option<String> {
1076 match &self.extension {
1077 ClassExtension::Monograph(r) => r.version.clone(),
1078 ClassExtension::Dataset(r) => r.version.clone(),
1079 ClassExtension::Software(r) => r.version.clone(),
1080 _ => None,
1081 }
1082 }
1083
1084 pub fn scale(&self) -> Option<String> {
1086 match &self.extension {
1087 ClassExtension::Monograph(r) => r.scale.clone(),
1088 _ => None,
1089 }
1090 }
1091
1092 pub fn abstract_text(&self) -> Option<RichText> {
1094 match &self.extension {
1095 ClassExtension::Monograph(r) => r.abstract_text.clone(),
1096 ClassExtension::CollectionComponent(r) => r.abstract_text.clone(),
1097 ClassExtension::SerialComponent(r) => r.abstract_text.clone(),
1098 _ => None,
1099 }
1100 }
1101
1102 pub fn container_title(&self) -> Option<Title> {
1104 match &self.extension {
1105 ClassExtension::Monograph(r) => r.container.as_ref().and_then(|c| match c {
1106 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1107 WorkRelation::Id(_) => None,
1108 }),
1109 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
1110 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1111 WorkRelation::Id(_) => None,
1112 }),
1113 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
1114 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1115 WorkRelation::Id(_) => None,
1116 }),
1117 ClassExtension::Serial(r) => r.container.as_ref().and_then(|c| match c {
1118 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1119 WorkRelation::Id(_) => None,
1120 }),
1121 ClassExtension::LegalCase(r) => r.reporter.clone().map(Title::Single),
1122 ClassExtension::Statute(r) => r.code.clone().map(Title::Single),
1123 ClassExtension::Treaty(r) => r.reporter.clone().map(Title::Single),
1124 ClassExtension::Event(r) => r.container.as_ref().and_then(|c| match c {
1125 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1126 WorkRelation::Id(_) => None,
1127 }),
1128 ClassExtension::AudioVisual(r) => r.container.as_ref().and_then(|c| match c {
1129 WorkRelation::Embedded(p) => p.title().or_else(|| p.container_title()),
1130 WorkRelation::Id(_) => None,
1131 }),
1132 _ => None,
1133 }
1134 }
1135
1136 pub fn collection_title(&self) -> Option<Title> {
1138 fn nested_collection_title(relation: &WorkRelation) -> Option<Title> {
1139 let WorkRelation::Embedded(parent) = relation else {
1140 return None;
1141 };
1142 let container = match parent.extension() {
1143 ClassExtension::Monograph(r) => r.container.as_ref(),
1144 ClassExtension::Collection(r) => r.container.as_ref(),
1145 ClassExtension::Serial(r) => r.container.as_ref(),
1146 _ => None,
1147 }?;
1148 match container {
1149 WorkRelation::Embedded(collection) => collection.title(),
1150 WorkRelation::Id(_) => None,
1151 }
1152 }
1153
1154 match &self.extension {
1155 ClassExtension::Monograph(r) => r.container.as_ref().and_then(nested_collection_title),
1156 ClassExtension::CollectionComponent(r) => {
1157 r.container.as_ref().and_then(nested_collection_title)
1158 }
1159 ClassExtension::SerialComponent(r) => {
1160 r.container.as_ref().and_then(nested_collection_title)
1161 }
1162 _ => None,
1163 }
1164 }
1165
1166 pub fn volume(&self) -> Option<NumOrStr> {
1168 match &self.extension {
1169 ClassExtension::Monograph(r) => r
1170 .volume
1171 .clone()
1172 .or_else(|| self.find_numbering(NumberingType::Volume))
1173 .map(NumOrStr::Str),
1174 ClassExtension::Collection(r) => r
1175 .volume
1176 .clone()
1177 .or_else(|| self.find_numbering(NumberingType::Volume))
1178 .map(NumOrStr::Str),
1179 ClassExtension::CollectionComponent(r) => r
1180 .volume
1181 .clone()
1182 .or_else(|| self.find_numbering(NumberingType::Volume))
1183 .or_else(|| {
1184 r.container.as_ref().and_then(|container| match container {
1185 WorkRelation::Embedded(parent) => {
1186 parent.volume().map(|volume| volume.to_string())
1187 }
1188 WorkRelation::Id(_) => None,
1189 })
1190 })
1191 .map(NumOrStr::Str),
1192 ClassExtension::SerialComponent(r) => r
1193 .volume
1194 .clone()
1195 .or_else(|| self.find_numbering(NumberingType::Volume))
1196 .map(NumOrStr::Str),
1197 ClassExtension::Classic(r) => r
1198 .volume
1199 .clone()
1200 .or_else(|| self.find_numbering(NumberingType::Volume))
1201 .map(NumOrStr::Str),
1202 ClassExtension::LegalCase(r) => r.volume.clone().map(NumOrStr::Str),
1203 ClassExtension::Statute(r) => r.volume.clone().map(NumOrStr::Str),
1204 ClassExtension::Treaty(r) => r.volume.clone().map(NumOrStr::Str),
1205 ClassExtension::Regulation(r) => r.volume.clone().map(NumOrStr::Str),
1206 _ => self
1207 .find_numbering(NumberingType::Volume)
1208 .map(NumOrStr::Str),
1209 }
1210 }
1211
1212 pub fn collection_number(&self) -> Option<String> {
1214 match &self.extension {
1215 ClassExtension::CollectionComponent(r) => r.container.as_ref().and_then(|c| match c {
1216 WorkRelation::Embedded(p) => p.collection_number(),
1217 WorkRelation::Id(_) => None,
1218 }),
1219 _ => self.find_numbering(NumberingType::Volume),
1220 }
1221 }
1222
1223 pub fn issue(&self) -> Option<NumOrStr> {
1225 match &self.extension {
1226 ClassExtension::Monograph(r) => r
1227 .issue
1228 .clone()
1229 .or_else(|| self.find_numbering(NumberingType::Issue))
1230 .map(NumOrStr::Str),
1231 ClassExtension::Collection(r) => r
1232 .issue
1233 .clone()
1234 .or_else(|| self.find_numbering(NumberingType::Issue))
1235 .map(NumOrStr::Str),
1236 ClassExtension::CollectionComponent(r) => r
1237 .issue
1238 .clone()
1239 .or_else(|| self.find_numbering(NumberingType::Issue))
1240 .map(NumOrStr::Str),
1241 ClassExtension::SerialComponent(r) => r
1242 .issue
1243 .clone()
1244 .or_else(|| self.find_numbering(NumberingType::Issue))
1245 .map(NumOrStr::Str),
1246 ClassExtension::Classic(r) => r
1247 .issue
1248 .clone()
1249 .or_else(|| self.find_numbering(NumberingType::Issue))
1250 .map(NumOrStr::Str),
1251 _ => self.find_numbering(NumberingType::Issue).map(NumOrStr::Str),
1252 }
1253 }
1254
1255 pub fn pages(&self) -> Option<NumOrStr> {
1257 match &self.extension {
1258 ClassExtension::Monograph(r) => r.pages.clone(),
1259 ClassExtension::CollectionComponent(r) => r.pages.clone(),
1260 ClassExtension::SerialComponent(r) => r.pages.clone().map(NumOrStr::Str),
1261 ClassExtension::LegalCase(r) => r.page.clone().map(NumOrStr::Str),
1262 ClassExtension::Statute(r) => r.page.clone().map(NumOrStr::Str),
1263 ClassExtension::Treaty(r) => r.page.clone().map(NumOrStr::Str),
1264 ClassExtension::Patent(r) => r.pages.clone().map(NumOrStr::Str),
1265 _ => None,
1266 }
1267 }
1268
1269 pub fn authority(&self) -> Option<String> {
1271 match &self.extension {
1272 ClassExtension::LegalCase(r) => r.authority.clone(),
1273 ClassExtension::Statute(r) => r.authority.clone(),
1274 ClassExtension::Hearing(r) => r.authority.clone(),
1275 ClassExtension::Regulation(r) => r.authority.clone(),
1276 ClassExtension::Brief(r) => r.authority.clone(),
1277 ClassExtension::Patent(r) => r.authority.clone(),
1278 ClassExtension::Standard(r) => r.authority.clone(),
1279 _ => None,
1280 }
1281 }
1282
1283 pub fn reporter(&self) -> Option<String> {
1285 match &self.extension {
1286 ClassExtension::LegalCase(r) => r.reporter.clone(),
1287 ClassExtension::Treaty(r) => r.reporter.clone(),
1288 _ => None,
1289 }
1290 }
1291
1292 pub fn code(&self) -> Option<String> {
1294 match &self.extension {
1295 ClassExtension::Statute(r) => r.code.clone(),
1296 ClassExtension::Regulation(r) => r.code.clone(),
1297 _ => None,
1298 }
1299 }
1300
1301 pub fn section(&self) -> Option<String> {
1303 match &self.extension {
1304 ClassExtension::Statute(r) => r.section.clone(),
1305 ClassExtension::Regulation(r) => r.section.clone(),
1306 ClassExtension::Classic(_) => self.find_numbering(NumberingType::Section),
1307 ClassExtension::SerialComponent(r) => r.section.clone(),
1308 _ => None,
1309 }
1310 }
1311
1312 pub fn number(&self) -> Option<String> {
1314 match &self.extension {
1315 ClassExtension::Monograph(r) => r
1316 .number
1317 .clone()
1318 .or_else(|| self.find_numbering(NumberingType::Number)),
1319 ClassExtension::Statute(r) => r.number.clone(),
1320 ClassExtension::Hearing(r) => r.session_number.clone(),
1321 ClassExtension::Collection(r) => r
1322 .number
1323 .clone()
1324 .or_else(|| self.find_numbering(NumberingType::Number)),
1325 ClassExtension::CollectionComponent(r) => r
1326 .number
1327 .clone()
1328 .or_else(|| self.find_numbering(NumberingType::Number)),
1329 ClassExtension::SerialComponent(r) => r
1330 .number
1331 .clone()
1332 .or_else(|| self.find_numbering(NumberingType::Number)),
1333 ClassExtension::Classic(r) => r
1334 .number
1335 .clone()
1336 .or_else(|| self.find_numbering(NumberingType::Number)),
1337 _ => self.find_numbering(NumberingType::Number),
1338 }
1339 }
1340
1341 pub fn report_number(&self) -> Option<String> {
1343 match &self.extension {
1344 ClassExtension::Monograph(_) => self.find_numbering(NumberingType::Report),
1345 _ => None,
1346 }
1347 }
1348
1349 pub fn edition(&self) -> Option<String> {
1351 match &self.extension {
1352 ClassExtension::Monograph(r) => r
1353 .edition
1354 .clone()
1355 .or_else(|| self.find_numbering(NumberingType::Edition)),
1356 ClassExtension::Collection(r) => r
1357 .edition
1358 .clone()
1359 .or_else(|| self.find_numbering(NumberingType::Edition)),
1360 ClassExtension::CollectionComponent(r) => r
1361 .edition
1362 .clone()
1363 .or_else(|| self.find_numbering(NumberingType::Edition)),
1364 ClassExtension::SerialComponent(r) => r
1365 .edition
1366 .clone()
1367 .or_else(|| self.find_numbering(NumberingType::Edition)),
1368 ClassExtension::Classic(r) => r
1369 .edition
1370 .clone()
1371 .or_else(|| self.find_numbering(NumberingType::Edition)),
1372 _ => self.find_numbering(NumberingType::Edition),
1373 }
1374 }
1375
1376 pub fn accessed(&self) -> Option<DateValue> {
1378 class_dispatch!(&self.extension, |r| r.accessed.clone(), unknown(_) => None)
1379 }
1380
1381 pub fn unknown_fields(&self) -> Option<&std::collections::BTreeMap<String, JsonValue>> {
1387 class_dispatch!(&self.extension, |r| Some(&r.unknown_fields), unknown(_) => None)
1388 }
1389
1390 fn original_embedded(&self) -> Option<&InputReference> {
1397 let relation = match &self.extension {
1398 ClassExtension::Monograph(r) => r.original.as_ref(),
1399 ClassExtension::CollectionComponent(r) => r.original.as_ref(),
1400 ClassExtension::SerialComponent(r) => r.original.as_ref(),
1401 ClassExtension::LegalCase(r) => r.original.as_ref(),
1402 ClassExtension::Statute(r) => r.original.as_ref(),
1403 ClassExtension::Treaty(r) => r.original.as_ref(),
1404 ClassExtension::Hearing(r) => r.original.as_ref(),
1405 ClassExtension::Regulation(r) => r.original.as_ref(),
1406 ClassExtension::Brief(r) => r.original.as_ref(),
1407 ClassExtension::Classic(r) => r.original.as_ref(),
1408 ClassExtension::Patent(r) => r.original.as_ref(),
1409 ClassExtension::Dataset(r) => r.original.as_ref(),
1410 ClassExtension::Standard(r) => r.original.as_ref(),
1411 ClassExtension::Software(r) => r.original.as_ref(),
1412 ClassExtension::Event(r) => r.original.as_ref(),
1413 ClassExtension::AudioVisual(r) => r.core.original.as_ref(),
1414 _ => None,
1415 }?;
1416 match relation {
1417 WorkRelation::Embedded(p) => Some(p),
1418 WorkRelation::Id(_) => None,
1419 }
1420 }
1421
1422 pub fn original_date(&self) -> Option<DateValue> {
1424 self.original_embedded()?.effective_issued_date()
1425 }
1426
1427 pub fn original_title(&self) -> Option<Title> {
1429 self.original_embedded()?.title()
1430 }
1431
1432 pub fn original_publisher_str(&self) -> Option<String> {
1434 self.original_embedded()?
1435 .publisher_str()
1436 .filter(|value| !value.is_empty())
1437 }
1438
1439 pub fn original_publisher_place(&self) -> Option<String> {
1441 self.original_embedded()?.publisher_place()
1442 }
1443
1444 pub fn isbn(&self) -> Option<String> {
1446 match &self.extension {
1447 ClassExtension::Monograph(r) => r.isbn.clone(),
1448 _ => None,
1449 }
1450 }
1451
1452 pub fn issn(&self) -> Option<String> {
1454 match &self.extension {
1455 ClassExtension::SerialComponent(r) => r.container.as_ref().and_then(|c| match c {
1456 WorkRelation::Embedded(s) => s.issn(),
1457 WorkRelation::Id(_) => None,
1458 }),
1459 ClassExtension::Serial(r) => r.issn.clone(),
1460 _ => None,
1461 }
1462 }
1463
1464 pub fn keywords(&self) -> Option<Vec<String>> {
1466 match &self.extension {
1467 ClassExtension::Monograph(r) => r.keywords.clone(),
1468 ClassExtension::CollectionComponent(r) => r.keywords.clone(),
1469 ClassExtension::SerialComponent(r) => r.keywords.clone(),
1470 ClassExtension::Collection(r) => r.keywords.clone(),
1471 ClassExtension::Serial(_) => None,
1472 ClassExtension::LegalCase(r) => r.keywords.clone(),
1473 ClassExtension::Statute(r) => r.keywords.clone(),
1474 ClassExtension::Treaty(r) => r.keywords.clone(),
1475 ClassExtension::Hearing(r) => r.keywords.clone(),
1476 ClassExtension::Regulation(r) => r.keywords.clone(),
1477 ClassExtension::Brief(r) => r.keywords.clone(),
1478 ClassExtension::Classic(r) => r.keywords.clone(),
1479 ClassExtension::Patent(r) => r.keywords.clone(),
1480 ClassExtension::Dataset(r) => r.keywords.clone(),
1481 ClassExtension::Standard(r) => r.keywords.clone(),
1482 ClassExtension::Software(r) => r.keywords.clone(),
1483 ClassExtension::Event(_) => None,
1484 ClassExtension::AudioVisual(_) => None,
1485 ClassExtension::Unknown(_) => None,
1486 }
1487 }
1488
1489 pub fn language(&self) -> Option<LangID> {
1491 class_dispatch!(
1492 &self.extension,
1493 |r| r.language.clone(),
1494 audio_visual(r) => r.core.language.clone(),
1495 unknown(_) => None
1496 )
1497 }
1498
1499 pub fn field_languages(&self) -> &FieldLanguageMap {
1501 class_dispatch!(
1502 &self.extension,
1503 |r| &r.field_languages,
1504 unknown(_) => &EMPTY_FIELD_LANGUAGES
1505 )
1506 }
1507
1508 pub fn set_id(&mut self, id: impl Into<RefID>) {
1514 let id = id.into();
1515 class_dispatch!(&mut self.extension, |r| r.id = Some(id.clone()), unknown(data) => {
1516 data.fields
1517 .insert("id".to_string(), JsonValue::String(id.to_string()));
1518 });
1519 }
1520
1521 pub fn ref_type(&self) -> String {
1523 match &self.extension {
1524 ClassExtension::Monograph(r) => self.monograph_ref_type(r),
1525 ClassExtension::CollectionComponent(r) => collection_component_ref_type(r),
1526 ClassExtension::SerialComponent(r) => serial_component_ref_type(r),
1527 ClassExtension::Collection(r) => match r.r#type {
1528 CollectionType::EditedBook => "book",
1529 _ => "collection",
1530 }
1531 .to_string(),
1532 ClassExtension::Serial(r) => match r.r#type {
1533 SerialType::AcademicJournal => "article-journal",
1534 SerialType::Magazine => "article-magazine",
1535 SerialType::Newspaper => "article-newspaper",
1536 SerialType::BroadcastProgram => "broadcast",
1537 _ => "serial",
1538 }
1539 .to_string(),
1540 ClassExtension::LegalCase(_) => "legal-case".to_string(),
1541 ClassExtension::Statute(_) => "statute".to_string(),
1542 ClassExtension::Treaty(_) => "treaty".to_string(),
1543 ClassExtension::Hearing(_) => "hearing".to_string(),
1544 ClassExtension::Regulation(_) => "regulation".to_string(),
1545 ClassExtension::Brief(_) => "brief".to_string(),
1546 ClassExtension::Classic(_) => "classic".to_string(),
1547 ClassExtension::Patent(_) => "patent".to_string(),
1548 ClassExtension::Dataset(_) => "dataset".to_string(),
1549 ClassExtension::Standard(_) => "standard".to_string(),
1550 ClassExtension::Software(_) => "software".to_string(),
1551 ClassExtension::Event(r) => event_ref_type(r).to_string(),
1552 ClassExtension::AudioVisual(r) => audio_visual_ref_type(&r.r#type).to_string(),
1553 ClassExtension::Unknown(data) => {
1554 debug_assert!(
1564 !ReferenceClass::KNOWN.contains(&data.class.as_str()),
1565 "ClassExtension::Unknown should never wrap a known class string (got `{}`)",
1566 data.class
1567 );
1568 data.class.clone()
1569 }
1570 }
1571 }
1572
1573 fn monograph_ref_type(&self, r: &Monograph) -> String {
1574 match r.r#type {
1575 MonographType::Book => {
1576 if r.genre
1577 .as_deref()
1578 .is_some_and(|genre| genre.eq_ignore_ascii_case("musical_score"))
1579 {
1580 return "musical-score".to_string();
1581 }
1582 if r.genre.as_deref().is_some_and(|genre| genre == "pamphlet") {
1583 return "pamphlet".to_string();
1584 }
1585 if r.medium
1586 .as_deref()
1587 .is_some_and(|m| m.to_ascii_lowercase().contains("interview"))
1588 {
1589 "interview"
1590 } else {
1591 "book"
1592 }
1593 .to_string()
1594 }
1595 MonographType::Manual => "manual".to_string(),
1596 MonographType::Report => "report".to_string(),
1597 MonographType::Thesis => "thesis".to_string(),
1598 MonographType::Webpage => "webpage".to_string(),
1599 MonographType::Post => if r.genre.as_deref() == Some("post-weblog") {
1600 "post-weblog"
1601 } else {
1602 "post"
1603 }
1604 .to_string(),
1605 MonographType::Interview => "interview".to_string(),
1606 MonographType::Manuscript => "manuscript".to_string(),
1607 MonographType::Preprint => "preprint".to_string(),
1608 MonographType::PersonalCommunication => "personal-communication".to_string(),
1609 MonographType::Document => {
1610 if let Some(genre) = r.genre.as_deref() {
1615 let lowered = genre.to_ascii_lowercase();
1616 if matches!(
1617 lowered.as_str(),
1618 "map" | "figure" | "graphic" | "periodical" | "collection"
1619 ) {
1620 return lowered;
1621 }
1622 }
1623 if let Some(genre) = r.genre.as_deref()
1624 && matches!(genre, "bill-proceeding" | "bill-record")
1625 {
1626 return genre.to_string();
1627 }
1628 if self.genre().as_deref() == Some("conference-paper") {
1629 return "paper-conference".to_string();
1630 }
1631 if r.medium
1632 .as_deref()
1633 .is_some_and(|m| m.to_ascii_lowercase().contains("interview"))
1634 {
1635 "interview"
1636 } else {
1637 "document"
1638 }
1639 .to_string()
1640 }
1641 _ => r.r#type.as_str().to_string(),
1642 }
1643 }
1644}
1645
1646fn collection_component_ref_type(r: &CollectionComponent) -> String {
1647 match r.r#type {
1648 MonographComponentType::Chapter => match r.genre.as_deref() {
1649 Some("entry-dictionary") => "entry-dictionary",
1650 Some("entry-encyclopedia") => "entry-encyclopedia",
1651 Some("entry") => "entry",
1652 _ => "chapter",
1653 }
1654 .to_string(),
1655 MonographComponentType::Document => "paper-conference".to_string(),
1656 _ => r.r#type.as_str().to_string(),
1657 }
1658}
1659
1660fn serial_component_ref_type(r: &SerialComponent) -> String {
1661 if r.genre.as_deref() == Some("entry-encyclopedia") {
1662 return "entry-encyclopedia".to_string();
1663 }
1664 if let Some(genre) = r.genre.as_deref()
1665 && matches!(genre, "review" | "review-book")
1666 {
1667 return genre.to_string();
1668 }
1669 let container_type = r.container.as_ref().and_then(|c| match c {
1670 WorkRelation::Embedded(p) => Some(p.ref_type()),
1671 WorkRelation::Id(_) => None,
1672 });
1673 match container_type.as_deref() {
1674 Some("article-magazine") => "article-magazine".to_string(),
1675 Some("article-newspaper") => "article-newspaper".to_string(),
1676 Some("broadcast") => if r
1677 .genre
1678 .as_deref()
1679 .is_some_and(|g| g.to_ascii_lowercase().contains("film"))
1680 {
1681 "motion-picture"
1682 } else {
1683 "broadcast"
1684 }
1685 .to_string(),
1686 _ => "article-journal".to_string(),
1687 }
1688}
1689
1690fn event_ref_type(r: &Event) -> &'static str {
1691 let lowered = r.genre.as_deref().map(str::to_ascii_lowercase);
1692 match lowered.as_deref() {
1693 Some(g) if g.contains("conference") || g.contains("paper") => "paper-conference",
1694 Some(g) if g.contains("broadcast") => "broadcast",
1695 Some(g) if g.contains("performance") => "performance",
1696 Some(g) if g.contains("talk") || g.contains("speech") => "speech",
1697 _ => "event",
1698 }
1699}
1700
1701fn audio_visual_ref_type(kind: &AudioVisualType) -> &'static str {
1702 match kind {
1703 AudioVisualType::Film => "motion-picture",
1704 AudioVisualType::Episode | AudioVisualType::Broadcast => "broadcast",
1705 AudioVisualType::Recording => "song",
1706 }
1707}
1708
1709fn collect_contributors_by_role(
1714 entries: &[ContributorEntry],
1715 role: &ContributorRole,
1716) -> Option<Contributor> {
1717 let mut matches = entries
1718 .iter()
1719 .filter(|entry| entry.roles.contains(role))
1720 .map(|e| &e.contributor);
1721 let first = matches.next()?;
1722 let Some(second) = matches.next() else {
1723 return Some(first.clone());
1724 };
1725 let list = std::iter::once(first)
1726 .chain(std::iter::once(second))
1727 .chain(matches)
1728 .cloned()
1729 .collect();
1730 Some(Contributor::ContributorList(ContributorList(list)))
1731}