Skip to content

Commit d31f027

Browse files
committed
Merge branch 'main' into fix-219
2 parents 5a16bfe + 77b1f54 commit d31f027

File tree

15 files changed

+353
-114
lines changed

15 files changed

+353
-114
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 0.8.0
2+
3+
- **Breaking change:** Fixed deserialization of page ranges,
4+
removing `From<u64> for PageRanges`
5+
- Added support for disambiguation to alphanumeric citation style
6+
- Raised limit for disambiguation resolving in complex cases
7+
- The year 0 will now render as 1BC for CSL
8+
19
# 0.7.0
210

311
- **Breaking change:** `Entry::page_range` now returns

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hayagriva"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
authors = ["Martin Haug <[email protected]>"]
55
edition = "2021"
66
license = "MIT OR Apache-2.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ default features by writing this in your `Cargo.toml`:
105105

106106
```toml
107107
[dependencies]
108-
hayagriva = { version = "0.7", default-features = false }
108+
hayagriva = { version = "0.8", default-features = false }
109109
```
110110

111111
### Selectors

archive/styles/alphanumeric.cbor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
�dinfo�fauthor��dnamekMartin [email protected]�dnamenLaurenz Mädjeeemailx[email protected]�hcategory��p@citation-formatelabelefield�bidx!http://typst.org/csl/alphanumericdissn�dlink�frights�e$textqMIT OR Apache-2.0gsummary�f$valuevAlphanumeric citationsetitle�[email protected]�dsort�ckey��i@variablefauthore@sortiascending�i@variablefissuede@sortiascendingflayout�f$value��dtext�i@variablencitation-labele@formdlongg@quotes�n@strip-periods��g@prefixa[g@suffixa]j@delimiterb, x@disambiguate-add-givenname�x@givenname-disambiguation-rulegby-citew@disambiguate-add-names�x@disambiguate-add-year-suffix�i@collapseocitation-numberx@after-collapse-delimiterb; s@near-note-distance�f@classgin-textw@initialize-with-hyphen�x@demote-non-dropping-particlepdisplay-and-sortemacro�flocale��
1+
�dinfo�fauthor��dnamekMartin [email protected]�dnamenLaurenz Mädjeeemailx[email protected]�hcategory��p@citation-formatelabelefield�bidx!http://typst.org/csl/alphanumericdissn�dlink�frights�e$textqMIT OR Apache-2.0gsummary�f$valuevAlphanumeric citationsetitle�[email protected]�dsort�ckey��i@variablefauthore@sortiascending�i@variablefissuede@sortiascendingflayout�f$value��dtext�i@variablencitation-labele@formdlongg@quotes�n@strip-periods���dtext�i@variablekyear-suffixe@formdlongg@quotes�n@strip-periods��g@prefixa[g@suffixa]j@delimiterb, x@disambiguate-add-givenname�x@givenname-disambiguation-rulegby-citew@disambiguate-add-names�x@disambiguate-add-year-suffix�i@collapseocitation-numberx@after-collapse-delimiterb; s@near-note-distance�f@classgin-textw@initialize-with-hyphen�x@demote-non-dropping-particlepdisplay-and-sortemacro�flocale��

docs/file-format.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ This section lists all possible fields and data types for them.
154154
|------------------|-----------------------------------------------------------|
155155
| **Data type:** | formattable string |
156156
| **Description:** | title of the item |
157-
| **Example:** | `title: Rick Astley: How An Internet Joke Revived My Career` |
157+
| **Example:** | `title: "Rick Astley: How An Internet Joke Revived My Career"` |
158158
159159
#### `author`
160160

src/csl/elem.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ pub enum ElemMeta {
141141
Name(NameVariable, usize),
142142
/// The entry corresponds to a citation item.
143143
Entry(usize),
144+
/// The element is the output of `cs:text` with a `variable` set to
145+
/// `citation-label`.
146+
CitationLabel,
144147
}
145148

146149
/// A container for element children with useful methods.
@@ -165,20 +168,18 @@ impl ElemChildren {
165168

166169
/// Retrieve a reference to the first child with a matching meta by
167170
/// DFS.
168-
pub fn get_meta(&self, meta: ElemMeta) -> Option<&Elem> {
169-
for child in &self.0 {
170-
match child {
171-
ElemChild::Elem(e) if e.meta == Some(meta) => return Some(e),
172-
ElemChild::Elem(e) => {
173-
if let Some(e) = e.children.get_meta(meta) {
174-
return Some(e);
175-
}
176-
}
177-
_ => {}
178-
}
179-
}
180-
181-
None
171+
pub fn find_meta(&self, meta: ElemMeta) -> Option<&Elem> {
172+
self.find_elem_by(&|e| e.meta == Some(meta))
173+
}
174+
175+
/// Retrieve a mutable reference to the first child matching the predicate
176+
/// by DFS.
177+
pub fn find_elem_by<F: Fn(&Elem) -> bool>(&self, f: &F) -> Option<&Elem> {
178+
self.0.iter().find_map(|child| match child {
179+
ElemChild::Elem(e) if f(e) => Some(e),
180+
ElemChild::Elem(e) => e.children.find_elem_by(f),
181+
_ => None,
182+
})
182183
}
183184

184185
/// Retrieve a mutable reference to the first child with a matching meta by

src/csl/mod.rs

Lines changed: 131 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T>
212212
//
213213
// If we have set the disambiguation state for an item, we need to set
214214
// the same state for all entries referencing that item.
215-
for _ in 0..6 {
215+
for _ in 0..16 {
216216
let ambiguous = find_ambiguous_sets(&res);
217217
if ambiguous.is_empty() {
218218
break;
@@ -296,7 +296,7 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T>
296296

297297
let Some(name_elem) = cite.items[i]
298298
.rendered
299-
.get_meta(ElemMeta::Names)
299+
.find_meta(ElemMeta::Names)
300300
.map(|e| format!("{:?}", e))
301301
else {
302302
continue;
@@ -639,19 +639,9 @@ fn date_replacement<T: EntryLike>(
639639

640640
ElemChildren(vec![ElemChild::Text(Formatted {
641641
text: if let Some(date) = date {
642-
format!(
643-
"{}{}",
644-
if date.year > 0 { date.year } else { date.year.abs() + 1 },
645-
if date.year < 1000 {
646-
if date.year < 0 {
647-
"BC"
648-
} else {
649-
"AD"
650-
}
651-
} else {
652-
""
653-
}
654-
)
642+
let mut s = String::with_capacity(4);
643+
write_year(date.year, false, &mut s).unwrap();
644+
s
655645
} else if let Some(no_date) = ctx
656646
.ctx(entry, cite_props.clone(), locale, term_locale, false)
657647
.term(Term::Other(OtherTerm::NoDate), TermForm::default(), false)
@@ -664,6 +654,33 @@ fn date_replacement<T: EntryLike>(
664654
})])
665655
}
666656

657+
pub fn write_year<W: std::fmt::Write>(
658+
year: i32,
659+
short: bool,
660+
w: &mut W,
661+
) -> std::fmt::Result {
662+
if short && year >= 1000 {
663+
return write!(w, "{:02}", year % 100);
664+
}
665+
666+
write!(
667+
w,
668+
"{}{}",
669+
if year > 0 { year } else { year.abs() + 1 },
670+
if year < 1000 {
671+
if year <= 0 {
672+
"BC"
673+
} else {
674+
// AD is used as a postfix, see
675+
// https://docs.citationstyles.org/en/stable/specification.html?#ad-and-bc
676+
"AD"
677+
}
678+
} else {
679+
""
680+
}
681+
)
682+
}
683+
667684
fn last_purpose_render<T: EntryLike + Debug>(
668685
ctx: &StyleContext<'_>,
669686
item: &SpeculativeItemRender<T>,
@@ -764,19 +781,32 @@ fn disambiguate_year_suffix<F, T>(
764781
T: EntryLike + PartialEq,
765782
F: FnMut(&T, DisambiguateState),
766783
{
767-
if renders
768-
.iter()
769-
.flat_map(|r| r.items.iter())
770-
.any(|i| i.rendered.get_meta(ElemMeta::Date).is_some())
771-
&& group.iter().any(|&(cite_idx, item_idx)| {
772-
renders[cite_idx].request.style.citation.disambiguate_add_year_suffix
773-
&& renders[cite_idx].items[item_idx]
774-
.cite_props
775-
.speculative
776-
.disambiguation
777-
.may_disambiguate_with_year_suffix()
778-
})
779-
{
784+
if renders.iter().flat_map(|r| r.items.iter()).any(|i| {
785+
let entry_has_date = i
786+
.entry
787+
.resolve_date_variable(DateVariable::Issued)
788+
.or_else(|| i.entry.resolve_date_variable(DateVariable::Accessed))
789+
.or_else(|| i.entry.resolve_date_variable(DateVariable::AvailableDate))
790+
.or_else(|| i.entry.resolve_date_variable(DateVariable::EventDate))
791+
.or_else(|| i.entry.resolve_date_variable(DateVariable::Submitted))
792+
.or_else(|| i.entry.resolve_date_variable(DateVariable::OriginalDate))
793+
.is_some();
794+
795+
i.rendered
796+
.find_elem_by(&|e| {
797+
// The citation label will contain the date if there is one.
798+
e.meta == Some(ElemMeta::Date)
799+
|| (entry_has_date && e.meta == Some(ElemMeta::CitationLabel))
800+
})
801+
.is_some()
802+
}) && group.iter().any(|&(cite_idx, item_idx)| {
803+
renders[cite_idx].request.style.citation.disambiguate_add_year_suffix
804+
&& renders[cite_idx].items[item_idx]
805+
.cite_props
806+
.speculative
807+
.disambiguation
808+
.may_disambiguate_with_year_suffix()
809+
}) {
780810
let mut entries = Vec::new();
781811
for &(cite_idx, item_idx) in group.iter() {
782812
let item = &renders[cite_idx].items[item_idx];
@@ -911,7 +941,7 @@ fn collapse_items<'a, T: EntryLike>(cite: &mut SpeculativeCiteRender<'a, '_, T>)
911941
// cannot be mutably borrowed below otherwise.
912942
let item = &cite.items[i];
913943
if item.hidden
914-
|| item.rendered.get_meta(ElemMeta::CitationNumber).is_none()
944+
|| item.rendered.find_meta(ElemMeta::CitationNumber).is_none()
915945
{
916946
end_range(&mut cite.items, &mut range_start, &mut just_collapsed);
917947
continue;
@@ -3096,4 +3126,76 @@ mod tests {
30963126
// }
30973127
}
30983128
}
3129+
3130+
#[test]
3131+
fn low_year_test() {
3132+
let yield_year = |year, short| {
3133+
let mut s = String::new();
3134+
write_year(year, short, &mut s).unwrap();
3135+
s
3136+
};
3137+
3138+
assert_eq!(yield_year(2021, false), "2021");
3139+
assert_eq!(yield_year(2021, true), "21");
3140+
assert_eq!(yield_year(0, false), "1BC");
3141+
assert_eq!(yield_year(-1, false), "2BC");
3142+
assert_eq!(yield_year(1, false), "1AD");
3143+
assert_eq!(yield_year(0, true), "1BC");
3144+
assert_eq!(yield_year(-1, true), "2BC");
3145+
assert_eq!(yield_year(1, true), "1AD");
3146+
}
3147+
3148+
#[test]
3149+
#[cfg(feature = "archive")]
3150+
fn test_alphanumeric_disambiguation() {
3151+
let bibtex = r#"@article{chenTransMorphTransformerUnsupervised2021,
3152+
title = {{{TransMorph}}: {{Transformer}} for Unsupervised Medical Image Registration},
3153+
author = {Chen, Junyu and Frey, Eric C. and He, Yufan and Segars, William P. and Li, Ye and Du, Yong},
3154+
date = {2021},
3155+
}
3156+
3157+
@article{chenViTVNetVisionTransformer2021,
3158+
title = {{{ViT-V-Net}}: {{Vision Transformer}} for {{Unsupervised Volumetric Medical Image Registration}}},
3159+
author = {Chen, Junyu and He, Yufan and Frey, Eric C. and Li, Ye and Du, Yong},
3160+
date = {2021},
3161+
}"#;
3162+
3163+
let library = crate::io::from_biblatex_str(bibtex).unwrap();
3164+
let alphanumeric = archive::ArchivedStyle::Alphanumeric.get();
3165+
let citationberg::Style::Independent(alphanumeric) = alphanumeric else {
3166+
unreachable!()
3167+
};
3168+
3169+
let mut driver = BibliographyDriver::new();
3170+
for entry in library.iter() {
3171+
driver.citation(CitationRequest::new(
3172+
vec![CitationItem::with_entry(entry)],
3173+
&alphanumeric,
3174+
None,
3175+
&[],
3176+
None,
3177+
));
3178+
}
3179+
3180+
let finished = driver.finish(BibliographyRequest {
3181+
style: &alphanumeric,
3182+
locale: None,
3183+
locale_files: &[],
3184+
});
3185+
3186+
let mut c1 = String::new();
3187+
let mut c2 = String::new();
3188+
3189+
finished.citations[0]
3190+
.citation
3191+
.write_buf(&mut c1, BufWriteFormat::Plain)
3192+
.unwrap();
3193+
finished.citations[1]
3194+
.citation
3195+
.write_buf(&mut c2, BufWriteFormat::Plain)
3196+
.unwrap();
3197+
3198+
assert_eq!(c1, "[Che+21a]");
3199+
assert_eq!(c2, "[Che+21b]");
3200+
}
30993201
}

src/csl/rendering/mod.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::types::{ChunkedString, Date, MaybeTyped, Numeric};
2020
use crate::PageRanges;
2121

2222
use super::taxonomy::EntryLike;
23-
use super::{Context, ElemMeta, IbidState, SpecialForm, UsageInfo};
23+
use super::{write_year, Context, ElemMeta, IbidState, SpecialForm, UsageInfo};
2424

2525
pub mod names;
2626

@@ -126,6 +126,11 @@ impl RenderCsl for citationberg::Text {
126126
{
127127
Some(ElemMeta::CitationNumber)
128128
}
129+
TextTarget::Variable { var, .. }
130+
if var == StandardVariable::CitationLabel.into() =>
131+
{
132+
Some(ElemMeta::CitationLabel)
133+
}
129134
TextTarget::Variable { .. } => Some(ElemMeta::Text),
130135
_ => None,
131136
},
@@ -769,17 +774,8 @@ fn render_date_part<T: EntryLike>(
769774
write!(ctx, "{}", val).unwrap();
770775
}
771776
}
772-
DateStrongAnyForm::Year(LongShortForm::Short) => {
773-
write!(ctx, "{:02}", (val % 100).abs()).unwrap();
774-
}
775-
DateStrongAnyForm::Year(LongShortForm::Long) => {
776-
write!(ctx, "{}", val.abs()).unwrap();
777-
}
778-
}
779-
780-
if let DateStrongAnyForm::Year(_) = form {
781-
if date.year < 1000 {
782-
ctx.push_str(if date.year < 0 { "BC" } else { "AD" });
777+
DateStrongAnyForm::Year(brevity) => {
778+
write_year(val, brevity == LongShortForm::Short, ctx).unwrap();
783779
}
784780
}
785781
}

src/csl/taxonomy.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,8 +810,15 @@ impl EntryLike for citationberg::json::Item {
810810
) -> Option<MaybeTyped<PageRanges>> {
811811
match variable {
812812
PageVariable::Page => match self.0.get("page")? {
813-
csl_json::Value::Number(n) => {
814-
Some(MaybeTyped::Typed(PageRanges::from(*n as u64)))
813+
&csl_json::Value::Number(n) => {
814+
// Page ranges use i32 internally, so we check whether the
815+
// number is in range.
816+
Some(match i32::try_from(n) {
817+
Ok(n) => MaybeTyped::Typed(PageRanges::from(n)),
818+
// If the number is not in range, we degrade to a
819+
// string, which disables some CSL features.
820+
Err(_) => MaybeTyped::String(n.to_string()),
821+
})
815822
}
816823
csl_json::Value::String(s) => {
817824
let res = MaybeTyped::<PageRanges>::infallible_from_str(s);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ default features by writing this in your `Cargo.toml`:
9191
9292
```toml
9393
[dependencies]
94-
hayagriva = { version = "0.7", default-features = false }
94+
hayagriva = { version = "0.8", default-features = false }
9595
```
9696
9797
# Selectors

0 commit comments

Comments
 (0)