From 7e275344239c5df111c3e1f76c01a73efd41c624 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:33:18 -0300 Subject: [PATCH 01/12] initial bib test support --- Cargo.toml | 3 +- tests/citeproc-pass.txt | 87 ++++++++++++++++++ tests/citeproc.rs | 190 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 263 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4728f4f7..86889a96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ biblatex = { version = "0.10.0", optional = true } ciborium = { version = "0.2.1", optional = true } clap = { version = "4", optional = true, features = ["cargo"] } strum = { version = "0.26", features = ["derive"], optional = true } +regex = { version = "1", optional = true } [dev-dependencies] heck = "0.5" @@ -44,4 +45,4 @@ required-features = ["cli"] [[test]] name = "citeproc" path = "tests/citeproc.rs" -required-features = ["csl-json"] +required-features = ["csl-json", "regex"] diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 3619e671..9f6c79b4 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -4,18 +4,25 @@ # # This file is automatically generated by running `cargo test --test citeproc --features csl-json -- write_passing --ignored`. + affix_InterveningEmpty affix_TextNodeWithMacro bugreports_Abnt bugreports_ArabicLocale +bugreports_AsaSpacing bugreports_AuthorYear bugreports_BadCitationUpdate bugreports_ChineseCharactersFamilyOnlyPluralLabel bugreports_ContextualPluralWithMainItemFields +bugreports_DisambiguationAddNamesBibliography bugreports_EmptyIfMatchNoneFail bugreports_SectionAndLocator +bugreports_SimpleBib bugreports_SingletonIfMatchNoneFail +bugreports_SortSecondaryKeyBibliography bugreports_TitleCase +bugreports_UndefinedInName2 +bugreports_UnisaHarvardInitialization bugreports_YearSuffixLingers bugreports_disambiguate bugreports_effingBug @@ -40,18 +47,21 @@ condition_NameAndTextVars condition_NumberIsNumeric condition_NumeralIsNumeric condition_NumeralWithTextIsNumeric +condition_RefTypeBranching condition_SingletonIfMatchNone condition_TextIsNotNumeric condition_VariableAll condition_VariableAny condition_VariableNone date_Accessed +date_AccessedCrash date_DateAD date_DateNoDateWithTest date_DisappearingBug date_EmptyStrings date_IgnoreNonexistentSort date_January +date_KeyVariable date_LiteralFailGracefullyIfNoValue date_LocalizedDateFormats-af-ZA date_LocalizedDateFormats-ar-AR @@ -121,21 +131,27 @@ disambiguate_DifferentSpacingInInitials disambiguate_DisambiguateTrueAndYearSuffixOne disambiguate_FailWithYearSuffix disambiguate_FamilyNameOnly +disambiguate_HonorFullnameInBibliography +disambiguate_ImplicitYearSuffixOnceOnly disambiguate_LastOnlyFailWithByCite disambiguate_NoTextElementUsesYearSuffixVariable disambiguate_PrimaryNameWithNonDroppingParticle +disambiguate_ThreeNoAuthorNoTitleEntries disambiguate_WithOriginalYear disambiguate_YearCollapseWithInstitution disambiguate_YearSuffixMacroSameYearExplicit disambiguate_YearSuffixMacroSameYearImplicit disambiguate_YearSuffixWithEtAlSubsequent +display_DisplayBlock flipflop_OrphanQuote form_TitleShort form_TitleShortNoLong form_TitleTestNoLongFalse +fullstyles_ABdNT fullstyles_APA group_ShortOutputOnly group_SuppressValueWithEmptySubgroup +group_SuppressWithEmptyNestedDateNode integration_CitationSort integration_CitationSortTwice label_CompactNamesAfterFullNames @@ -193,9 +209,12 @@ name_CeltsAndToffsWithHyphens name_CiteGroupDelimiterWithYearCollapse name_CollapseRoleLabels name_Delimiter +name_EditorTranslatorBoth name_EditorTranslatorSameEmptyTerm name_EditorTranslatorSameWithTerm +name_EditorTranslatorWithTranslatorOnlyBib name_EtAlKanji +name_EtAlUseLast name_FirstInitialFullForm name_FormattingOfParticles name_GreekSimple @@ -207,6 +226,7 @@ name_InstitutionDecoration name_LabelAfterPlural name_LabelAfterPluralDecorations name_LabelFormatBug +name_LiteralWithComma name_MultipleLiteral name_NoNameNode name_NonDroppingParticleDefault @@ -231,48 +251,98 @@ name_WesternTwoAuthors name_WithNonBreakingSpace name_namepartAffixesNameAsSortOrder name_namepartAffixesNameAsSortOrderDemoteNonDroppingParticle +nameattr_AndOnBibliographyInBibliography nameattr_AndOnBibliographyInCitation +nameattr_AndOnCitationInBibliography nameattr_AndOnCitationInCitation +nameattr_AndOnNamesInBibliography nameattr_AndOnNamesInCitation +nameattr_AndOnStyleInBibliography +nameattr_AndOnStyleInCitation +nameattr_DelimiterPrecedesEtAlOnBibliographyInBibliography nameattr_DelimiterPrecedesEtAlOnBibliographyInCitation +nameattr_DelimiterPrecedesEtAlOnCitationInBibliography nameattr_DelimiterPrecedesEtAlOnCitationInCitation +nameattr_DelimiterPrecedesEtAlOnNamesInBibliography nameattr_DelimiterPrecedesEtAlOnNamesInCitation +nameattr_DelimiterPrecedesEtAlOnStyleInBibliography nameattr_DelimiterPrecedesEtAlOnStyleInCitation +nameattr_DelimiterPrecedesLastOnBibliographyInBibliography nameattr_DelimiterPrecedesLastOnBibliographyInCitation +nameattr_DelimiterPrecedesLastOnCitationInBibliography nameattr_DelimiterPrecedesLastOnCitationInCitation +nameattr_DelimiterPrecedesLastOnNamesInBibliography nameattr_DelimiterPrecedesLastOnNamesInCitation +nameattr_DelimiterPrecedesLastOnStyleInBibliography nameattr_DelimiterPrecedesLastOnStyleInCitation +nameattr_EtAlMinOnBibliographyInBibliography nameattr_EtAlMinOnBibliographyInCitation +nameattr_EtAlMinOnCitationInBibliography nameattr_EtAlMinOnCitationInCitation +nameattr_EtAlMinOnNamesInBibliography nameattr_EtAlMinOnNamesInCitation +nameattr_EtAlMinOnStyleInBibliography nameattr_EtAlMinOnStyleInCitation +nameattr_EtAlSubsequentMinOnBibliographyInBibliography nameattr_EtAlSubsequentMinOnBibliographyInCitation +nameattr_EtAlSubsequentMinOnCitationInBibliography +nameattr_EtAlSubsequentMinOnNamesInBibliography +nameattr_EtAlSubsequentMinOnStyleInBibliography +nameattr_EtAlSubsequentUseFirstOnBibliographyInBibliography nameattr_EtAlSubsequentUseFirstOnBibliographyInCitation +nameattr_EtAlSubsequentUseFirstOnCitationInBibliography +nameattr_EtAlSubsequentUseFirstOnStyleInBibliography +nameattr_EtAlUseFirstOnBibliographyInBibliography nameattr_EtAlUseFirstOnBibliographyInCitation +nameattr_EtAlUseFirstOnCitationInBibliography nameattr_EtAlUseFirstOnCitationInCitation +nameattr_EtAlUseFirstOnNamesInBibliography nameattr_EtAlUseFirstOnNamesInCitation +nameattr_EtAlUseFirstOnStyleInBibliography nameattr_EtAlUseFirstOnStyleInCitation +nameattr_InitializeWithOnBibliographyInBibliography nameattr_InitializeWithOnBibliographyInCitation +nameattr_InitializeWithOnCitationInBibliography nameattr_InitializeWithOnCitationInCitation +nameattr_InitializeWithOnNamesInBibliography nameattr_InitializeWithOnNamesInCitation +nameattr_InitializeWithOnStyleInBibliography nameattr_InitializeWithOnStyleInCitation +nameattr_NameAsSortOrderOnBibliographyInBibliography nameattr_NameAsSortOrderOnBibliographyInCitation +nameattr_NameAsSortOrderOnCitationInBibliography nameattr_NameAsSortOrderOnCitationInCitation +nameattr_NameAsSortOrderOnNamesInBibliography nameattr_NameAsSortOrderOnNamesInCitation +nameattr_NameAsSortOrderOnStyleInBibliography nameattr_NameAsSortOrderOnStyleInCitation +nameattr_NameDelimiterOnBibliographyInBibliography nameattr_NameDelimiterOnBibliographyInCitation +nameattr_NameDelimiterOnCitationInBibliography nameattr_NameDelimiterOnCitationInCitation +nameattr_NameDelimiterOnNamesInBibliography nameattr_NameDelimiterOnNamesInCitation +nameattr_NameDelimiterOnStyleInBibliography nameattr_NameDelimiterOnStyleInCitation +nameattr_NameFormOnBibliographyInBibliography nameattr_NameFormOnBibliographyInCitation +nameattr_NameFormOnCitationInBibliography nameattr_NameFormOnCitationInCitation +nameattr_NameFormOnNamesInBibliography nameattr_NameFormOnNamesInCitation +nameattr_NameFormOnStyleInBibliography nameattr_NameFormOnStyleInCitation nameattr_NamesDelimiterOnBibliographyInCitation +nameattr_NamesDelimiterOnCitationInBibliography +nameattr_NamesDelimiterOnNamesInBibliography nameattr_NamesDelimiterOnNamesInCitation +nameattr_SortSeparatorOnBibliographyInBibliography nameattr_SortSeparatorOnBibliographyInCitation +nameattr_SortSeparatorOnCitationInBibliography nameattr_SortSeparatorOnCitationInCitation +nameattr_SortSeparatorOnNamesInBibliography nameattr_SortSeparatorOnNamesInCitation +nameattr_SortSeparatorOnStyleInBibliography nameattr_SortSeparatorOnStyleInCitation nameorder_Long nameorder_LongNameAsSortDemoteDisplayAndSort @@ -281,6 +351,7 @@ nameorder_Short nameorder_ShortDemoteDisplayAndSort nameorder_ShortNameAsSortDemoteNever namespaces_NonNada3 +number_FailingDelimiters number_IsNumericWithAlpha number_MixedPageRange number_PageFirst @@ -296,22 +367,38 @@ page_NumberPageFirst page_PluralDetectWithEndash page_WithLocaleAndWeirdDelimiter plural_LabelForced +position_IbidWithSuffix position_NearNoteUnsupported position_TrueInCitation punctuation_DateStripPeriods +punctuation_DelimiterWithStripPeriodsAndSubstitute2 +punctuation_DelimiterWithStripPeriodsAndSubstitute3 punctuation_DoNotSuppressColonAfterPeriod punctuation_NoSuppressOfPeriodBeforeSemicolon +punctuation_SemicolonDelimiter +quotes_Punctuation +sort_BibliographyResortOnUpdate +sort_CaseInsensitiveBibliography sort_CaseInsensitiveCitation sort_Citation +sort_CitationNumberPrimaryAscendingViaMacroBibliography +sort_CitationNumberPrimaryAscendingViaVariableBibliography sort_CitationSecondaryKey sort_CiteGroupDelimiter +sort_DaleDalebout sort_DateVariable sort_DateVariableMixedElementsAscendingA sort_DateVariableMixedElementsAscendingB sort_DateVariableMixedElementsDescendingA sort_DateVariableMixedElementsDescendingB +sort_EtAlUseLast +sort_FamilyOnly sort_LatinUnicode sort_LocalizedDateLimitedParts +sort_NameParticleInNameSortFalse +sort_NameParticleInNameSortTrue +sort_NamesUseLast +sort_StatusFieldDescending sort_TestInheritance sortseparator_SortSeparatorEmpty substitute_RepeatedNamesOk diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 0f1f10e9..9e82121a 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -3,6 +3,7 @@ use std::io::{BufWriter, Write}; use std::path::PathBuf; use std::str::FromStr; +use std::sync::OnceLock; use std::{fmt, fs}; mod common; @@ -491,19 +492,14 @@ where .map_or(false, |d| d.end.is_some()) }); - if case.mode == TestMode::Bibliography { - if print { - eprintln!("Skipping test {}\t(cause: Bibliography mode)", display()); - } - false - } else if !can_test { + if !can_test { if print { eprintln!("Skipping test {}\t(cause: unsupported test feature)", display()); } false - } else if case.result.contains('<') { + } else if case.mode != TestMode::Bibliography && case.result.contains('<') { if print { - eprintln!("Skipping test {}\t(cause: HTML suspected)", display()); + eprintln!("Skipping test {}\t(cause: HTML suspected in citation result)", display()); } false } else if contains_date_ranges { @@ -579,15 +575,37 @@ where let rendered = driver.finish(BibliographyRequest::new(&style, None, locales)); - for citation in rendered.citations { - citation - .citation - .write_buf(&mut output, hayagriva::BufWriteFormat::Plain) - .unwrap(); - output.push('\n'); - } + let formatted_result = match case.mode { + TestMode::Citation => { + for citation in rendered.citations { + citation + .citation + .write_buf(&mut output, hayagriva::BufWriteFormat::Plain) + .unwrap(); + output.push('\n'); + } - if output.trim() == case.result.trim() { + case.result.trim() + }, + TestMode::Bibliography => { + static INDENT_REGEX: OnceLock = OnceLock::new(); + + let bib = rendered.bibliography.expect("Bibliography mode test but no bibliography was rendered"); + citeproc_bib::render(&bib, &mut output).unwrap(); + output.push('\n'); + + // Remove indentation from original result to match our own output, + // which is not indented or pretty-printed. It appears that + // citeproc test results simply indent elements with two spaces at + // the start when the line gets too long, or for each inner bib + // entry. + &*INDENT_REGEX + .get_or_init(|| regex::Regex::new(r#"\n\s*"#).unwrap()) + .replace_all(case.result.trim(), "") + }, + }; + + if output.trim() == formatted_result { true } else { eprintln!("Test {} failed", display()); @@ -597,6 +615,146 @@ where } } +/// Functions to format bibliography rendered by hayagriva using citeproc's HTML +/// output format, in order to be able to compare the generated HTML. +mod citeproc_bib { + use core::fmt; + + use citationberg::{Display, FontStyle, FontVariant, FontWeight, VerticalAlign}; + use hayagriva::{BufWriteFormat, Elem, ElemChild, Formatting}; + + pub(super) fn render(bib: &hayagriva::RenderedBibliography, output: &mut String) -> Result<(), fmt::Error> { + output.push_str(r#"
"#); + for item in &bib.items { + // TODO: Verify whether this automatically implies left-margin + // followed by right-inline (cf. test bugreports_AsmJournals.txt) + render_item(item, output)?; + } + output.push_str("
"); + Ok(()) + } + + fn render_item(item: &hayagriva::BibliographyItem, output: &mut String) -> Result<(), fmt::Error> { + output.push_str(r#"
"#); + if let Some(field) = &item.first_field { + render_child(field, output)?; + } + for child in &item.content.0 { + render_child(child, output)?; + } + output.push_str("
"); + Ok(()) + } + + fn render_child(child: &ElemChild, output: &mut String) -> Result<(), fmt::Error> { + match child { + ElemChild::Text(formatted) => { + render_formatted_text(formatted, output) + } + ElemChild::Elem(e) => render_elem(e, output), + elem => elem.write_buf(output, BufWriteFormat::Html), + } + } + + fn render_formatted_text(text: &hayagriva::Formatted, output: &mut String) -> Result<(), fmt::Error> { + let formatting = text.formatting; + if formatting == Formatting::default() { + output.push_str(&text.text); + return Ok(()); + } + + // TODO: Spaces between multiple CSS? (No example with multiple in tests) + let mut css = String::new(); + let mut suffix = String::new(); + let mut push_elem = |start, end| { + output.push_str(start); + suffix.insert_str(0, end); + }; + + match formatting.vertical_align { + VerticalAlign::Sub => { + push_elem("", ""); + } + VerticalAlign::Sup => { + push_elem("", ""); + } + VerticalAlign::Baseline => { + // TODO: Figure out what happens when baseline is combined with + // something else. + push_elem(r#""#, ""); + } + VerticalAlign::None => {} + } + + match formatting.font_weight { + FontWeight::Bold => { + push_elem("", ""); + } + FontWeight::Light => { + // TODO: Find an example test where this is used + css.push_str("font-weight:lighter;"); + } + FontWeight::Normal => { + // TODO: Do we support ...? + // It should force 'font-style:normal' when inside bold/italics + // Relevant test: flipflop_ItalicsWithOk.txt + } + } + + match formatting.font_style { + FontStyle::Italic => { + // NOTE: This has to come after (be inside) bold + // (Citeproc tests use ... when both are present) + push_elem("", "") + }, + FontStyle::Normal => { + // TODO: Same note for bold and + }, + } + + match formatting.font_variant { + FontVariant::SmallCaps => css.push_str("font-variant:small-caps;"), + FontVariant::Normal => { + // TODO: ... translates to + // 'font-variant:normal' inside smallcaps + // Relevant test: flipflop_ItalicsWithOkAndTextcase.txt + } + } + + if css != "" { + push_elem(&format!(""), ""); + } + + output.push_str(&text.text); + output.push_str(&suffix); + Ok(()) + } + + fn render_elem(elem: &Elem, output: &mut String) -> Result<(), fmt::Error> { + let mut div_suffix = ""; + if let Some(display) = elem.display { + div_suffix = ""; + let div_class = match display { + Display::Block => "csl-block", + Display::LeftMargin => "csl-left-margin", + Display::RightInline => "csl-right-inline", + Display::Indent => "csl-indent", + }; + + output.push_str(&format!("
")); + } + + for child in &elem.children.0 { + render_child(child, output)?; + } + + if div_suffix != "" { + output.push_str(&div_suffix); + } + Ok(()) + } +} + #[test] fn purposes() { let style = ArchivedStyle::by_name("apa").unwrap().get(); From 76c0c5c09face1ebb25d3244982a1dadffa5c2e2 Mon Sep 17 00:00:00 2001 From: DerDrodt Date: Thu, 17 Oct 2024 09:19:13 +0200 Subject: [PATCH 02/12] Fix bib layout affixes --- src/csl/mod.rs | 10 +++++++++- tests/citeproc-pass.txt | 9 +++++++++ tests/citeproc.rs | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 8ac6a330..610f535f 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -1386,7 +1386,15 @@ impl<'a> StyleContext<'a> { let mut ctx = self.ctx(entry, props, locale, term_locale, true); ctx.writing .push_name_options(&self.csl.bibliography.as_ref()?.name_options); - self.csl.bibliography.as_ref()?.layout.render(&mut ctx); + + let layout = &self.csl.bibliography.as_ref()?.layout; + if let Some(prefix) = layout.prefix.as_ref() { + ctx.push_str(prefix); + } + layout.render(&mut ctx); + if let Some(suffix) = layout.suffix.as_ref() { + ctx.push_str(suffix); + } Some(ctx) } diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 9f6c79b4..fa2e2b6d 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -16,16 +16,19 @@ bugreports_ChineseCharactersFamilyOnlyPluralLabel bugreports_ContextualPluralWithMainItemFields bugreports_DisambiguationAddNamesBibliography bugreports_EmptyIfMatchNoneFail +bugreports_NoEventInNestedMacroWithOldProcessor bugreports_SectionAndLocator bugreports_SimpleBib bugreports_SingletonIfMatchNoneFail bugreports_SortSecondaryKeyBibliography +bugreports_StyleError001 bugreports_TitleCase bugreports_UndefinedInName2 bugreports_UnisaHarvardInitialization bugreports_YearSuffixLingers bugreports_disambiguate bugreports_effingBug +bugreports_parenthesis bugreports_undefinedCrash collapse_AuthorCollapse collapse_AuthorCollapseDifferentAuthorsOneWithEtAl @@ -33,6 +36,7 @@ collapse_AuthorCollapseNoDate collapse_CitationNumberRangesMixed collapse_CitationNumberRangesMixed2 collapse_CitationNumberRangesMixed3 +collapse_CitationNumberRangesOneOnly collapse_NumericDuplicate collapse_NumericDuplicate2 collapse_TrailingDelimiter @@ -143,12 +147,14 @@ disambiguate_YearSuffixMacroSameYearExplicit disambiguate_YearSuffixMacroSameYearImplicit disambiguate_YearSuffixWithEtAlSubsequent display_DisplayBlock +etal_CitationAndBibliographyDecorationsInBibliography flipflop_OrphanQuote form_TitleShort form_TitleShortNoLong form_TitleTestNoLongFalse fullstyles_ABdNT fullstyles_APA +fullstyles_ChicagoNoteWithBibliographyWithPublisher group_ShortOutputOnly group_SuppressValueWithEmptySubgroup group_SuppressWithEmptyNestedDateNode @@ -237,6 +243,7 @@ name_PeriodAfterInitials name_QuashOrdinaryVariableRenderedViaSubstitute name_RomanianTwo name_SemicolonWithAnd +name_SubsequentAuthorSubstituteSingleField name_SubstituteMacroInheritDecorations name_SubstituteName name_SubstituteOnDateGroupSpanFail @@ -377,9 +384,11 @@ punctuation_DoNotSuppressColonAfterPeriod punctuation_NoSuppressOfPeriodBeforeSemicolon punctuation_SemicolonDelimiter quotes_Punctuation +quotes_PunctuationNasty sort_BibliographyResortOnUpdate sort_CaseInsensitiveBibliography sort_CaseInsensitiveCitation +sort_ChicagoYearSuffix1 sort_Citation sort_CitationNumberPrimaryAscendingViaMacroBibliography sort_CitationNumberPrimaryAscendingViaVariableBibliography diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 9e82121a..7a2fa37b 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -881,7 +881,7 @@ fn case_folding() { .content .write_buf(&mut buf, hayagriva::BufWriteFormat::Plain) .unwrap(); - assert_eq!(buf, ". my lowercase container title"); + assert_eq!(buf, ". my lowercase container title."); } #[test] From 652099c9e1bfdfd24641050e056ec42e3548f5a9 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:53:58 -0300 Subject: [PATCH 03/12] remove tests which aren't passing yet (TODO: bugreports_parenthesis should work... is citeproc trimming things?) --- tests/citeproc-pass.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index fa2e2b6d..a0faf408 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -28,7 +28,6 @@ bugreports_UnisaHarvardInitialization bugreports_YearSuffixLingers bugreports_disambiguate bugreports_effingBug -bugreports_parenthesis bugreports_undefinedCrash collapse_AuthorCollapse collapse_AuthorCollapseDifferentAuthorsOneWithEtAl @@ -243,7 +242,6 @@ name_PeriodAfterInitials name_QuashOrdinaryVariableRenderedViaSubstitute name_RomanianTwo name_SemicolonWithAnd -name_SubsequentAuthorSubstituteSingleField name_SubstituteMacroInheritDecorations name_SubstituteName name_SubstituteOnDateGroupSpanFail @@ -388,7 +386,6 @@ quotes_PunctuationNasty sort_BibliographyResortOnUpdate sort_CaseInsensitiveBibliography sort_CaseInsensitiveCitation -sort_ChicagoYearSuffix1 sort_Citation sort_CitationNumberPrimaryAscendingViaMacroBibliography sort_CitationNumberPrimaryAscendingViaVariableBibliography From 7e46bfcacb12ef96a96f8af4ab1e2f5a00db9fe1 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:58:42 -0300 Subject: [PATCH 04/12] add missing passing test with affixes fix --- tests/citeproc-pass.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index a0faf408..7c7a50e8 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -4,7 +4,6 @@ # # This file is automatically generated by running `cargo test --test citeproc --features csl-json -- write_passing --ignored`. - affix_InterveningEmpty affix_TextNodeWithMacro bugreports_Abnt @@ -411,6 +410,7 @@ substitute_RepeatedNamesOk substitute_SubstituteOnlyOnceString substitute_SubstituteOnlyOnceTerm substitute_SubstituteOnlyOnceVariable +substitute_SuppressOrdinaryVariable textcase_AfterQuote textcase_CapitalsUntouched textcase_StopWordBeforeHyphen From 7673edf9927fda4621c842fe585575b1dfe100ad Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:58:42 -0300 Subject: [PATCH 05/12] move regex to a dev dependency --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86889a96..4e9bd303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,11 +32,11 @@ biblatex = { version = "0.10.0", optional = true } ciborium = { version = "0.2.1", optional = true } clap = { version = "4", optional = true, features = ["cargo"] } strum = { version = "0.26", features = ["derive"], optional = true } -regex = { version = "1", optional = true } [dev-dependencies] heck = "0.5" serde_json = "1" +regex = "1" [[bin]] name = "hayagriva" @@ -45,4 +45,4 @@ required-features = ["cli"] [[test]] name = "citeproc" path = "tests/citeproc.rs" -required-features = ["csl-json", "regex"] +required-features = ["csl-json"] From 5b8274229228ab8f5657aa5bc215041a96d9975e Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:02:22 -0300 Subject: [PATCH 06/12] format --- tests/citeproc.rs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 7a2fa37b..5b487f2a 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -499,7 +499,10 @@ where false } else if case.mode != TestMode::Bibliography && case.result.contains('<') { if print { - eprintln!("Skipping test {}\t(cause: HTML suspected in citation result)", display()); + eprintln!( + "Skipping test {}\t(cause: HTML suspected in citation result)", + display() + ); } false } else if contains_date_ranges { @@ -586,11 +589,13 @@ where } case.result.trim() - }, + } TestMode::Bibliography => { static INDENT_REGEX: OnceLock = OnceLock::new(); - let bib = rendered.bibliography.expect("Bibliography mode test but no bibliography was rendered"); + let bib = rendered + .bibliography + .expect("Bibliography mode test but no bibliography was rendered"); citeproc_bib::render(&bib, &mut output).unwrap(); output.push('\n'); @@ -602,7 +607,7 @@ where &*INDENT_REGEX .get_or_init(|| regex::Regex::new(r#"\n\s*"#).unwrap()) .replace_all(case.result.trim(), "") - }, + } }; if output.trim() == formatted_result { @@ -623,7 +628,10 @@ mod citeproc_bib { use citationberg::{Display, FontStyle, FontVariant, FontWeight, VerticalAlign}; use hayagriva::{BufWriteFormat, Elem, ElemChild, Formatting}; - pub(super) fn render(bib: &hayagriva::RenderedBibliography, output: &mut String) -> Result<(), fmt::Error> { + pub(super) fn render( + bib: &hayagriva::RenderedBibliography, + output: &mut String, + ) -> Result<(), fmt::Error> { output.push_str(r#"
"#); for item in &bib.items { // TODO: Verify whether this automatically implies left-margin @@ -634,7 +642,10 @@ mod citeproc_bib { Ok(()) } - fn render_item(item: &hayagriva::BibliographyItem, output: &mut String) -> Result<(), fmt::Error> { + fn render_item( + item: &hayagriva::BibliographyItem, + output: &mut String, + ) -> Result<(), fmt::Error> { output.push_str(r#"
"#); if let Some(field) = &item.first_field { render_child(field, output)?; @@ -648,15 +659,16 @@ mod citeproc_bib { fn render_child(child: &ElemChild, output: &mut String) -> Result<(), fmt::Error> { match child { - ElemChild::Text(formatted) => { - render_formatted_text(formatted, output) - } + ElemChild::Text(formatted) => render_formatted_text(formatted, output), ElemChild::Elem(e) => render_elem(e, output), elem => elem.write_buf(output, BufWriteFormat::Html), } } - fn render_formatted_text(text: &hayagriva::Formatted, output: &mut String) -> Result<(), fmt::Error> { + fn render_formatted_text( + text: &hayagriva::Formatted, + output: &mut String, + ) -> Result<(), fmt::Error> { let formatting = text.formatting; if formatting == Formatting::default() { output.push_str(&text.text); @@ -706,10 +718,10 @@ mod citeproc_bib { // NOTE: This has to come after (be inside) bold // (Citeproc tests use ... when both are present) push_elem("", "") - }, + } FontStyle::Normal => { // TODO: Same note for bold and - }, + } } match formatting.font_variant { From 355ebd74446473c83a78f970ee86ff37502b62c2 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:05:33 -0300 Subject: [PATCH 07/12] clippy --- tests/citeproc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 5b487f2a..cd704a0f 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -733,7 +733,7 @@ mod citeproc_bib { } } - if css != "" { + if !css.is_empty() { push_elem(&format!(""), ""); } @@ -760,8 +760,8 @@ mod citeproc_bib { render_child(child, output)?; } - if div_suffix != "" { - output.push_str(&div_suffix); + if !div_suffix.is_empty() { + output.push_str(div_suffix); } Ok(()) } From 425575110c8a0f2f264901ae8847f787c810361a Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:10:55 -0300 Subject: [PATCH 08/12] fix second-field-align --- tests/citeproc.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index cd704a0f..72d839cf 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -634,8 +634,6 @@ mod citeproc_bib { ) -> Result<(), fmt::Error> { output.push_str(r#"
"#); for item in &bib.items { - // TODO: Verify whether this automatically implies left-margin - // followed by right-inline (cf. test bugreports_AsmJournals.txt) render_item(item, output)?; } output.push_str("
"); @@ -646,13 +644,20 @@ mod citeproc_bib { item: &hayagriva::BibliographyItem, output: &mut String, ) -> Result<(), fmt::Error> { + let mut second_field_align_suffix = ""; output.push_str(r#"
"#); if let Some(field) = &item.first_field { + // Uses 'second-field-align', so add implicit alignment + // (cf. test bugreports_AsmJournals.txt) + output.push_str("
"); render_child(field, output)?; + output.push_str("
"); + second_field_align_suffix = "
"; } for child in &item.content.0 { render_child(child, output)?; } + output.push_str(second_field_align_suffix); output.push_str("
"); Ok(()) } From 771da697cad0629be812d485562e848b6ef2cae7 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:10:55 -0300 Subject: [PATCH 09/12] do not output anchor elements for links --- tests/citeproc-pass.txt | 4 ++++ tests/citeproc.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index 7c7a50e8..e1c7ed71 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -15,6 +15,7 @@ bugreports_ChineseCharactersFamilyOnlyPluralLabel bugreports_ContextualPluralWithMainItemFields bugreports_DisambiguationAddNamesBibliography bugreports_EmptyIfMatchNoneFail +bugreports_MatchedAuthorAndDate bugreports_NoEventInNestedMacroWithOldProcessor bugreports_SectionAndLocator bugreports_SimpleBib @@ -23,6 +24,7 @@ bugreports_SortSecondaryKeyBibliography bugreports_StyleError001 bugreports_TitleCase bugreports_UndefinedInName2 +bugreports_UndefinedStr bugreports_UnisaHarvardInitialization bugreports_YearSuffixLingers bugreports_disambiguate @@ -375,6 +377,7 @@ position_IbidWithSuffix position_NearNoteUnsupported position_TrueInCitation punctuation_DateStripPeriods +punctuation_DelimiterWithStripPeriodsAndSubstitute1 punctuation_DelimiterWithStripPeriodsAndSubstitute2 punctuation_DelimiterWithStripPeriodsAndSubstitute3 punctuation_DoNotSuppressColonAfterPeriod @@ -391,6 +394,7 @@ sort_CitationNumberPrimaryAscendingViaVariableBibliography sort_CitationSecondaryKey sort_CiteGroupDelimiter sort_DaleDalebout +sort_DateMacroSortWithSecondFieldAlign sort_DateVariable sort_DateVariableMixedElementsAscendingA sort_DateVariableMixedElementsAscendingB diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 72d839cf..2a6ed8d1 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -666,6 +666,9 @@ mod citeproc_bib { match child { ElemChild::Text(formatted) => render_formatted_text(formatted, output), ElemChild::Elem(e) => render_elem(e, output), + + // Citeproc bib tests do not output for links + ElemChild::Link { text, url: _ } => render_formatted_text(text, output), elem => elem.write_buf(output, BufWriteFormat::Html), } } From 3550d0490ecf3e652166c0256fd5de7a445c3d91 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:04:23 -0300 Subject: [PATCH 10/12] adjust comments --- tests/citeproc.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 2a6ed8d1..ab613b89 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -673,6 +673,16 @@ mod citeproc_bib { } } + /// Applies the appropriate HTML tags to formatted text, according to + /// citeproc test output. + /// + /// Note that citeproc (csl-json) input accepts + /// '...' within text to nullify outer + /// formatting, for example apply 'font-style:normal' inside bold and + /// italics (see flipflop_ItalicsWithOk), or 'font-variant:normal' inside + /// smallcaps (see flipflop_ItalicsWithOkAndTextcase). We do not support + /// this notation, especially since we do not expose csl-json input to + /// hayagriva users anyway. fn render_formatted_text( text: &hayagriva::Formatted, output: &mut String, @@ -683,7 +693,8 @@ mod citeproc_bib { return Ok(()); } - // TODO: Spaces between multiple CSS? (No example with multiple in tests) + // NOTE: There are no tests with multiple CSS styles, so for now we + // join them without any spacing. let mut css = String::new(); let mut suffix = String::new(); let mut push_elem = |start, end| { @@ -714,11 +725,7 @@ mod citeproc_bib { // TODO: Find an example test where this is used css.push_str("font-weight:lighter;"); } - FontWeight::Normal => { - // TODO: Do we support ...? - // It should force 'font-style:normal' when inside bold/italics - // Relevant test: flipflop_ItalicsWithOk.txt - } + FontWeight::Normal => {} } match formatting.font_style { @@ -727,18 +734,12 @@ mod citeproc_bib { // (Citeproc tests use ... when both are present) push_elem("", "") } - FontStyle::Normal => { - // TODO: Same note for bold and - } + FontStyle::Normal => {} } match formatting.font_variant { FontVariant::SmallCaps => css.push_str("font-variant:small-caps;"), - FontVariant::Normal => { - // TODO: ... translates to - // 'font-variant:normal' inside smallcaps - // Relevant test: flipflop_ItalicsWithOkAndTextcase.txt - } + FontVariant::Normal => {} } if !css.is_empty() { From 415e4004375c33ca150a3b4c03fccf38bbe5526f Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:05:57 -0300 Subject: [PATCH 11/12] adjust missing test --- tests/citeproc.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index ab613b89..b3f09ce2 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -710,8 +710,6 @@ mod citeproc_bib { push_elem("", ""); } VerticalAlign::Baseline => { - // TODO: Figure out what happens when baseline is combined with - // something else. push_elem(r#""#, ""); } VerticalAlign::None => {} @@ -722,7 +720,8 @@ mod citeproc_bib { push_elem("", ""); } FontWeight::Light => { - // TODO: Find an example test where this is used + // NOTE: This is not used in any tests, so we can only assume + // this is done through this CSS style for now. css.push_str("font-weight:lighter;"); } FontWeight::Normal => {} From f5880dd320b364a6bf014a149e72ae247e6851b2 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:17:47 -0300 Subject: [PATCH 12/12] use 'apply_(affix)' for bibliography layout affixes --- src/csl/mod.rs | 13 ++++++------- tests/citeproc-pass.txt | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 610f535f..2d587f97 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -15,7 +15,7 @@ use citationberg::{ taxonomy as csl_taxonomy, Affixes, BaseLanguage, Citation, CitationFormat, Collapse, CslMacro, Display, GrammarGender, IndependentStyle, InheritableNameOptions, Layout, LayoutRenderingElement, Locale, LocaleCode, Names, SecondFieldAlign, StyleCategory, - StyleClass, TermForm, ToFormatting, + StyleClass, TermForm, ToAffixes, ToFormatting, }; use citationberg::{DateForm, LongShortForm, OrdinalLookup, TextCase}; use indexmap::IndexSet; @@ -1388,13 +1388,12 @@ impl<'a> StyleContext<'a> { .push_name_options(&self.csl.bibliography.as_ref()?.name_options); let layout = &self.csl.bibliography.as_ref()?.layout; - if let Some(prefix) = layout.prefix.as_ref() { - ctx.push_str(prefix); - } + let affixes = layout.to_affixes(); + + let affix_loc = ctx.apply_prefix(&affixes); layout.render(&mut ctx); - if let Some(suffix) = layout.suffix.as_ref() { - ctx.push_str(suffix); - } + ctx.apply_suffix(&affixes, affix_loc); + Some(ctx) } diff --git a/tests/citeproc-pass.txt b/tests/citeproc-pass.txt index e1c7ed71..43469317 100644 --- a/tests/citeproc-pass.txt +++ b/tests/citeproc-pass.txt @@ -24,6 +24,7 @@ bugreports_SortSecondaryKeyBibliography bugreports_StyleError001 bugreports_TitleCase bugreports_UndefinedInName2 +bugreports_UndefinedNotString bugreports_UndefinedStr bugreports_UnisaHarvardInitialization bugreports_YearSuffixLingers