Skip to content

Commit

Permalink
Add cite grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
reknih committed Oct 18, 2023
1 parent f981729 commit 9d1135e
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 36 deletions.
12 changes: 12 additions & 0 deletions src/csl/elem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ impl Elem {
Ok(())
}

pub(super) fn to_string(&self, format: BufWriteFormat) -> String {
let mut buf = String::new();
self.write_buf(&mut buf, format).unwrap();
buf
}

pub(super) fn is_empty(&self) -> bool {
if self.children.is_empty() {
true
Expand Down Expand Up @@ -186,6 +192,12 @@ impl ElemChildren {
}
Ok(())
}

pub(super) fn to_string(&self, format: BufWriteFormat) -> String {
let mut buf = String::new();
self.write_buf(&mut buf, format).unwrap();
buf
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down
108 changes: 84 additions & 24 deletions src/csl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct SpeculativeItemRender<'a> {
cite_props: CiteProperties<'a>,
checked_disambiguate: bool,
first_name: Option<NameDisambiguationProperties>,
delim_override: Option<&'a str>,
}

#[derive(Debug, Hash, PartialEq, Eq)]
Expand Down Expand Up @@ -76,7 +77,6 @@ impl<'a> BibliographyDriver<'a> {
}

let non_empty: Vec<_> = res.into_iter().filter(|c| c.has_content()).collect();
// TODO in-cite disambiguation.

let formatting =
Formatting::default().apply(style.csl.citation.layout.to_formatting());
Expand Down Expand Up @@ -210,6 +210,7 @@ impl<'a> BibliographyDriver<'a> {
cite_props,
checked_disambiguate: ctx.writing.checked_disambiguate,
first_name: ctx.writing.first_name.clone(),
delim_override: None,
rendered: ctx.flush(),
});

Expand Down Expand Up @@ -240,11 +241,9 @@ impl<'a> BibliographyDriver<'a> {

for group in ambiguous.iter() {
// 2a. Name Disambiguation loop
if bib_style.csl.citation.disambiguate_add_givenname {
disambiguate_names(&res, group, |entry, state| {
mark(&mut rerender, entry, state)
});
}
disambiguate_names(&res, group, |entry, state| {
mark(&mut rerender, entry, state)
});

// Do not try other methods if the previous method succeeded.
if !rerender.is_empty() {
Expand Down Expand Up @@ -282,28 +281,82 @@ impl<'a> BibliographyDriver<'a> {
}
}

// 3. Group adjacent citations.
for cite in res.iter_mut() {
// This map contains the last index of each entry with this names
// elem.
let mut map: HashMap<String, usize> = HashMap::new();

for i in 0..cite.items.len() {
let Some(delim) =
cite.request.style.citation.cite_group_delimiter.as_deref().or_else(
|| {
cite.request
.style
.citation
.collapse
.is_some()
.then_some(Citation::DEFAULT_CITE_GROUP_DELIMITER)
},
)
else {
continue;
};

let Some(name_elem) = cite.items[i]
.rendered
.get_meta(ElemMeta::Names)
.map(|e| e.to_string(BufWriteFormat::Plain))
else {
continue;
};

let mut prev = None;
let target = *map
.entry(name_elem)
.and_modify(|i| {
prev = Some(*i);
*i += 1
})
.or_insert(i);

let mut pos = i;
while target < pos {
cite.items.swap(pos, pos - 1);
pos -= 1;
}

cite.items[target].delim_override = Some(delim);
if let Some(prev) = prev {
cite.items[prev].delim_override = None;
}
}
}

// 4. Render citations with locator.
// 5. Collapse grouped citations.
// 6. Add affixes.

for render in res.iter() {
print!("{}", render.request.prefix().unwrap_or_default());
let mut first = true;
for item in render.items.iter() {
if !first {
if let Some(delim) = &render.request.style.citation.layout.delimiter {
for (i, item) in render.items.iter().enumerate() {
if i != 0 {
if let Some(delim) = render
.items
.get(i - 1)
.and_then(|i| i.delim_override)
.or(render.request.style.citation.layout.delimiter.as_deref())
{
print!("{}", delim);
}
}

let mut buf = String::new();
item.rendered.write_buf(&mut buf, BufWriteFormat::Plain).unwrap();
print!("{}", buf);
first = false;
}
println!("{}", render.request.suffix().unwrap_or_default());
}

// 3. Group adjacent citations.
// 3a. Collapse grouped citations.
// 4. Add affixes.

todo!()
}
}
Expand All @@ -326,13 +379,23 @@ fn disambiguate_names<F>(
continue;
}

if let Some(name_props) = &item.first_name {
let name_props_slot = if let DisambiguateState::NameDisambiguation(n) =
&item.cite_props.speculative.disambiguation
{
Some(n)
} else {
item.first_name.as_ref()
};

if let Some(name_props) = name_props_slot {
let mut name_props = name_props.clone();
name_props.disambiguate(
if name_props.disambiguate(
style.citation.disambiguate_add_givenname,
style.citation.givenname_disambiguation_rule,
style.citation.disambiguate_add_names,
);
mark(item.entry, DisambiguateState::NameDisambiguation(name_props))
) {
mark(item.entry, DisambiguateState::NameDisambiguation(name_props))
}
}
}
}
Expand Down Expand Up @@ -432,8 +495,7 @@ fn find_ambiguous_sets(cites: &[SpeculativeCiteRender]) -> Vec<AmbiguousGroup> {
continue;
}

let mut buf = String::new();
item.rendered.write_buf(&mut buf, BufWriteFormat::Plain).unwrap();
let buf = item.rendered.to_string(BufWriteFormat::Plain);
match map.entry(buf) {
HmEntry::Occupied(entry) => match *entry.get() {
PotentialDisambiguation::Single(pos) => {
Expand Down Expand Up @@ -1277,8 +1339,6 @@ impl DisambiguateState {
(Self::Choose, other) => other,
(self_, Self::Choose) => self_,
(Self::YearSuffix(a), Self::YearSuffix(b)) => Self::YearSuffix(a.max(b)),
(Self::YearSuffix(_), other) => other,
(self_, Self::YearSuffix(_)) => self_,
}
}
}
Expand Down
59 changes: 47 additions & 12 deletions src/csl/rendering/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,32 @@ pub struct NameDisambiguationProperties {
impl NameDisambiguationProperties {
/// Disambiguate the name further. Return none if the name cannot be
/// disambiguated further.
pub fn disambiguate(&mut self, rule: DisambiguationRule, add_names: bool) -> bool {
pub fn disambiguate(
&mut self,
may_upgrade: bool,
rule: DisambiguationRule,
add_names: bool,
) -> bool {
let allow_full_first_name = rule.allows_full_first_names();

for list in self.name_forms.iter_mut() {
let mut idx = 0;
// First try to step an item that is `Some`.
for (i, form) in list.iter_mut().enumerate() {
if let Some(form) = form {
if let Some(new_form) = form.disambiguate(allow_full_first_name) {
*form = new_form;
return true;
}

if !rule.allows_multiple_names() {
return false;
}
if may_upgrade {
// First try to step an item that is `Some`.
for (i, form) in list.iter_mut().enumerate() {
if let Some(form) = form {
if let Some(new_form) = form.disambiguate(allow_full_first_name) {
*form = new_form;
return true;
}

idx = i;
if !rule.allows_multiple_names() {
return false;
}

idx = i;
}
}
}

Expand All @@ -135,6 +143,29 @@ impl NameDisambiguationProperties {
false
}

/// Disambuiguate a list of identical names.
pub fn disambiguate_list(&mut self, variable: NameVariable, items: &[usize]) -> bool {
let mut change = false;
for &idx in items {
let Some(outer) = self.variables.iter().position(|v| v == &variable) else {
continue;
};

let form = &mut self.name_forms[outer][idx];
if let Some(form) = form {
if let Some(new_form) = form.disambiguate(true) {
*form = new_form;
change = true;
}
} else {
*form = Some(self.default_name_form);
change = true;
}
}

change
}

/// Return the more disambiguated form of the name.
pub fn max(self, other: Self) -> Self {
let count_some = |x: &Self| {
Expand Down Expand Up @@ -283,6 +314,10 @@ impl RenderCsl for Names {
}
}

// TODO Compare each elem with a name meta and run
// [`NameDisambiguationProperties::disambiguate_list`] on the identical
// pairs. Rerender if necessary.

ctx.apply_suffix(&self.affixes, affix_loc);
ctx.commit_elem(depth, self.display, Some(ElemMeta::Names));
ctx.writing.pop_name_options();
Expand Down

0 comments on commit 9d1135e

Please sign in to comment.