use std::collections::HashMap;
use crate::{
HasSpan, Span,
blocks::{Block, IsBlock},
content::{XrefSegment, render_xref_template},
document::Catalog,
parser::{InlineSubstitutionRenderer, ReferenceResolver, ReferenceWarnings, ResolutionContext},
};
struct TitleNode<'src> {
template: String,
xrefs: Vec<XrefSegment>,
map_id: Option<String>,
source: Span<'src>,
}
pub(crate) fn resolve_title_references<'src>(
blocks: &mut [Block<'src>],
catalog: &Catalog,
resolver: &dyn ReferenceResolver,
renderer: &dyn InlineSubstitutionRenderer,
warnings: &mut ReferenceWarnings<'src>,
) {
let mut nodes: Vec<TitleNode<'src>> = Vec::new();
collect(blocks, &mut nodes);
if nodes.is_empty() {
return;
}
let mut id_to_node: HashMap<&str, (usize, &TitleNode<'src>)> = HashMap::new();
for (index, node) in nodes.iter().enumerate() {
if let Some(id) = &node.map_id {
id_to_node.entry(id.as_str()).or_insert((index, node));
}
}
let mut memo: Vec<Option<String>> = vec![None; nodes.len()];
let mut in_progress: Vec<bool> = vec![false; nodes.len()];
for (index, node) in nodes.iter().enumerate() {
compute(
index,
node,
&id_to_node,
catalog,
resolver,
renderer,
&mut memo,
&mut in_progress,
warnings,
);
}
let mut index = 0;
write_back(blocks, &memo, &mut index);
}
fn collect<'src>(blocks: &mut [Block<'src>], nodes: &mut Vec<TitleNode<'src>>) {
for block in blocks.iter_mut() {
if let Block::Section(section) = block {
if let Some((template, xrefs)) = section.section_title_deferred_parts() {
let map_id = if section.has_explicit_reftext() {
None
} else {
section.reference_id()
};
nodes.push(TitleNode {
template: template.to_string(),
xrefs: xrefs.to_vec(),
map_id,
source: section.section_title_source(),
});
}
} else {
let source = block.span();
if let Some((template, xrefs)) = block
.block_title_content_mut()
.and_then(|title| title.deferred_parts())
{
nodes.push(TitleNode {
template: template.to_string(),
xrefs: xrefs.to_vec(),
map_id: None,
source,
});
}
}
collect(block.child_blocks_mut(), nodes);
}
}
fn write_back<'src>(blocks: &mut [Block<'src>], memo: &[Option<String>], index: &mut usize) {
for block in blocks.iter_mut() {
if let Block::Section(section) = block {
if section.section_title_deferred_parts().is_some() {
if let Some(rendered) = memo.get(*index).and_then(Option::as_ref) {
section.set_section_title_rendered(rendered.clone());
}
*index += 1;
}
} else if let Some(title) = block.block_title_content_mut()
&& title.deferred_parts().is_some()
{
if let Some(rendered) = memo.get(*index).and_then(Option::as_ref) {
title.set_rendered(rendered.clone());
}
*index += 1;
}
write_back(block.child_blocks_mut(), memo, index);
}
}
#[allow(clippy::too_many_arguments)]
fn compute<'src>(
index: usize,
node: &TitleNode<'src>,
id_to_node: &HashMap<&str, (usize, &TitleNode<'src>)>,
catalog: &Catalog,
resolver: &dyn ReferenceResolver,
renderer: &dyn InlineSubstitutionRenderer,
memo: &mut [Option<String>],
in_progress: &mut [bool],
warnings: &mut ReferenceWarnings<'src>,
) -> String {
if let Some(Some(rendered)) = memo.get(index) {
return rendered.clone();
}
if let Some(flag) = in_progress.get_mut(index) {
*flag = true;
}
let mut xrefs = node.xrefs.clone();
for xref in xrefs.iter_mut() {
let mut resolved = resolver.resolve(&ResolutionContext {
target: &xref.target,
provided_text: xref.provided_text.as_deref(),
derived: xref.derived.as_ref(),
});
let has_explicit_text = xref.provided_text.as_deref().is_some_and(|t| !t.is_empty());
if !has_explicit_text
&& let Some(reference) = resolved.as_mut()
&& let Some(target_id) = lookup_id(catalog, &xref.target)
&& let Some(&(target_index, target_node)) = id_to_node.get(target_id.as_str())
&& reference.href.strip_prefix('#') == Some(target_id.as_str())
{
let catalog_reftext = catalog
.get_ref(&target_id)
.and_then(|entry| entry.reftext.as_deref());
let resolver_chose_text = reference
.text
.as_deref()
.is_some_and(|text| Some(text) != catalog_reftext);
if !resolver_chose_text {
let target_in_progress = in_progress.get(target_index).copied().unwrap_or(false);
reference.text = if target_in_progress {
None
} else {
Some(compute(
target_index,
target_node,
id_to_node,
catalog,
resolver,
renderer,
memo,
in_progress,
warnings,
))
};
}
}
if resolved.is_none() && xref.derived.is_none() {
warnings.unresolved(&xref.target, node.source);
}
xref.resolved = resolved;
}
let rendered = render_xref_template(&node.template, &xrefs, renderer);
if let Some(flag) = in_progress.get_mut(index) {
*flag = false;
}
if let Some(slot) = memo.get_mut(index) {
*slot = Some(rendered.clone());
}
rendered
}
fn lookup_id(catalog: &Catalog, target: &str) -> Option<String> {
if catalog.contains_id(target) {
Some(target.to_string())
} else {
catalog.resolve_id(target)
}
}