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 let sorted_refs = self.sort_references(self.bibliography.values().collect());
395 let bibliography = self.process_sorted_refs::<_, F>(sorted_refs.iter().copied(), run);
396 ProcessedReferences {
397 bibliography,
398 citations: None,
399 }
400 }
401
402 pub(crate) fn process_selected_references_with_format<F, I>(
411 &self,
412 item_ids: I,
413 run: &FinalizedRun,
414 ) -> ProcessedReferences
415 where
416 F: OutputFormat<Output = String>,
417 I: IntoIterator<Item = String>,
418 {
419 let selected: HashSet<String> = item_ids.into_iter().collect();
420 let sorted_refs = self.sort_references(self.bibliography.values().collect());
421 let bibliography = self.process_sorted_refs::<_, F>(
422 sorted_refs
423 .iter()
424 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
425 .copied(),
426 run,
427 );
428 ProcessedReferences {
429 bibliography,
430 citations: None,
431 }
432 }
433
434 pub fn process_bibliography_entry(
436 &self,
437 reference: &Reference,
438 entry_number: usize,
439 run: &FinalizedRun,
440 ) -> Option<ProcTemplate> {
441 self.with_bibliography_renderer(run, |renderer| {
442 renderer.process_bibliography_entry(reference, entry_number)
443 })
444 }
445
446 pub fn process_bibliography_entry_with_format<F>(
448 &self,
449 reference: &Reference,
450 entry_number: usize,
451 run: &FinalizedRun,
452 ) -> Option<ProcTemplate>
453 where
454 F: OutputFormat<Output = String>,
455 {
456 self.with_bibliography_renderer(run, |renderer| {
457 renderer.process_bibliography_entry_with_format::<F>(reference, entry_number)
458 })
459 }
460
461 pub fn contributors_match(&self, prev: &Reference, current: &Reference) -> bool {
465 let config = self.style.options.as_ref().unwrap_or(&self.default_config);
466 let matcher = Matcher::new(&self.style, config, &self.locale);
467 matcher.contributors_match(prev, current)
468 }
469
470 pub fn render_bibliography_with_format<F>(&self, run: &FinalizedRun) -> String
472 where
473 F: OutputFormat<Output = String>,
474 {
475 self.render_bibliography_with_format_and_annotations::<F>(None, None, run)
476 }
477
478 pub fn render_bibliography_with_format_and_annotations<F>(
480 &self,
481 annotations: Option<&HashMap<String, String>>,
482 annotation_style: Option<&AnnotationStyle>,
483 run: &FinalizedRun,
484 ) -> String
485 where
486 F: OutputFormat<Output = String>,
487 {
488 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
489 self.bibliography.keys().cloned().collect::<Vec<_>>(),
490 annotations,
491 annotation_style,
492 run,
493 )
494 }
495
496 pub fn render_selected_bibliography_with_format<F, I>(
498 &self,
499 item_ids: I,
500 run: &FinalizedRun,
501 ) -> String
502 where
503 F: OutputFormat<Output = String>,
504 I: IntoIterator<Item = String>,
505 {
506 self.render_selected_bibliography_with_format_and_annotations::<F, _>(
507 item_ids, None, None, run,
508 )
509 }
510
511 pub fn render_selected_bibliography_with_format_and_annotations<F, I>(
518 &self,
519 item_ids: I,
520 annotations: Option<&HashMap<String, String>>,
521 annotation_style: Option<&AnnotationStyle>,
522 run: &FinalizedRun,
523 ) -> String
524 where
525 F: OutputFormat<Output = String>,
526 I: IntoIterator<Item = String>,
527 {
528 let selected: HashSet<String> = item_ids.into_iter().collect();
529
530 if let Some(groups) = self.effective_custom_groups() {
532 let all_entries = self.sorted_id_stubs();
533 return self.render_with_custom_groups_filtered::<F>(
534 &all_entries,
535 groups,
536 &selected,
537 annotations,
538 annotation_style,
539 run,
540 );
541 }
542
543 let bibliography_options = self.get_bibliography_options();
545 if let Some(partitioning) = bibliography_options.sort_partitioning.as_ref()
546 && crate::sort_partitioning::should_render_sections(partitioning)
547 {
548 let all_sorted = self.sort_references(self.bibliography.values().collect());
549 let selected_sorted: Vec<&Reference> = all_sorted
550 .into_iter()
551 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
552 .collect();
553 return self.render_with_partition_sections::<F>(
554 selected_sorted,
555 partitioning,
556 annotations,
557 annotation_style,
558 run,
559 );
560 }
561
562 let sorted_refs = self.sort_references(self.bibliography.values().collect());
564
565 let bibliography = self.process_sorted_refs::<_, F>(
566 sorted_refs
567 .iter()
568 .filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
569 .copied(),
570 run,
571 );
572
573 let bibliography = self.merge_compound_entries::<F>(bibliography, run);
574 crate::render::refs_to_string_with_format::<F>(bibliography, annotations, annotation_style)
575 }
576
577 pub fn render_bibliography(&self) -> String {
585 let run = self.begin_run().finalize();
586 self.render_bibliography_with_format::<crate::render::plain::PlainText>(&run)
587 }
588
589 pub fn process_references_with_format_standalone<F>(&self) -> ProcessedReferences
592 where
593 F: OutputFormat<Output = String>,
594 {
595 let run = self.begin_run().finalize();
596 self.process_references_with_format::<F>(&run)
597 }
598
599 pub fn process_bibliography_entry_standalone(
602 &self,
603 reference: &Reference,
604 entry_number: usize,
605 ) -> Option<ProcTemplate> {
606 let run = self.begin_run().finalize();
607 self.process_bibliography_entry(reference, entry_number, &run)
608 }
609
610 pub fn process_bibliography_entry_with_format_standalone<F>(
613 &self,
614 reference: &Reference,
615 entry_number: usize,
616 ) -> Option<ProcTemplate>
617 where
618 F: OutputFormat<Output = String>,
619 {
620 let run = self.begin_run().finalize();
621 self.process_bibliography_entry_with_format::<F>(reference, entry_number, &run)
622 }
623
624 pub fn render_bibliography_with_format_standalone<F>(&self) -> String
627 where
628 F: OutputFormat<Output = String>,
629 {
630 let run = self.begin_run().finalize();
631 self.render_bibliography_with_format::<F>(&run)
632 }
633
634 pub fn render_bibliography_with_format_and_annotations_standalone<F>(
638 &self,
639 annotations: Option<&HashMap<String, String>>,
640 annotation_style: Option<&AnnotationStyle>,
641 ) -> String
642 where
643 F: OutputFormat<Output = String>,
644 {
645 let run = self.begin_run().finalize();
646 self.render_bibliography_with_format_and_annotations::<F>(
647 annotations,
648 annotation_style,
649 &run,
650 )
651 }
652
653 pub fn render_selected_bibliography_with_format_standalone<F, I>(&self, item_ids: I) -> String
656 where
657 F: OutputFormat<Output = String>,
658 I: IntoIterator<Item = String>,
659 {
660 let run = self.begin_run().finalize();
661 self.render_selected_bibliography_with_format::<F, I>(item_ids, &run)
662 }
663
664 pub fn render_selected_bibliography_with_format_and_annotations_standalone<F, I>(
668 &self,
669 item_ids: I,
670 annotations: Option<&HashMap<String, String>>,
671 annotation_style: Option<&AnnotationStyle>,
672 ) -> String
673 where
674 F: OutputFormat<Output = String>,
675 I: IntoIterator<Item = String>,
676 {
677 let run = self.begin_run().finalize();
678 self.render_selected_bibliography_with_format_and_annotations::<F, I>(
679 item_ids,
680 annotations,
681 annotation_style,
682 &run,
683 )
684 }
685}