From 78bb4784f33241df4d730f6cffb017292638c87f Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:41:02 -0300 Subject: [PATCH 1/5] Fix CI (#197) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ae6983c..ac18748 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ archive = ["ciborium"] csl-json = ["citationberg/json"] [dependencies] -citationberg = { git = "https://github.com/typst/citationberg.git", rev = "858782e" } +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" From af287f853e97ad7d6ee8cc26fe0e582a98702187 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:46:02 -0300 Subject: [PATCH 2/5] Don't force space after sort separator (#195) --- src/csl/rendering/names.rs | 4 ---- tests/citeproc-pass.txt | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index a0113f5..3c93675 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/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 0853321..fcbbe72 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 @@ -302,6 +303,7 @@ sort_DateVariableMixedElementsDescendingB sort_LatinUnicode sort_LocalizedDateLimitedParts sort_TestInheritance +sortseparator_SortSeparatorEmpty substitute_RepeatedNamesOk substitute_SubstituteOnlyOnceString substitute_SubstituteOnlyOnceTerm From f70041fd6afe18e7e0145199a8aeaa2f8efcc63e Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Tue, 6 Aug 2024 16:29:34 +0200 Subject: [PATCH 3/5] Fix sorting citation by citation number (#168) * Fix sorting citation by citation number We need to defer sorting the citations until the citation numbers are assigned. * Add local test for sorting by citation number Citation number sorting is coverd in the test suite only by CITATIONS tests which we do not support. This commit adds support for local test cases and adapts one such test to use CITATION-ITEMS. * use generics for citation number * move local tests to tests/local --------- Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com> --- src/csl/mod.rs | 24 +++-- src/csl/sort.rs | 10 +- tests/citeproc.rs | 12 +++ .../collapse_CitationNumberRangesSort.txt | 98 +++++++++++++++++++ 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 tests/local/collapse_CitationNumberRangesSort.txt diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 362afa9..dba7403 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 { diff --git a/src/csl/sort.rs b/src/csl/sort.rs index 16826fc..1c8ab83 100644 --- a/src/csl/sort.rs +++ b/src/csl/sort.rs @@ -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.rs b/tests/citeproc.rs index 1f22642..9395d13 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_CitationNumberRangesSort.txt b/tests/local/collapse_CitationNumberRangesSort.txt new file mode 100644 index 0000000..88b42c4 --- /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 =====<< From 7f8c73615c25fc538e8cc7e4b63b44f1676648cf Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:42:26 -0300 Subject: [PATCH 4/5] Ensure sorting items by standard variable is case-insensitive (#198) --- src/csl/sort.rs | 4 ++-- tests/citeproc-pass.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/csl/sort.rs b/src/csl/sort.rs index 1c8ab83..bcf9380 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) } diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index fcbbe72..c10d743 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -292,6 +292,7 @@ position_TrueInCitation punctuation_DateStripPeriods punctuation_DoNotSuppressColonAfterPeriod punctuation_NoSuppressOfPeriodBeforeSemicolon +sort_CaseInsensitiveCitation sort_Citation sort_CitationSecondaryKey sort_CiteGroupDelimiter From 954962eb314eb401c2cae99b09d6f5f1912701fc Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Wed, 7 Aug 2024 23:56:43 -0400 Subject: [PATCH 5/5] Fix formatting of collapsed numeric citations (#176) * Update preset styles * Add failing test for #154 * Fix formatting of collapsed numeric citations * Revert changes to archive * Refactor collapse_items * Replace collapse test with local version * Remove styles/numeric.csl * simplify test * slightly improve collapse code * remove redundant test import * wrap comment at 80 columns --- src/csl/mod.rs | 55 ++++--- ...collapse_CitationNumberRangesSeparated.txt | 154 ++++++++++++++++++ 2 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 tests/local/collapse_CitationNumberRangesSeparated.txt diff --git a/src/csl/mod.rs b/src/csl/mod.rs index dba7403..6206275 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -856,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(); @@ -864,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 = { @@ -891,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; } @@ -914,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/tests/local/collapse_CitationNumberRangesSeparated.txt b/tests/local/collapse_CitationNumberRangesSeparated.txt new file mode 100644 index 0000000..39cd5b3 --- /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 =====<<