diff --git a/Cargo.toml b/Cargo.toml index 3f38f2fc..10731d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ csl-json = ["citationberg/json"] [dependencies] citationberg = { git = "https://github.com/typst/citationberg.git", rev = "ad52765" } +citationberg = { git = "https://github.com/typst/citationberg.git", rev = "61ca6a7fcc48365f805e521cc8bc1f8f679ff372" } indexmap = { version = "2.0.2", features = ["serde"] } numerals = "0.1.4" paste = "1.0.14" diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 362afa9b..62062757 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -83,12 +83,9 @@ impl<'a, T: EntryLike> BibliographyDriver<'a, T> { /// Create a new citation with the given items. pub fn citation(&mut self, mut req: CitationRequest<'a, T>) { - let style = req.style(); - for (i, item) in req.items.iter_mut().enumerate() { item.initial_idx = i; } - style.sort(&mut req.items, style.csl.citation.sort.as_ref(), req.locale.as_ref()); self.citations.push(req); } } @@ -96,7 +93,7 @@ impl<'a, T: EntryLike> BibliographyDriver<'a, T> { /// Implementations for finishing the bibliography. impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T> { /// Render the bibliography. - pub fn finish(self, request: BibliographyRequest<'_>) -> Rendered { + pub fn finish(mut self, request: BibliographyRequest<'_>) -> Rendered { // 1. Assign citation numbers by bibliography ordering or by citation // order and render them a first time without their locators. let bib_style = request.style(); @@ -115,6 +112,7 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T> &mut entries, bib_style.csl.bibliography.as_ref().and_then(|b| b.sort.as_ref()), request.locale.as_ref(), + |_| 0, ); let citation_number = |item: &T| { entries.iter().position(|e| e.entry == item).expect("entry not found") @@ -124,10 +122,17 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T> let mut res: Vec> = Vec::new(); let mut last_cite: Option<&CitationItem> = None; - for citation in &self.citations { - let items = &citation.items; + for citation in &mut self.citations { let style = citation.style(); + style.sort( + &mut citation.items, + style.csl.citation.sort.as_ref(), + citation.locale.as_ref(), + &citation_number, + ); + + let items = &citation.items; let mut renders: Vec> = Vec::new(); for item in items.iter() { @@ -516,7 +521,12 @@ pub fn standalone_citation( mut req: CitationRequest<'_, T>, ) -> ElemChildren { let style = req.style(); - style.sort(&mut req.items, style.csl.citation.sort.as_ref(), req.locale.as_ref()); + style.sort( + &mut req.items, + style.csl.citation.sort.as_ref(), + req.locale.as_ref(), + |_| 0, + ); let mut res = vec![]; let mut all_hidden = true; for item in req.items { @@ -846,7 +856,11 @@ fn find_ambiguous_sets( fn collapse_items<'a, T: EntryLike>(cite: &mut SpeculativeCiteRender<'a, '_, T>) { let style = &cite.request.style; - let after_collapse_delim = style.citation.after_collapse_delimiter.as_deref(); + let after_collapse_delim = style + .citation + .after_collapse_delimiter + .as_deref() + .or(style.citation.layout.delimiter.as_deref()); let group_delimiter = style.citation.cite_group_delimiter.as_deref(); @@ -854,24 +868,36 @@ fn collapse_items<'a, T: EntryLike>(cite: &mut SpeculativeCiteRender<'a, '_, T>) Some(Collapse::CitationNumber) => { // Option with the start and end of the range. let mut range_start: Option<(usize, usize)> = None; + let mut just_collapsed = false; + + let end_range = |items: &mut [SpeculativeItemRender<'a, T>], + range_start: &mut Option<(usize, usize)>, + just_collapsed: &mut bool| { + let use_after_collapse_delim = *just_collapsed; + *just_collapsed = false; + + if let &mut Some((start, end)) = range_start { + // If the previous citation range was collapsed, use the + // after-collapse delimiter before the next item. + if use_after_collapse_delim { + items[start].delim_override = after_collapse_delim; + } - let end_range = - |items: &mut [SpeculativeItemRender<'a, T>], - range_start: &mut Option<(usize, usize)>| { - if let &mut Some((start, end)) = range_start { - // There should be at least three items in the range. - if start + 1 < end { - items[end].delim_override = - after_collapse_delim.or(Some("–")); - - for item in &mut items[start + 1..end] { - item.hidden = true; - } + // There should be at least three items in the range to + // collapse. + if start + 1 < end { + items[end].delim_override = Some("–"); + + for item in &mut items[start + 1..end] { + item.hidden = true; } + + *just_collapsed = true; } + } - *range_start = None; - }; + *range_start = None; + }; for i in 0..cite.items.len() { let citation_number = { @@ -881,7 +907,7 @@ fn collapse_items<'a, T: EntryLike>(cite: &mut SpeculativeCiteRender<'a, '_, T>) if item.hidden || item.rendered.get_meta(ElemMeta::CitationNumber).is_none() { - end_range(&mut cite.items, &mut range_start); + end_range(&mut cite.items, &mut range_start, &mut just_collapsed); continue; } @@ -904,18 +930,15 @@ fn collapse_items<'a, T: EntryLike>(cite: &mut SpeculativeCiteRender<'a, '_, T>) range_start = Some((start, i)); } _ => { - end_range(&mut cite.items, &mut range_start); + end_range(&mut cite.items, &mut range_start, &mut just_collapsed); range_start = Some((i, i)); } } } - end_range(&mut cite.items, &mut range_start); + end_range(&mut cite.items, &mut range_start, &mut just_collapsed); } Some(Collapse::Year | Collapse::YearSuffix | Collapse::YearSuffixRanged) => { - let after_collapse_delim = - after_collapse_delim.or(style.citation.layout.delimiter.as_deref()); - // Index of where the current group started and the group we are // currently in. let mut group_idx: Option<(usize, usize)> = None; diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index a0113f5e..3c936750 100644 --- a/src/csl/rendering/names.rs +++ b/src/csl/rendering/names.rs @@ -654,7 +654,6 @@ fn write_name( if name.given_name.is_some() { ctx.push_str(sort_sep); - ctx.ensure_space(); let idx = ctx.push_format(first_format); let cidx = ctx.push_case(first_case); @@ -680,7 +679,6 @@ fn write_name( if let Some(suffix) = &name.suffix { ctx.push_str(sort_sep); - ctx.ensure_space(); ctx.push_str(suffix); } }; @@ -704,7 +702,6 @@ fn write_name( if name.given_name.is_some() { ctx.push_str(sort_sep); - ctx.ensure_space(); let idx = ctx.push_format(first_format); let cidx = ctx.push_case(first_case); @@ -735,7 +732,6 @@ fn write_name( if let Some(suffix) = &name.suffix { ctx.push_str(sort_sep); - ctx.ensure_space(); ctx.push_str(suffix); } }; diff --git a/src/csl/sort.rs b/src/csl/sort.rs index 16826fcc..bcf9380a 100644 --- a/src/csl/sort.rs +++ b/src/csl/sort.rs @@ -27,10 +27,10 @@ impl<'a> StyleContext<'a> { SortKey::Variable { variable: Variable::Standard(s), .. } => { let a = InstanceContext::sort_instance(a, a_idx) .resolve_standard_variable(LongShortForm::default(), *s) - .map(|s| s.to_string()); + .map(|s| s.to_string().to_lowercase()); let b = InstanceContext::sort_instance(b, b_idx) .resolve_standard_variable(LongShortForm::default(), *s) - .map(|s| s.to_string()); + .map(|s| s.to_string().to_lowercase()); a.cmp(&b) } @@ -137,12 +137,20 @@ impl<'a> StyleContext<'a> { cites: &mut [CitationItem], sort: Option<&Sort>, term_locale: Option<&LocaleCode>, + citation_number: impl Fn(&T) -> usize, ) { if let Some(sort) = sort { cites.sort_by(|a, b| { let mut ordering = Ordering::Equal; for key in &sort.keys { - ordering = self.cmp_entries(a, 0, b, 0, key, term_locale); + ordering = self.cmp_entries( + a, + citation_number(a.entry), + b, + citation_number(b.entry), + key, + term_locale, + ); if ordering != Ordering::Equal { break; } diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 0853321c..c10d7431 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -262,6 +262,7 @@ nameattr_NameFormOnNamesInCitation nameattr_NameFormOnStyleInCitation nameattr_NamesDelimiterOnBibliographyInCitation nameattr_NamesDelimiterOnNamesInCitation +nameattr_SortSeparatorOnBibliographyInCitation nameattr_SortSeparatorOnCitationInCitation nameattr_SortSeparatorOnNamesInCitation nameattr_SortSeparatorOnStyleInCitation @@ -291,6 +292,7 @@ position_TrueInCitation punctuation_DateStripPeriods punctuation_DoNotSuppressColonAfterPeriod punctuation_NoSuppressOfPeriodBeforeSemicolon +sort_CaseInsensitiveCitation sort_Citation sort_CitationSecondaryKey sort_CiteGroupDelimiter @@ -302,6 +304,7 @@ sort_DateVariableMixedElementsDescendingB sort_LatinUnicode sort_LocalizedDateLimitedParts sort_TestInheritance +sortseparator_SortSeparatorEmpty substitute_RepeatedNamesOk substitute_SubstituteOnlyOnceString substitute_SubstituteOnlyOnceTerm diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 1f226422..9395d13b 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -439,6 +439,18 @@ fn test_single_file() { assert!(test_file(case, &locales, || path.display())); } +#[test] +fn test_local_files() { + let locales = locales(); + let test_path = PathBuf::from("tests/local"); + + for path in iter_files_with_name(&test_path, "txt", |_| true) { + let case = build_case(&std::fs::read_to_string(&path).unwrap()); + assert!(can_test(&case, || path.display(), true)); + assert!(test_file(case, &locales, || path.display())); + } +} + fn build_case(s: &str) -> TestCase { let mut s = Scanner::new(s); let mut builder = TestCaseBuilder::new(); diff --git a/tests/local/collapse_CitationNumberRangesSeparated.txt b/tests/local/collapse_CitationNumberRangesSeparated.txt new file mode 100644 index 00000000..39cd5b3a --- /dev/null +++ b/tests/local/collapse_CitationNumberRangesSeparated.txt @@ -0,0 +1,154 @@ +>>===== MODE =====>> +citation +<<===== MODE =====<< + +Simplified from collapse_CitationNumberRangesInsert.txt + + +>>===== RESULT =====>> +[1–8] +[1–3;5] +[4,6–8] +[2,3,6] +<<===== RESULT =====<< + +>>===== CITATION-ITEMS =====>> +[ + [ + { + "id": "ITEM-1" + }, + { + "id": "ITEM-2" + }, + { + "id": "ITEM-3" + }, + { + "id": "ITEM-4" + }, + { + "id": "ITEM-5" + }, + { + "id": "ITEM-6" + }, + { + "id": "ITEM-7" + }, + { + "id": "ITEM-8" + } + ], + [ + { + "id": "ITEM-1" + }, + { + "id": "ITEM-2" + }, + { + "id": "ITEM-3" + }, + { + "id": "ITEM-5" + } + ], + [ + { + "id": "ITEM-4" + }, + { + "id": "ITEM-6" + }, + { + "id": "ITEM-7" + }, + { + "id": "ITEM-8" + } + ], + [ + { + "id": "ITEM-2" + }, + { + "id": "ITEM-3" + }, + { + "id": "ITEM-6" + } + ] +] +<<===== CITATION-ITEMS =====<< + + +>>===== CSL =====>> + +<<===== CSL =====<< + + +>>===== INPUT =====>> +[ + { + "id": "ITEM-1", + "title": "Paper 1", + "type": "book" + }, + { + "id": "ITEM-2", + "title": "Paper 2", + "type": "book" + }, + { + "id": "ITEM-3", + "title": "Paper 3", + "type": "book" + }, + { + "id": "ITEM-4", + "title": "Paper 4", + "type": "book" + }, + { + "id": "ITEM-5", + "title": "Paper 5", + "type": "book" + }, + { + "id": "ITEM-6", + "title": "Paper 6", + "type": "book" + }, + { + "id": "ITEM-7", + "title": "Paper 7", + "type": "book" + }, + { + "id": "ITEM-8", + "title": "Paper 8", + "type": "book" + } +] +<<===== INPUT =====<< + + +>>===== VERSION =====>> +1.0 +<<===== VERSION =====<< diff --git a/tests/local/collapse_CitationNumberRangesSort.txt b/tests/local/collapse_CitationNumberRangesSort.txt new file mode 100644 index 00000000..88b42c41 --- /dev/null +++ b/tests/local/collapse_CitationNumberRangesSort.txt @@ -0,0 +1,98 @@ +>>===== MODE =====>> +citation +<<===== MODE =====<< + +Simplified from collapse_CitationNumberRangesInsert.txt + + +>>===== RESULT =====>> +[1]–[4] +[1]–[4] +<<===== RESULT =====<< + +>>===== CITATION-ITEMS =====>> +[ + [ + { + "id": "ITEM-1" + }, + { + "id": "ITEM-2" + }, + { + "id": "ITEM-3" + }, + { + "id": "ITEM-4" + } + ], + [ + { + "id": "ITEM-2" + }, + { + "id": "ITEM-1" + }, + { + "id": "ITEM-4" + }, + { + "id": "ITEM-3" + } + ] +] +<<===== CITATION-ITEMS =====<< + + +>>===== CSL =====>> + +<<===== CSL =====<< + + +>>===== INPUT =====>> +[ + { + "id": "ITEM-1", + "title": "Paper 1", + "type": "book" + }, + { + "id": "ITEM-2", + "title": "Paper 2", + "type": "book" + }, + { + "id": "ITEM-3", + "title": "Paper 3", + "type": "book" + }, + { + "id": "ITEM-4", + "title": "Paper 4", + "type": "book" + } +] +<<===== INPUT =====<< + + +>>===== VERSION =====>> +1.0 +<<===== VERSION =====<<