From b3c1970c878c3a31458b2572778c1e0cacb4eece Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Thu, 7 Mar 2024 12:56:09 +0100 Subject: [PATCH] Fix trailing quotation mak in quotes --- src/csl/mod.rs | 90 +-------- src/csl/rendering/mod.rs | 364 ++++++++++++++++++++++++++++--------- src/csl/rendering/names.rs | 116 ++++++++---- tests/citeproc-pass.txt | 3 +- 4 files changed, 373 insertions(+), 200 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index f5d8b139..ca8be44d 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -1617,8 +1617,6 @@ pub(crate) struct WritingContext { cases: NonEmptyStack>, /// Inheritable name options. name_options: NonEmptyStack, - /// Usage info for the current nesting level. - usage_info: RefCell>, // Buffers. /// The buffer we're writing to. If block-level or formatting changes, we @@ -1643,7 +1641,6 @@ impl Default for WritingContext { format_stack: NonEmptyStack::default(), cases: NonEmptyStack::default(), name_options: NonEmptyStack::default(), - usage_info: RefCell::default(), buf: CaseFolder::default(), elem_stack: NonEmptyStack::default(), } @@ -1837,16 +1834,6 @@ impl WritingContext { self.elem_stack.finish() } - /// Note that we have used a macro that had non-empty content. - fn printed_non_empty_macro(&mut self) { - self.usage_info.get_mut().last_mut().has_used_macros = true; - } - - /// Note that we have used a group that had non-empty content. - fn printed_non_empty_group(&mut self) { - self.usage_info.get_mut().last_mut().has_non_empty_group = true; - } - /// Set whether to strip periods. fn may_strip_periods(&mut self, strip: bool) { self.strip_periods = strip; @@ -1882,38 +1869,12 @@ impl WritingContext { self.cases.drain(idx.0).for_each(drop); } - - /// Push an element on the usage info stack. - fn push_usage_info(&mut self) -> UsageInfoIdx { - let info = self.usage_info.get_mut(); - let idx = info.len(); - info.push(UsageInfo::new()); - UsageInfoIdx(idx) - } - /// Reconfigures the case folder's case to the current fn reconfigure(&mut self) { self.buf .reconfigure((*self.cases.last()).map(Into::into).unwrap_or_default()); } - /// Pop an element from the usage info stack. - fn pop_usage_info(&mut self, idx: UsageInfoIdx) -> UsageInfo { - let info = self.usage_info.get_mut(); - let mut v = info.drain(idx.0).collect::>(); - if v.is_empty() { - return UsageInfo::default(); - } - - let mut first = v.remove(0); - - for e in v.drain(0..v.len()) { - first = first.merge_child(e); - } - - first - } - /// Push a new suppressed variable if we are suppressing queried variables. fn maybe_suppress(&self, variable: Variable) { if self.suppress_queried_variables { @@ -1945,11 +1906,6 @@ impl WritingContext { .sum::() } - /// Check if the last subtree is empty. - fn last_is_empty(&self) -> bool { - !self.buf.has_content() && !self.elem_stack.last().has_content() - } - /// Write the [`NameDisambiguationProperties`] that the first `cs:name` /// element used. Do not change if this is not the first `cs:name` element. /// Returns whether this was the first `cs:name` element. @@ -2554,10 +2510,6 @@ impl<'a, T: EntryLike> Context<'a, T> { variable: NumberVariable, silent: bool, ) -> Option> { - if !silent { - self.writing.usage_info.borrow_mut().last_mut().has_vars = true; - } - // Replace the citation label with citation number if necessary. if variable == NumberVariable::CitationNumber { if self.bibliography { @@ -2585,10 +2537,6 @@ impl<'a, T: EntryLike> Context<'a, T> { self.writing.prepare_variable_query(variable)?; let res = self.instance.resolve_number_variable(variable); - - if res.is_some() { - self.writing.usage_info.borrow_mut().last_mut().has_non_empty_vars = true; - } res } @@ -2601,10 +2549,6 @@ impl<'a, T: EntryLike> Context<'a, T> { variable: csl_taxonomy::StandardVariable, silent: bool, ) -> Option> { - if !silent { - self.writing.usage_info.borrow_mut().last_mut().has_vars = true; - } - // Replace the citation label with citation number if necessary. if variable == StandardVariable::CitationLabel { if self.bibliography { @@ -2633,9 +2577,6 @@ impl<'a, T: EntryLike> Context<'a, T> { self.writing.prepare_variable_query(variable)?; let res = self.instance.resolve_standard_variable(form, variable); - if res.is_some() { - self.writing.usage_info.borrow_mut().last_mut().has_non_empty_vars = true; - } res } @@ -2647,16 +2588,9 @@ impl<'a, T: EntryLike> Context<'a, T> { variable: csl_taxonomy::DateVariable, silent: bool, ) -> Option> { - if !silent { - self.writing.usage_info.borrow_mut().last_mut().has_vars = true; - } - self.writing.prepare_variable_query(variable)?; let res = self.instance.entry.resolve_date_variable(variable); - if res.is_some() { - self.writing.usage_info.borrow_mut().last_mut().has_non_empty_vars = true; - } res } @@ -2668,19 +2602,11 @@ impl<'a, T: EntryLike> Context<'a, T> { variable: csl_taxonomy::NameVariable, silent: bool, ) -> Vec> { - if !silent { - self.writing.usage_info.borrow_mut().last_mut().has_vars = true; - } - if self.writing.prepare_variable_query(variable).is_none() { return Vec::new(); } let res = self.instance.entry.resolve_name_variable(variable); - - if !res.is_empty() { - self.writing.usage_info.borrow_mut().last_mut().has_non_empty_vars = true; - } res } @@ -2766,9 +2692,6 @@ impl DisplayLoc { #[must_use = "case stack must be popped"] struct CaseIdx(NonZeroUsize); -#[must_use = "usage info stack must be popped"] -struct UsageInfoIdx(NonZeroUsize); - impl Write for Context<'_, T> { fn write_str(&mut self, s: &str) -> std::fmt::Result { self.push_str(s); @@ -2785,11 +2708,6 @@ struct UsageInfo { } impl UsageInfo { - /// Create a new usage info object. - fn new() -> Self { - Self::default() - } - /// Merge usage info with the info of a child. fn merge_child(self, child: Self) -> Self { Self { @@ -2799,6 +2717,14 @@ impl UsageInfo { has_non_empty_group: self.has_non_empty_group || child.has_non_empty_group, } } + + /// Whether a group with this usage info should be rendered. + fn should_render_group(&self) -> bool { + !self.has_vars + || (self.has_non_empty_vars + || self.has_used_macros + || self.has_non_empty_group) + } } /// Describes the bracket preference of a citation style. diff --git a/src/csl/rendering/mod.rs b/src/csl/rendering/mod.rs index bd710096..bbb6edeb 100644 --- a/src/csl/rendering/mod.rs +++ b/src/csl/rendering/mod.rs @@ -17,7 +17,7 @@ use crate::lang::{Case, SentenceCase, TitleCase}; use crate::types::{ChunkedString, Date, MaybeTyped, Numeric}; use super::taxonomy::EntryLike; -use super::{Context, ElemMeta, IbidState, SpecialForm}; +use super::{Context, ElemMeta, IbidState, SpecialForm, UsageInfo}; pub mod names; @@ -28,11 +28,13 @@ pub(crate) trait RenderCsl { fn render(&self, ctx: &mut Context); /// Check whether the element will render a given variable. fn will_render(&self, ctx: &mut Context, var: Variable) -> bool; + /// Check whether the element will print something and what. + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo); } impl RenderCsl for citationberg::Text { fn render(&self, ctx: &mut Context) { - let Some(target) = ResolvedTextTarget::compute(self, ctx) else { return }; + let Some(target) = ResolvedTextTarget::compute(self, ctx, false) else { return }; let depth = ctx.push_elem(self.formatting); // Only print affixes if this macro is not used out of its original context. @@ -89,21 +91,15 @@ impl RenderCsl for citationberg::Text { if matches!(var, NumberVariable::Page) => { // TODO: Remove this hack - ctx.push_str(&n.to_str().replace("-", "–")) + ctx.push_str(&n.to_str().replace('-', "–")) } NumberVariableResult::Regular(n) => ctx.push_str(&n.to_str()), NumberVariableResult::Transparent(n) => ctx.push_transparent(n), }, ResolvedTextTarget::Macro(mac) => { - let len = ctx.writing.len(); - for child in &mac.children { child.render(ctx); } - - if len < ctx.writing.len() { - ctx.writing.printed_non_empty_macro(); - } } ResolvedTextTarget::Term(s) => ctx.push_str(s), ResolvedTextTarget::Value(val) => ctx.push_str(val), @@ -136,7 +132,9 @@ impl RenderCsl for citationberg::Text { } fn will_render(&self, ctx: &mut Context, var: Variable) -> bool { - let Some(target) = ResolvedTextTarget::compute(self, ctx) else { return false }; + let Some(target) = ResolvedTextTarget::compute(self, ctx, false) else { + return false; + }; match target { ResolvedTextTarget::StandardVariable(s, _) => var == Variable::Standard(s), ResolvedTextTarget::NumberVariable(n, _) => var == Variable::Number(n), @@ -147,6 +145,54 @@ impl RenderCsl for citationberg::Text { ResolvedTextTarget::Value(_) => false, } } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + // If suppress_queried_variables is set to true, we need to perform a + // silent lookup, otherwise we need to perform a regular lookup. + let suppressing = ctx.writing.suppress_queried_variables; + + ctx.writing.stop_suppressing_queried_variables(); + let lookup_result = ResolvedTextTarget::compute(self, ctx, suppressing); + if suppressing { + ctx.writing.start_suppressing_queried_variables(); + } + let targets_variable = matches!(self.target, TextTarget::Variable { .. }); + + let Some(target) = lookup_result else { + return ( + false, + UsageInfo { has_vars: targets_variable, ..Default::default() }, + ); + }; + + match target { + ResolvedTextTarget::StandardVariable(_, s) if s.is_empty() => { + (false, UsageInfo { has_vars: true, ..Default::default() }) + } + ResolvedTextTarget::StandardVariable(_, _) + | ResolvedTextTarget::NumberVariable(_, _) => ( + true, + UsageInfo { + has_vars: true, + has_non_empty_vars: true, + ..Default::default() + }, + ), + ResolvedTextTarget::Macro(mac) => { + let mut will_print = false; + let mut info = UsageInfo::default(); + for child in &mac.children { + let (print, child_info) = child.will_have_info(ctx); + will_print |= print; + info = info.merge_child(child_info) + } + + (will_print, UsageInfo { has_used_macros: true, ..info }) + } + ResolvedTextTarget::Term(_) => (true, UsageInfo::default()), + ResolvedTextTarget::Value(v) => (!v.is_empty(), UsageInfo::default()), + } + } } enum ResolvedTextTarget<'a, 'b> { @@ -161,6 +207,7 @@ impl<'a, 'b> ResolvedTextTarget<'a, 'b> { fn compute( text: &'b citationberg::Text, ctx: &mut Context<'a, T>, + silent: bool, ) -> Option { match ctx.instance.kind { // If we are supposed to print a variable we need to return if this @@ -192,10 +239,10 @@ impl<'a, 'b> ResolvedTextTarget<'a, 'b> { match &text.target { TextTarget::Variable { var: Variable::Standard(var), form } => ctx - .resolve_standard_variable(*form, *var, false) + .resolve_standard_variable(*form, *var, silent) .map(|s| ResolvedTextTarget::StandardVariable(*var, s)), TextTarget::Variable { var: Variable::Number(var), .. } => ctx - .resolve_number_variable(*var, false) + .resolve_number_variable(*var, silent) .map(|n| ResolvedTextTarget::NumberVariable(*var, n)), TextTarget::Variable { .. } => None, TextTarget::Macro { name } => { @@ -269,6 +316,31 @@ impl RenderCsl for citationberg::Number { var == Variable::Number(self.variable) } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + if self.will_render(ctx, self.variable.into()) { + // If suppress_queried_variables is set to true, we need to perform a + // silent lookup, otherwise we need to perform a regular lookup. + let suppressing = ctx.writing.suppress_queried_variables; + ctx.writing.stop_suppressing_queried_variables(); + let lookup_result = ctx.resolve_number_variable(self.variable, suppressing); + + if suppressing { + ctx.writing.start_suppressing_queried_variables(); + } + + ( + lookup_result.is_some(), + UsageInfo { + has_vars: true, + has_non_empty_vars: lookup_result.is_some(), + ..Default::default() + }, + ) + } else { + (false, UsageInfo { has_vars: true, ..Default::default() }) + } + } } fn render_typed_num( @@ -309,11 +381,52 @@ fn render_page_range(range: std::ops::Range, ctx: &mut Contex .unwrap(); } +fn label_pluralization( + label: &citationberg::Label, + variable: NumberVariableResult, +) -> bool { + match label.label.plural { + LabelPluralize::Always => true, + LabelPluralize::Never => false, + LabelPluralize::Contextual => match variable { + NumberVariableResult::Regular(MaybeTyped::String(_)) => false, + NumberVariableResult::Regular(MaybeTyped::Typed(n)) => { + n.is_plural(label.variable.is_number_of_variable()) + } + NumberVariableResult::Transparent(_) => false, + }, + } +} + impl RenderCsl for citationberg::Label { fn render(&self, ctx: &mut Context) { + if !self.will_have_info(ctx).0 { + return; + } + + let Some(variable) = ctx.resolve_number_variable(self.variable, false) else { + return; + }; + + let depth = ctx.push_elem(citationberg::Formatting::default()); + let plural = label_pluralization(self, variable); + + let content = ctx + .term(Term::from(self.variable), self.label.form, plural) + .unwrap_or_default(); + + render_label_with_var(&self.label, ctx, content); + ctx.commit_elem(depth, None, Some(ElemMeta::Label)); + } + + fn will_render(&self, _ctx: &mut Context, _var: Variable) -> bool { + false + } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { match ctx.instance.kind { Some(SpecialForm::VarOnly(Variable::Number(n))) if self.variable != n => { - return + return (false, UsageInfo::default()); } Some( SpecialForm::VarOnly(_) @@ -321,7 +434,7 @@ impl RenderCsl for citationberg::Label { | SpecialForm::OnlyYearSuffix, ) => { if self.variable != NumberVariable::Locator { - return; + return (true, UsageInfo::default()); } } _ => {} @@ -336,36 +449,18 @@ impl RenderCsl for citationberg::Label { .locator .map_or(false, |l| l.0 == Locator::Custom) { - return; + return (false, UsageInfo::default()); } - let Some(variable) = ctx.resolve_number_variable(self.variable, false) else { - return; - }; - - let depth = ctx.push_elem(citationberg::Formatting::default()); - let plural = match self.label.plural { - LabelPluralize::Always => true, - LabelPluralize::Never => false, - LabelPluralize::Contextual => match variable { - NumberVariableResult::Regular(MaybeTyped::String(_)) => false, - NumberVariableResult::Regular(MaybeTyped::Typed(n)) => { - n.is_plural(self.variable.is_number_of_variable()) - } - NumberVariableResult::Transparent(_) => false, - }, - }; - - let content = ctx - .term(Term::from(self.variable), self.label.form, plural) - .unwrap_or_default(); - - render_label_with_var(&self.label, ctx, content); - ctx.commit_elem(depth, None, Some(ElemMeta::Label)); - } - - fn will_render(&self, _ctx: &mut Context, _var: Variable) -> bool { - false + if let Some(num) = ctx.resolve_number_variable(self.variable, false) { + let plural = label_pluralization(self, num); + ( + ctx.term(Term::from(self.variable), self.label.form, plural).is_some(), + UsageInfo::default(), + ) + } else { + (false, UsageInfo::default()) + } } } @@ -512,6 +607,32 @@ impl RenderCsl for citationberg::Date { Some(var) == self.variable.map(Variable::Date) } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + let suppressing = ctx.writing.suppress_queried_variables; + ctx.writing.stop_suppressing_queried_variables(); + let Some(variable) = self.variable else { + return (false, UsageInfo { has_vars: true, ..Default::default() }); + }; + + if suppressing { + ctx.writing.start_suppressing_queried_variables(); + } + + if !self.will_render(ctx, variable.into()) { + (false, UsageInfo::default()) + } else { + let has_non_empty_vars = ctx.resolve_date_variable(variable, false).is_some(); + ( + has_non_empty_vars, + UsageInfo { + has_vars: true, + has_non_empty_vars, + ..Default::default() + }, + ) + } + } } fn render_date_part( @@ -647,32 +768,54 @@ fn render_year_suffix_implicitly(ctx: &mut Context) { } } -impl RenderCsl for citationberg::Choose { - fn render(&self, ctx: &mut Context) { - for branch in self.branches() { - if branch.match_.test(BranchConditionIter::from_branch(branch, ctx)) { - render_with_delimiter(&branch.children, self.delimiter.as_deref(), ctx); - return; - } +fn choose_children( + choose: &citationberg::Choose, + ctx: &mut Context, + mut f: F, +) -> Option +where + F: FnMut(&[LayoutRenderingElement], &mut Context) -> R, +{ + for branch in choose.branches() { + if branch.match_.test(BranchConditionIter::from_branch(branch, ctx)) { + return Some(f(&branch.children, ctx)); } + } - if let Some(fallthrough) = &self.otherwise { - render_with_delimiter(&fallthrough.children, self.delimiter.as_deref(), ctx); - } + choose + .otherwise + .as_ref() + .map(|fallthrough| f(&fallthrough.children, ctx)) +} + +impl RenderCsl for citationberg::Choose { + fn render(&self, ctx: &mut Context) { + choose_children(self, ctx, |children, ctx| { + render_with_delimiter(children, self.delimiter.as_deref(), ctx); + }); } fn will_render(&self, ctx: &mut Context, var: Variable) -> bool { - for branch in self.branches() { - if branch.match_.test(BranchConditionIter::from_branch(branch, ctx)) { - return branch.children.iter().any(|c| c.will_render(ctx, var)); + choose_children(self, ctx, |children, ctx| { + children.iter().any(|c| c.will_render(ctx, var)) + }) + .unwrap_or_default() + } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + choose_children(self, ctx, |children, ctx| { + let mut info = UsageInfo::default(); + let mut will_print = false; + + for child in children { + let (print, child_info) = child.will_have_info(ctx); + will_print |= print; + info = info.merge_child(child_info); } - } - if let Some(fallthrough) = &self.otherwise { - fallthrough.children.iter().any(|c| c.will_render(ctx, var)) - } else { - false - } + (will_print, info) + }) + .unwrap_or_else(|| (false, UsageInfo::default())) } } @@ -681,11 +824,16 @@ fn render_with_delimiter( delimiter: Option<&str>, ctx: &mut Context, ) { - let mut last_empty = true; + let mut first = true; let mut loc = None; for child in children { - if !last_empty { + // Do not render the child if it will not print anything. + if !child.will_have_info(ctx).0 { + continue; + } + + if !first { if let Some(delim) = delimiter { let prev_loc = std::mem::take(&mut loc); @@ -710,20 +858,12 @@ fn render_with_delimiter( LayoutRenderingElement::Group(_group) => _group.render(ctx), } - last_empty = ctx.writing.last_is_empty(); - if last_empty { - ctx.discard_elem(pos); - } else { - ctx.commit_elem(pos, None, None); - } + first = false; + ctx.commit_elem(pos, None, None); } if let Some(loc) = loc { - if last_empty { - ctx.discard_elem(loc); - } else { - ctx.commit_elem(loc, None, None); - } + ctx.commit_elem(loc, None, None); } } @@ -966,32 +1106,48 @@ impl<'a, 'b, T: EntryLike> Iterator for BranchConditionIter<'a, 'b, T> { impl RenderCsl for citationberg::Group { fn render(&self, ctx: &mut Context) { - let info = ctx.writing.push_usage_info(); let idx = ctx.push_elem(self.to_formatting()); let affixes = self.to_affixes(); let affix_loc = ctx.apply_prefix(&affixes); + let info = self.will_have_info(ctx).1; render_with_delimiter(&self.children, self.delimiter.as_deref(), ctx); ctx.apply_suffix(&affixes, affix_loc); - let info = ctx.writing.pop_usage_info(info); - if info.has_vars - && (!info.has_non_empty_vars - && !info.has_used_macros - && !info.has_non_empty_group) - { - ctx.discard_elem(idx); - } else { + if info.should_render_group() { ctx.commit_elem(idx, self.display, None); - ctx.writing.printed_non_empty_group() + } else { + ctx.discard_elem(idx); } } fn will_render(&self, ctx: &mut Context, var: Variable) -> bool { self.children.iter().any(|e| e.will_render(ctx, var)) } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + let mut info = UsageInfo::default(); + let mut will_print = false; + + for child in &self.children { + let (print, child_info) = child.will_have_info(ctx); + will_print |= print; + info = info.merge_child(child_info); + + match child { + citationberg::LayoutRenderingElement::Group(_) if print => { + info.has_non_empty_group = true; + } + _ => {} + } + } + + will_print &= info.should_render_group(); + + (will_print, info) + } } impl RenderCsl for citationberg::LayoutRenderingElement { @@ -1032,6 +1188,26 @@ impl RenderCsl for citationberg::LayoutRenderingElement { } } } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + match self { + citationberg::LayoutRenderingElement::Text(text) => text.will_have_info(ctx), + citationberg::LayoutRenderingElement::Number(num) => num.will_have_info(ctx), + citationberg::LayoutRenderingElement::Label(label) => { + label.will_have_info(ctx) + } + citationberg::LayoutRenderingElement::Date(date) => date.will_have_info(ctx), + citationberg::LayoutRenderingElement::Names(names) => { + names.will_have_info(ctx) + } + citationberg::LayoutRenderingElement::Choose(choose) => { + choose.will_have_info(ctx) + } + citationberg::LayoutRenderingElement::Group(group) => { + group.will_have_info(ctx) + } + } + } } impl RenderCsl for citationberg::Layout { @@ -1046,6 +1222,19 @@ impl RenderCsl for citationberg::Layout { fn will_render(&self, ctx: &mut Context, var: Variable) -> bool { self.elements.iter().any(|e| e.will_render(ctx, var)) } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + let mut info = UsageInfo::default(); + let mut will_print = false; + + for child in &self.elements { + let (print, child_info) = child.will_have_info(ctx); + will_print |= print; + info = info.merge_child(child_info); + } + + (will_print, info) + } } impl RenderCsl for citationberg::RenderingElement { @@ -1062,6 +1251,13 @@ impl RenderCsl for citationberg::RenderingElement { citationberg::RenderingElement::Other(o) => o.will_render(ctx, var), } } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + match self { + citationberg::RenderingElement::Layout(l) => l.will_have_info(ctx), + citationberg::RenderingElement::Other(o) => o.will_have_info(ctx), + } + } } impl From for Case { diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index a4fae709..7016e119 100644 --- a/src/csl/rendering/names.rs +++ b/src/csl/rendering/names.rs @@ -11,7 +11,7 @@ use citationberg::{ use citationberg::{DisambiguationRule, TermForm}; use crate::csl::taxonomy::EntryLike; -use crate::csl::{Context, DisambiguateState, ElemMeta, SpecialForm}; +use crate::csl::{Context, DisambiguateState, ElemMeta, SpecialForm, UsageInfo}; use crate::types::Person; use super::{render_label_with_var, RenderCsl}; @@ -170,6 +170,46 @@ impl NameDisambiguationProperties { } } +fn renders_given_special_form( + names: &Names, + ctx: &Context, + is_empty: bool, +) -> bool { + match &ctx.instance.kind { + Some(SpecialForm::VarOnly(Variable::Name(var))) => { + // Skip if none of the variables are the author and the supplement does not contain the author either. + let contains_v = names.variable.iter().any(|v| var == v); + let substitute_will_render_v = is_empty + && names.substitute().map_or(false, |s| { + s.children + .iter() + .filter_map(|c| match c { + LayoutRenderingElement::Names(n) => Some(n.variable.iter()), + _ => None, + }) + .flatten() + .any(|v| var == v) + }); + if !contains_v && !substitute_will_render_v { + return false; + } + } + Some( + SpecialForm::VarOnly(_) + | SpecialForm::OnlyFirstDate + | SpecialForm::OnlyYearSuffix, + ) => return false, + Some(SpecialForm::SuppressAuthor) => { + if names.variable.iter().any(|v| &NameVariable::Author == v) { + return false; + } + } + None => {} + } + + true +} + impl RenderCsl for Names { fn render(&self, ctx: &mut Context) { // The editor and translator variables need to be merged if they are @@ -212,38 +252,9 @@ impl RenderCsl for Names { // Write the substitute if all variables are empty. let is_empty = people.iter().all(|(p, _)| p.is_empty()); // Suppress this variable if we are in a special form. - match &ctx.instance.kind { - Some(SpecialForm::VarOnly(Variable::Name(var))) => { - // Skip if none of the variables are the author and the supplement does not contain the author either. - let contains_v = self.variable.iter().any(|v| var == v); - let substitute_will_render_v = is_empty - && self.substitute().map_or(false, |s| { - s.children - .iter() - .filter_map(|c| match c { - LayoutRenderingElement::Names(n) => { - Some(n.variable.iter()) - } - _ => None, - }) - .flatten() - .any(|v| var == v) - }); - if !contains_v && !substitute_will_render_v { - return; - } - } - Some( - SpecialForm::VarOnly(_) - | SpecialForm::OnlyFirstDate - | SpecialForm::OnlyYearSuffix, - ) => return, - Some(SpecialForm::SuppressAuthor) => { - if self.variable.iter().any(|v| &NameVariable::Author == v) { - return; - } - } - None => {} + if !renders_given_special_form(self, ctx, is_empty) { + ctx.writing.pop_name_options(); + return; } if is_empty { @@ -391,6 +402,45 @@ impl RenderCsl for Names { false } + + fn will_have_info(&self, ctx: &mut Context) -> (bool, UsageInfo) { + let suppressing = ctx.writing.suppress_queried_variables; + ctx.writing.stop_suppressing_queried_variables(); + + let is_empty = self + .variable + .iter() + .all(|v| ctx.resolve_name_variable(*v, false).is_empty()); + + let substitute_info = self + .substitute() + .iter() + .flat_map(|s| s.children.iter()) + .map(|c| c.will_have_info(ctx)) + .fold((false, UsageInfo::default()), |(a, b), (c, d)| { + (a || c, b.merge_child(d)) + }); + + if suppressing { + ctx.writing.start_suppressing_queried_variables(); + } + + let visible = renders_given_special_form(self, ctx, is_empty) + && (!is_empty || substitute_info.0); + + ( + visible, + if is_empty { + substitute_info.1 + } else { + UsageInfo { + has_vars: true, + has_non_empty_vars: !is_empty, + ..UsageInfo::default() + } + }, + ) + } } #[derive(Debug, Clone, Copy)] diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 0d4e601d..740f3c38 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -2,7 +2,7 @@ # The test fails if a test case in this file fails or if a test case # that is not in this file passes. # -# This file is automatically generated by running `cargo test --test citeproc --features csl-json -- write_passing `. +# This file is automatically generated by running `cargo test --test citeproc --features csl-json -- write_passing --ignored`. affix_InterveningEmpty affix_TextNodeWithMacro @@ -129,6 +129,7 @@ disambiguate_YearSuffixWithEtAlSubsequent flipflop_OrphanQuote form_TitleTestNoLongFalse fullstyles_APA +group_SuppressValueWithEmptySubgroup integration_CitationSort integration_CitationSortTwice label_CompactNamesAfterFullNames