1mod compound;
13mod grouping;
14
15#[cfg(test)]
16#[allow(
17 clippy::unwrap_used,
18 clippy::expect_used,
19 clippy::panic,
20 clippy::indexing_slicing,
21 clippy::todo,
22 clippy::unimplemented,
23 clippy::unreachable,
24 clippy::get_unwrap,
25 reason = "Panicking is acceptable and often desired in tests."
26)]
27mod tests;
28
29use super::matching::Matcher;
30use super::rendering::{CompoundRenderData, Renderer, RendererResources};
31use super::run_state::FinalizedRun;
32use super::{ProcessedReferences, Processor};
33use crate::api::AnnotationStyle;
34use crate::reference::Reference;
35use crate::render::format::OutputFormat;
36use crate::render::{ProcEntry, ProcTemplate};
37use crate::values::ProcHints;
38use citum_schema::grouping::BibliographyGroup;
39use citum_schema::options::{Config, bibliography::BibliographyConfig};
40use std::collections::{HashMap, HashSet};
41use std::sync::Arc;
42
43#[derive(Debug, Clone, Default)]
45pub(crate) struct RenderedBibliographyGroup {
46 pub(crate) heading: Option<String>,
48 pub(crate) body: String,
50 pub(crate) entries: Vec<crate::render::ProcEntry>,
52}
53
54#[derive(Debug, Clone, Default)]
61pub(crate) struct DocumentBibliography {
62 pub(crate) content: String,
64 pub(crate) entries: Vec<crate::render::ProcEntry>,
66}
67
68#[cfg(feature = "parallel")]
76pub(crate) const PARALLEL_MIN_ENTRIES: usize = 32;
77
78fn number_sorted_refs<'a>(
88 sorted_refs: impl Iterator<Item = &'a Reference>,
89 run: &FinalizedRun,
90) -> Vec<(&'a Reference, usize)> {
91 let numbers = run
92 .state()
93 .citation_numbers
94 .read()
95 .unwrap_or_else(std::sync::PoisonError::into_inner);
96 sorted_refs
97 .enumerate()
98 .map(|(index, reference)| {
99 let entry_number = numbers
100 .get(reference.id().unwrap_or_default().as_str())
101 .copied()
102 .unwrap_or(index + 1);
103 (reference, entry_number)
104 })
105 .collect()
106}
107
108struct EntryRenderContext<'a> {
122 style: &'a citum_schema::Style,
124 hints: &'a HashMap<String, ProcHints>,
126 config: Arc<Config>,
128 bibliography_config: Arc<BibliographyConfig>,
130 run: &'a FinalizedRun,
132}
133
134impl Processor {
135 fn effective_custom_groups(&self) -> Option<&[BibliographyGroup]> {
141 self.style
142 .bibliography
143 .as_ref()
144 .filter(|bibliography| bibliography.groups_enabled)
145 .and_then(|bibliography| bibliography.groups.as_deref())
146 }
147
148 fn flat_render_context<'a>(&'a self, run: &'a FinalizedRun) -> EntryRenderContext<'a> {
151 EntryRenderContext {
152 style: &self.style,
153 hints: &self.hints,
154 config: Arc::new(self.get_bibliography_config().into_owned()),
155 bibliography_config: Arc::new(self.get_bibliography_options().into_owned()),
156 run,
157 }
158 }
159
160 fn entry_renderer<'a>(&'a self, ctx: &EntryRenderContext<'a>) -> Renderer<'a> {
162 Renderer::new(
163 RendererResources {
164 style: ctx.style,
165 bibliography: &self.bibliography,
166 locale: &self.locale,
167 config: ctx.config.clone(),
168 bibliography_config: Some(ctx.bibliography_config.clone()),
169 first_note_by_id: None,
170 },
171 ctx.hints,
172 &ctx.run.state().citation_numbers,
173 CompoundRenderData {
174 set_by_ref: &self.compound_set_by_ref,
175 member_index: &self.compound_member_index,
176 sets: &self.compound_sets,
177 },
178 self.show_semantics,
179 self.inject_ast_indices,
180 self.abbreviation_map.as_ref(),
181 )
182 }
183
184 fn render_numbered_refs<'a, F>(
191 &self,
192 numbered_refs: &[(&'a Reference, usize)],
193 ctx: &EntryRenderContext<'_>,
194 ) -> Vec<(&'a Reference, Option<ProcTemplate>, Option<String>)>
195 where
196 F: OutputFormat<Output = String>,
197 {
198 #[cfg(feature = "parallel")]
199 if numbered_refs.len() >= PARALLEL_MIN_ENTRIES {
200 return self.render_numbered_refs_parallel::<F>(numbered_refs, ctx);
201 }
202 self.render_numbered_refs_sequential::<F>(numbered_refs, ctx)
203 }
204
205 fn render_numbered_refs_sequential<'a, F>(
208 &self,
209 numbered_refs: &[(&'a Reference, usize)],
210 ctx: &EntryRenderContext<'_>,
211 ) -> Vec<(&'a Reference, Option<ProcTemplate>, Option<String>)>
212 where
213 F: OutputFormat<Output = String>,
214 {
215 let renderer = self.entry_renderer(ctx);
216 numbered_refs
217 .iter()
218 .map(|&(reference, entry_number)| {
219 (
220 reference,
221 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number),
222 renderer.bibliography_marker_with_format::<F>(reference, entry_number),
223 )
224 })
225 .collect()
226 }
227
228 #[cfg(feature = "parallel")]
238 fn render_numbered_refs_parallel<'a, F>(
239 &self,
240 numbered_refs: &[(&'a Reference, usize)],
241 ctx: &EntryRenderContext<'_>,
242 ) -> Vec<(&'a Reference, Option<ProcTemplate>, Option<String>)>
243 where
244 F: OutputFormat<Output = String>,
245 {
246 use rayon::prelude::*;
247 numbered_refs
248 .par_iter()
249 .map(|&(reference, entry_number)| {
250 let renderer = self.entry_renderer(ctx);
251 (
252 reference,
253 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number),
254 renderer.bibliography_marker_with_format::<F>(reference, entry_number),
255 )
256 })
257 .collect()
258 }
259
260 fn with_bibliography_renderer<T>(
262 &self,
263 run: &FinalizedRun,
264 render: impl FnOnce(Renderer<'_>) -> T,
265 ) -> T {
266 let bibliography_shared_config = self.get_bibliography_config();
267 let bibliography_config = self.get_bibliography_options().into_owned();
268 let renderer = Renderer::new(
269 RendererResources {
270 style: &self.style,
271 bibliography: &self.bibliography,
272 locale: &self.locale,
273 config: Arc::new(bibliography_shared_config.into_owned()),
274 bibliography_config: Some(Arc::new(bibliography_config)),
275 first_note_by_id: None,
276 },
277 &self.hints,
278 &run.state().citation_numbers,
279 CompoundRenderData {
280 set_by_ref: &self.compound_set_by_ref,
281 member_index: &self.compound_member_index,
282 sets: &self.compound_sets,
283 },
284 self.show_semantics,
285 self.inject_ast_indices,
286 self.abbreviation_map.as_ref(),
287 );
288
289 render(renderer)
290 }
291
292 fn process_sorted_refs<'a, I, F>(&self, sorted_refs: I, run: &FinalizedRun) -> Vec<ProcEntry>
306 where
307 I: Iterator<Item = &'a Reference>,
308 F: OutputFormat<Output = String>,
309 {
310 let ctx = self.flat_render_context(run);
311 let numbered_refs = number_sorted_refs(sorted_refs, run);
312 let rendered = self.render_numbered_refs::<F>(&numbered_refs, &ctx);
313
314 let substitute = ctx
315 .bibliography_config
316 .subsequent_author_substitute
317 .as_ref();
318 self.apply_substitution_post_pass::<F>(rendered, substitute, &ctx)
319 }
320
321 fn apply_substitution_post_pass<F>(
333 &self,
334 rendered: Vec<(&Reference, Option<ProcTemplate>, Option<String>)>,
335 substitute: Option<&String>,
336 ctx: &EntryRenderContext<'_>,
337 ) -> Vec<ProcEntry>
338 where
339 F: OutputFormat<Output = String>,
340 {
341 let renderer = substitute.map(|_| self.entry_renderer(ctx));
342 let mut bibliography = Vec::with_capacity(rendered.len());
343 let mut previous_reference: Option<&Reference> = None;
344
345 for (reference, processed, marker) in rendered {
346 let Some(mut processed) = processed else {
347 continue;
348 };
349
350 if let Some(substitute_string) = substitute
351 && let Some(renderer) = renderer.as_ref()
352 && let Some(previous) = previous_reference
353 && self.contributors_match(previous, reference)
354 {
355 renderer
356 .apply_author_substitution_with_format::<F>(&mut processed, substitute_string);
357 }
358
359 let ref_id = reference.id().unwrap_or_default().to_string();
360 bibliography.push(ProcEntry {
361 id: ref_id,
362 marker,
363 template: processed,
364 metadata: self.extract_metadata(reference, ctx),
365 });
366 previous_reference = Some(reference);
367 }
368
369 bibliography
370 }
371
372 pub fn process_references(&self) -> ProcessedReferences {
381 let run = self.begin_run().finalize();
382 self.process_references_with_format::<crate::render::plain::PlainText>(&run)
383 }
384
385 pub fn process_references_with_format<F>(&self, run: &FinalizedRun) -> ProcessedReferences
394 where
395 F: OutputFormat<Output = String>,
396 {
397 if self.style.bibliography.is_none() {
398 return ProcessedReferences::default();
399 }
400 let sorted_refs = self.sort_references(self.bibliography.values().collect());
401 let bibliography = self.process_sorted_refs::<_, F>(sorted_refs.iter().copied(), run);
402 ProcessedReferences {
403 bibliography,
404 citations: None,
405 }
406 }
407
408 pub(crate) fn process_selected_references_with_format<F, I>(
417 &self,
418 item_ids: I,
419 run: &FinalizedRun,
420 ) -> ProcessedReferences
421 where
422 F: OutputFormat<Output = String>,
423 I: IntoIterator<Item = String>,
424 {
425 if self.style.bibliography.is_none() {
426 return ProcessedReferences::default();
427 }
428 let selected: HashSet<String> = item_ids.into_iter().collect();
429 let sorted_refs = self.sort_references(self.bibliography.values().collect());
430 let bibliography = self.process_sorted_refs::<_, F>(
431 sorted_refs
432 .iter()
433 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
434 .copied(),
435 run,
436 );
437 ProcessedReferences {
438 bibliography,
439 citations: None,
440 }
441 }
442
443 pub fn process_bibliography_entry(
445 &self,
446 reference: &Reference,
447 entry_number: usize,
448 run: &FinalizedRun,
449 ) -> Option<ProcTemplate> {
450 self.style.bibliography.as_ref()?;
451 self.with_bibliography_renderer(run, |renderer| {
452 renderer.process_bibliography_entry(reference, entry_number)
453 })
454 }
455
456 pub fn process_bibliography_entry_with_format<F>(
458 &self,
459 reference: &Reference,
460 entry_number: usize,
461 run: &FinalizedRun,
462 ) -> Option<ProcTemplate>
463 where
464 F: OutputFormat<Output = String>,
465 {
466 self.style.bibliography.as_ref()?;
467 self.with_bibliography_renderer(run, |renderer| {
468 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number)
469 })
470 }
471
472 pub fn contributors_match(&self, prev: &Reference, current: &Reference) -> bool {
476 let config = self.style.options.as_ref().unwrap_or(&self.default_config);
477 let matcher = Matcher::new(&self.style, config, &self.locale);
478 matcher.contributors_match(prev, current)
479 }
480
481 pub fn render_bibliography_with_format<F>(&self, run: &FinalizedRun) -> String
483 where
484 F: OutputFormat<Output = String>,
485 {
486 self.render_bibliography_with_format_and_annotations::<F>(None, None, run)
487 }
488
489 pub fn render_bibliography_with_format_and_annotations<F>(
491 &self,
492 annotations: Option<&HashMap<String, String>>,
493 annotation_style: Option<&AnnotationStyle>,
494 run: &FinalizedRun,
495 ) -> String
496 where
497 F: OutputFormat<Output = String>,
498 {
499 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
500 self.bibliography.keys().cloned().collect::<Vec<_>>(),
501 annotations,
502 annotation_style,
503 run,
504 )
505 }
506
507 pub fn render_selected_bibliography_with_format<F, I>(
509 &self,
510 item_ids: I,
511 run: &FinalizedRun,
512 ) -> String
513 where
514 F: OutputFormat<Output = String>,
515 I: IntoIterator<Item = String>,
516 {
517 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
518 item_ids, None, None, run,
519 )
520 }
521
522 pub fn render_selected_bibliography_with_format_and_annotations<F, I>(
529 &self,
530 item_ids: I,
531 annotations: Option<&HashMap<String, String>>,
532 annotation_style: Option<&AnnotationStyle>,
533 run: &FinalizedRun,
534 ) -> String
535 where
536 F: OutputFormat<Output = String>,
537 I: IntoIterator<Item = String>,
538 {
539 if self.style.bibliography.is_none() {
540 return String::new();
541 }
542 let selected: HashSet<String> = item_ids.into_iter().collect();
543
544 if let Some(groups) = self.effective_custom_groups() {
546 let all_entries = self.sorted_id_stubs();
547 return self.render_with_custom_groups_filtered::<F>(
548 &all_entries,
549 groups,
550 &selected,
551 annotations,
552 annotation_style,
553 run,
554 );
555 }
556
557 let bibliography_options = self.get_bibliography_options();
559 if let Some(partitioning) = bibliography_options.sort_partitioning.as_ref()
560 && crate::sort_partitioning::should_render_sections(partitioning)
561 {
562 let all_sorted = self.sort_references(self.bibliography.values().collect());
563 let selected_sorted: Vec<&Reference> = all_sorted
564 .into_iter()
565 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
566 .collect();
567 return self.render_with_partition_sections::<F>(
568 selected_sorted,
569 partitioning,
570 annotations,
571 annotation_style,
572 run,
573 );
574 }
575
576 let sorted_refs = self.sort_references(self.bibliography.values().collect());
578
579 let bibliography = self.process_sorted_refs::<_, F>(
580 sorted_refs
581 .iter()
582 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
583 .copied(),
584 run,
585 );
586
587 let bibliography = self.merge_compound_entries::<F>(bibliography, run);
588 crate::render::refs_to_string_with_format::<F>(bibliography, annotations, annotation_style)
589 }
590
591 pub fn render_bibliography(&self) -> String {
599 let run = self.begin_run().finalize();
600 self.render_bibliography_with_format::<crate::render::plain::PlainText>(&run)
601 }
602
603 pub fn process_references_with_format_standalone<F>(&self) -> ProcessedReferences
606 where
607 F: OutputFormat<Output = String>,
608 {
609 let run = self.begin_run().finalize();
610 self.process_references_with_format::<F>(&run)
611 }
612
613 pub fn process_bibliography_entry_standalone(
616 &self,
617 reference: &Reference,
618 entry_number: usize,
619 ) -> Option<ProcTemplate> {
620 let run = self.begin_run().finalize();
621 self.process_bibliography_entry(reference, entry_number, &run)
622 }
623
624 pub fn process_bibliography_entry_with_format_standalone<F>(
627 &self,
628 reference: &Reference,
629 entry_number: usize,
630 ) -> Option<ProcTemplate>
631 where
632 F: OutputFormat<Output = String>,
633 {
634 let run = self.begin_run().finalize();
635 self.process_bibliography_entry_with_format::<F>(reference, entry_number, &run)
636 }
637
638 pub fn render_bibliography_with_format_standalone<F>(&self) -> String
641 where
642 F: OutputFormat<Output = String>,
643 {
644 let run = self.begin_run().finalize();
645 self.render_bibliography_with_format::<F>(&run)
646 }
647
648 pub fn render_bibliography_with_format_and_annotations_standalone<F>(
652 &self,
653 annotations: Option<&HashMap<String, String>>,
654 annotation_style: Option<&AnnotationStyle>,
655 ) -> String
656 where
657 F: OutputFormat<Output = String>,
658 {
659 let run = self.begin_run().finalize();
660 self.render_bibliography_with_format_and_annotations::<F>(
661 annotations,
662 annotation_style,
663 &run,
664 )
665 }
666
667 pub fn render_selected_bibliography_with_format_standalone<F, I>(&self, item_ids: I) -> String
670 where
671 F: OutputFormat<Output = String>,
672 I: IntoIterator<Item = String>,
673 {
674 let run = self.begin_run().finalize();
675 self.render_selected_bibliography_with_format::<F, I>(item_ids, &run)
676 }
677
678 pub fn render_selected_bibliography_with_format_and_annotations_standalone<F, I>(
682 &self,
683 item_ids: I,
684 annotations: Option<&HashMap<String, String>>,
685 annotation_style: Option<&AnnotationStyle>,
686 ) -> String
687 where
688 F: OutputFormat<Output = String>,
689 I: IntoIterator<Item = String>,
690 {
691 let run = self.begin_run().finalize();
692 self.render_selected_bibliography_with_format_and_annotations::<F, I>(
693 item_ids,
694 annotations,
695 annotation_style,
696 &run,
697 )
698 }
699}