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>)>
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>)>
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 )
223 })
224 .collect()
225 }
226
227 #[cfg(feature = "parallel")]
237 fn render_numbered_refs_parallel<'a, F>(
238 &self,
239 numbered_refs: &[(&'a Reference, usize)],
240 ctx: &EntryRenderContext<'_>,
241 ) -> Vec<(&'a Reference, Option<ProcTemplate>)>
242 where
243 F: OutputFormat<Output = String>,
244 {
245 use rayon::prelude::*;
246 numbered_refs
247 .par_iter()
248 .map(|&(reference, entry_number)| {
249 let renderer = self.entry_renderer(ctx);
250 (
251 reference,
252 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number),
253 )
254 })
255 .collect()
256 }
257
258 fn with_bibliography_renderer<T>(
260 &self,
261 run: &FinalizedRun,
262 render: impl FnOnce(Renderer<'_>) -> T,
263 ) -> T {
264 let bibliography_shared_config = self.get_bibliography_config();
265 let bibliography_config = self.get_bibliography_options().into_owned();
266 let renderer = Renderer::new(
267 RendererResources {
268 style: &self.style,
269 bibliography: &self.bibliography,
270 locale: &self.locale,
271 config: Arc::new(bibliography_shared_config.into_owned()),
272 bibliography_config: Some(Arc::new(bibliography_config)),
273 first_note_by_id: None,
274 },
275 &self.hints,
276 &run.state().citation_numbers,
277 CompoundRenderData {
278 set_by_ref: &self.compound_set_by_ref,
279 member_index: &self.compound_member_index,
280 sets: &self.compound_sets,
281 },
282 self.show_semantics,
283 self.inject_ast_indices,
284 self.abbreviation_map.as_ref(),
285 );
286
287 render(renderer)
288 }
289
290 fn process_sorted_refs<'a, I, F>(&self, sorted_refs: I, run: &FinalizedRun) -> Vec<ProcEntry>
304 where
305 I: Iterator<Item = &'a Reference>,
306 F: OutputFormat<Output = String>,
307 {
308 let ctx = self.flat_render_context(run);
309 let numbered_refs = number_sorted_refs(sorted_refs, run);
310 let rendered = self.render_numbered_refs::<F>(&numbered_refs, &ctx);
311
312 let substitute = ctx
313 .bibliography_config
314 .subsequent_author_substitute
315 .as_ref();
316 self.apply_substitution_post_pass::<F>(rendered, substitute, &ctx)
317 }
318
319 fn apply_substitution_post_pass<F>(
331 &self,
332 rendered: Vec<(&Reference, Option<ProcTemplate>)>,
333 substitute: Option<&String>,
334 ctx: &EntryRenderContext<'_>,
335 ) -> Vec<ProcEntry>
336 where
337 F: OutputFormat<Output = String>,
338 {
339 let renderer = substitute.map(|_| self.entry_renderer(ctx));
340 let mut bibliography = Vec::with_capacity(rendered.len());
341 let mut previous_reference: Option<&Reference> = None;
342
343 for (reference, processed) in rendered {
344 let Some(mut processed) = processed else {
345 continue;
346 };
347
348 if let Some(substitute_string) = substitute
349 && let Some(renderer) = renderer.as_ref()
350 && let Some(previous) = previous_reference
351 && self.contributors_match(previous, reference)
352 {
353 renderer
354 .apply_author_substitution_with_format::<F>(&mut processed, substitute_string);
355 }
356
357 let ref_id = reference.id().unwrap_or_default().to_string();
358 bibliography.push(ProcEntry {
359 id: ref_id,
360 template: processed,
361 metadata: self.extract_metadata(reference, ctx),
362 });
363 previous_reference = Some(reference);
364 }
365
366 bibliography
367 }
368
369 pub fn process_references(&self) -> ProcessedReferences {
378 let run = self.begin_run().finalize();
379 self.process_references_with_format::<crate::render::plain::PlainText>(&run)
380 }
381
382 pub fn process_references_with_format<F>(&self, run: &FinalizedRun) -> ProcessedReferences
391 where
392 F: OutputFormat<Output = String>,
393 {
394 if self.style.bibliography.is_none() {
395 return ProcessedReferences::default();
396 }
397 let sorted_refs = self.sort_references(self.bibliography.values().collect());
398 let bibliography = self.process_sorted_refs::<_, F>(sorted_refs.iter().copied(), run);
399 ProcessedReferences {
400 bibliography,
401 citations: None,
402 }
403 }
404
405 pub(crate) fn process_selected_references_with_format<F, I>(
414 &self,
415 item_ids: I,
416 run: &FinalizedRun,
417 ) -> ProcessedReferences
418 where
419 F: OutputFormat<Output = String>,
420 I: IntoIterator<Item = String>,
421 {
422 if self.style.bibliography.is_none() {
423 return ProcessedReferences::default();
424 }
425 let selected: HashSet<String> = item_ids.into_iter().collect();
426 let sorted_refs = self.sort_references(self.bibliography.values().collect());
427 let bibliography = self.process_sorted_refs::<_, F>(
428 sorted_refs
429 .iter()
430 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
431 .copied(),
432 run,
433 );
434 ProcessedReferences {
435 bibliography,
436 citations: None,
437 }
438 }
439
440 pub fn process_bibliography_entry(
442 &self,
443 reference: &Reference,
444 entry_number: usize,
445 run: &FinalizedRun,
446 ) -> Option<ProcTemplate> {
447 self.style.bibliography.as_ref()?;
448 self.with_bibliography_renderer(run, |renderer| {
449 renderer.process_bibliography_entry(reference, entry_number)
450 })
451 }
452
453 pub fn process_bibliography_entry_with_format<F>(
455 &self,
456 reference: &Reference,
457 entry_number: usize,
458 run: &FinalizedRun,
459 ) -> Option<ProcTemplate>
460 where
461 F: OutputFormat<Output = String>,
462 {
463 self.style.bibliography.as_ref()?;
464 self.with_bibliography_renderer(run, |renderer| {
465 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number)
466 })
467 }
468
469 pub fn contributors_match(&self, prev: &Reference, current: &Reference) -> bool {
473 let config = self.style.options.as_ref().unwrap_or(&self.default_config);
474 let matcher = Matcher::new(&self.style, config, &self.locale);
475 matcher.contributors_match(prev, current)
476 }
477
478 pub fn render_bibliography_with_format<F>(&self, run: &FinalizedRun) -> String
480 where
481 F: OutputFormat<Output = String>,
482 {
483 self.render_bibliography_with_format_and_annotations::<F>(None, None, run)
484 }
485
486 pub fn render_bibliography_with_format_and_annotations<F>(
488 &self,
489 annotations: Option<&HashMap<String, String>>,
490 annotation_style: Option<&AnnotationStyle>,
491 run: &FinalizedRun,
492 ) -> String
493 where
494 F: OutputFormat<Output = String>,
495 {
496 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
497 self.bibliography.keys().cloned().collect::<Vec<_>>(),
498 annotations,
499 annotation_style,
500 run,
501 )
502 }
503
504 pub fn render_selected_bibliography_with_format<F, I>(
506 &self,
507 item_ids: I,
508 run: &FinalizedRun,
509 ) -> String
510 where
511 F: OutputFormat<Output = String>,
512 I: IntoIterator<Item = String>,
513 {
514 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
515 item_ids, None, None, run,
516 )
517 }
518
519 pub fn render_selected_bibliography_with_format_and_annotations<F, I>(
526 &self,
527 item_ids: I,
528 annotations: Option<&HashMap<String, String>>,
529 annotation_style: Option<&AnnotationStyle>,
530 run: &FinalizedRun,
531 ) -> String
532 where
533 F: OutputFormat<Output = String>,
534 I: IntoIterator<Item = String>,
535 {
536 if self.style.bibliography.is_none() {
537 return String::new();
538 }
539 let selected: HashSet<String> = item_ids.into_iter().collect();
540
541 if let Some(groups) = self.effective_custom_groups() {
543 let all_entries = self.sorted_id_stubs();
544 return self.render_with_custom_groups_filtered::<F>(
545 &all_entries,
546 groups,
547 &selected,
548 annotations,
549 annotation_style,
550 run,
551 );
552 }
553
554 let bibliography_options = self.get_bibliography_options();
556 if let Some(partitioning) = bibliography_options.sort_partitioning.as_ref()
557 && crate::sort_partitioning::should_render_sections(partitioning)
558 {
559 let all_sorted = self.sort_references(self.bibliography.values().collect());
560 let selected_sorted: Vec<&Reference> = all_sorted
561 .into_iter()
562 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
563 .collect();
564 return self.render_with_partition_sections::<F>(
565 selected_sorted,
566 partitioning,
567 annotations,
568 annotation_style,
569 run,
570 );
571 }
572
573 let sorted_refs = self.sort_references(self.bibliography.values().collect());
575
576 let bibliography = self.process_sorted_refs::<_, F>(
577 sorted_refs
578 .iter()
579 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
580 .copied(),
581 run,
582 );
583
584 let bibliography = self.merge_compound_entries::<F>(bibliography, run);
585 crate::render::refs_to_string_with_format::<F>(bibliography, annotations, annotation_style)
586 }
587
588 pub fn render_bibliography(&self) -> String {
596 let run = self.begin_run().finalize();
597 self.render_bibliography_with_format::<crate::render::plain::PlainText>(&run)
598 }
599
600 pub fn process_references_with_format_standalone<F>(&self) -> ProcessedReferences
603 where
604 F: OutputFormat<Output = String>,
605 {
606 let run = self.begin_run().finalize();
607 self.process_references_with_format::<F>(&run)
608 }
609
610 pub fn process_bibliography_entry_standalone(
613 &self,
614 reference: &Reference,
615 entry_number: usize,
616 ) -> Option<ProcTemplate> {
617 let run = self.begin_run().finalize();
618 self.process_bibliography_entry(reference, entry_number, &run)
619 }
620
621 pub fn process_bibliography_entry_with_format_standalone<F>(
624 &self,
625 reference: &Reference,
626 entry_number: usize,
627 ) -> Option<ProcTemplate>
628 where
629 F: OutputFormat<Output = String>,
630 {
631 let run = self.begin_run().finalize();
632 self.process_bibliography_entry_with_format::<F>(reference, entry_number, &run)
633 }
634
635 pub fn render_bibliography_with_format_standalone<F>(&self) -> String
638 where
639 F: OutputFormat<Output = String>,
640 {
641 let run = self.begin_run().finalize();
642 self.render_bibliography_with_format::<F>(&run)
643 }
644
645 pub fn render_bibliography_with_format_and_annotations_standalone<F>(
649 &self,
650 annotations: Option<&HashMap<String, String>>,
651 annotation_style: Option<&AnnotationStyle>,
652 ) -> String
653 where
654 F: OutputFormat<Output = String>,
655 {
656 let run = self.begin_run().finalize();
657 self.render_bibliography_with_format_and_annotations::<F>(
658 annotations,
659 annotation_style,
660 &run,
661 )
662 }
663
664 pub fn render_selected_bibliography_with_format_standalone<F, I>(&self, item_ids: I) -> String
667 where
668 F: OutputFormat<Output = String>,
669 I: IntoIterator<Item = String>,
670 {
671 let run = self.begin_run().finalize();
672 self.render_selected_bibliography_with_format::<F, I>(item_ids, &run)
673 }
674
675 pub fn render_selected_bibliography_with_format_and_annotations_standalone<F, I>(
679 &self,
680 item_ids: I,
681 annotations: Option<&HashMap<String, String>>,
682 annotation_style: Option<&AnnotationStyle>,
683 ) -> String
684 where
685 F: OutputFormat<Output = String>,
686 I: IntoIterator<Item = String>,
687 {
688 let run = self.begin_run().finalize();
689 self.render_selected_bibliography_with_format_and_annotations::<F, I>(
690 item_ids,
691 annotations,
692 annotation_style,
693 &run,
694 )
695 }
696}