Skip to content

Commit

Permalink
Add link, markup, gendered ordinals
Browse files Browse the repository at this point in the history
  • Loading branch information
reknih committed Oct 19, 2023
1 parent 66d4146 commit d24d69a
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 48 deletions.
93 changes: 75 additions & 18 deletions src/csl/elem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ impl Elem {
.map(|c| match c {
ElemChild::Text(t) => t.text.len(),
ElemChild::Elem(e) => e.str_len(),
ElemChild::Markup(m) => m.len(),
ElemChild::Link { text, .. } => text.text.len(),
})
.sum()
}
Expand Down Expand Up @@ -203,6 +205,7 @@ impl ElemChildren {
self.0.last_mut().and_then(|c| match c {
ElemChild::Text(t) => Some(&mut t.text),
ElemChild::Elem(e) => e.children.last_text_mut(),
ElemChild::Markup(_) | ElemChild::Link { .. } => None,
})
}

Expand All @@ -227,8 +230,14 @@ impl ElemChildren {

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ElemChild {
/// This is some text.
Text(Formatted),
/// A child element.
Elem(Elem),
/// This should be processed by Typst.
Markup(String),
/// This is a link.
Link { text: Formatted, url: String },
}

impl ElemChild {
Expand All @@ -238,47 +247,57 @@ impl ElemChild {
format: BufWriteFormat,
) -> Result<(), fmt::Error> {
match self {
ElemChild::Text(t) if format == BufWriteFormat::HTML => {
let is_default = t.formatting == Formatting::default();
if !is_default {
w.write_str("<span style=\"")?;
t.formatting.write_css(w)?;
w.write_str("\">")?;
}
ElemChild::Text(t) => {
t.formatting.write_start(w, format)?;
w.write_str(&t.text)?;
if !is_default {
w.write_str("</span>")?;
}
t.formatting.write_end(w, format)?;
Ok(())
}
ElemChild::Text(t) if format == BufWriteFormat::VT100 => {
t.formatting.write_vt100(w)?;
w.write_str(&t.text)?;
w.write_str("\x1b[0m")
}
ElemChild::Text(t) => w.write_str(&t.text),
ElemChild::Elem(e) => e.write_buf(w, format),
ElemChild::Markup(m) => w.write_str(m),
ElemChild::Link { text, url } if format == BufWriteFormat::HTML => {
w.write_str("<a href=\"")?;
w.write_str(url)?;
w.write_str("\">")?;
text.formatting.write_start(w, format)?;
w.write_str(&text.text)?;
text.formatting.write_end(w, format)?;
w.write_str("</a>")
}
ElemChild::Link { text, .. } => {
text.formatting.write_start(w, format)?;
w.write_str(&text.text)?;
text.formatting.write_end(w, format)
}
}
}

pub(super) fn str_len(&self) -> usize {
match self {
ElemChild::Text(t) => t.text.len(),
ElemChild::Elem(e) => e.str_len(),
ElemChild::Markup(m) => m.len(),
ElemChild::Link { text, .. } => text.text.len(),
}
}

pub(super) fn has_content(&self) -> bool {
match self {
ElemChild::Text(t) => t.text.chars().any(|c| !c.is_whitespace()),
ElemChild::Text(Formatted { text, .. }) | ElemChild::Markup(text) => {
text.chars().any(|c| !c.is_whitespace())
}
ElemChild::Elem(e) => e.has_content(),
ElemChild::Link { .. } => true,
}
}

pub(super) fn is_empty(&self) -> bool {
match self {
ElemChild::Text(t) => t.text.is_empty(),
ElemChild::Text(Formatted { text, .. }) | ElemChild::Markup(text) => {
text.is_empty()
}
ElemChild::Elem(e) => e.is_empty(),
ElemChild::Link { text, .. } => text.text.is_empty(),
}
}
}
Expand Down Expand Up @@ -452,6 +471,44 @@ impl Formatting {

Ok(())
}

pub(super) fn write_start(
&self,
buf: &mut impl fmt::Write,
format: BufWriteFormat,
) -> Result<(), fmt::Error> {
match format {
BufWriteFormat::Plain => Ok(()),
BufWriteFormat::VT100 => self.write_vt100(buf),
BufWriteFormat::HTML => {
let is_default = self == &Formatting::default();
if !is_default {
buf.write_str("<span style=\"")?;
self.write_css(buf)?;
buf.write_str("\">")?;
}
Ok(())
}
}
}

pub(super) fn write_end(
&self,
buf: &mut impl fmt::Write,
format: BufWriteFormat,
) -> Result<(), fmt::Error> {
match format {
BufWriteFormat::Plain => Ok(()),
BufWriteFormat::VT100 => buf.write_str("\x1b[0m"),
BufWriteFormat::HTML => {
let is_default = self == &Formatting::default();
if !is_default {
buf.write_str("</span>")?;
}
Ok(())
}
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
41 changes: 36 additions & 5 deletions src/csl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use std::{mem, vec};
use citationberg::taxonomy::{Locator, OtherTerm, Term, Variable};
use citationberg::{
taxonomy as csl_taxonomy, Affixes, Citation, Collapse, CslMacro, Display,
IndependentStyle, InheritableNameOptions, Locale, LocaleCode, RendersYearSuffix,
SecondFieldAlign, StyleClass, TermForm, ToFormatting,
GrammarGender, IndependentStyle, InheritableNameOptions, Locale, LocaleCode,
RendersYearSuffix, SecondFieldAlign, StyleClass, TermForm, ToFormatting,
};
use citationberg::{DateForm, LongShortForm, OrdinalLookup, TextCase};
use indexmap::IndexSet;
Expand Down Expand Up @@ -1258,6 +1258,7 @@ impl WritingContext {
}
Some(ElemChild::Text(_)) => false,
Some(ElemChild::Elem(e)) => e.has_content(),
Some(ElemChild::Markup(_) | ElemChild::Link { .. }) => true,
None => false,
};

Expand Down Expand Up @@ -1802,14 +1803,33 @@ impl<'a> Context<'a> {
for chunk in &chunked.0 {
match chunk.kind {
ChunkKind::Normal => self.push_str(&chunk.value),
_ => {
self.writing.buf.push_chunk(chunk);
ChunkKind::Verbatim => {
self.writing.buf.push_verbatim(&chunk.value);
self.writing.pull_punctuation = false;
}
ChunkKind::Math => {
self.writing.save_to_block();
self.writing
.elem_stack
.last_mut()
.0
.push(ElemChild::Markup(chunk.value.clone()))
}
}
}
}

/// Push a link into the buffer.
pub fn push_link(&mut self, chunked: &ChunkedString, url: String) {
let format = *self.writing.formatting();
self.writing.save_to_block();
self.writing
.elem_stack
.last_mut()
.0
.push(ElemChild::Link { text: format.add_text(chunked.to_string()), url })
}

/// Folds all remaining elements into the first element and returns it.
fn flush(self) -> ElemChildren {
self.writing.flush()
Expand All @@ -1832,6 +1852,17 @@ impl<'a> Context<'a> {
None
}

/// Get the gender of a term.
fn gender(&self, term: Term) -> Option<GrammarGender> {
if let Some(localization) =
self.style.lookup_locale(|l| l.term(term, TermForm::default()))
{
return localization.gender;
} else {
None
}
}

/// Get a localized date format.
fn localized_date(&self, form: DateForm) -> Option<&'a citationberg::Date> {
self.style
Expand Down Expand Up @@ -2076,7 +2107,7 @@ mod tests {
let mut driver = BibliographyDriver::new().unwrap();

for n in (0..bib.len()).step_by(3) {
let mut items = vec![
let items = vec![
CitationItem::from_entry(bib.nth(n).unwrap()),
CitationItem::from_entry(bib.nth(n + 1).unwrap()),
CitationItem::from_entry(bib.nth(n + 2).unwrap()),
Expand Down
56 changes: 44 additions & 12 deletions src/csl/rendering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,41 @@ impl RenderCsl for citationberg::Text {
let cidx = ctx.push_case(self.text_case);

match &self.target {
TextTarget::Variable { var, form } => ctx.push_chunked(
match var {
Variable::Standard(var) => ctx.resolve_standard_variable(*form, *var),
Variable::Number(var) => ctx
.resolve_number_variable(*var)
.map(|t| Cow::Owned(t.to_chunked_string())),
_ => None,
TextTarget::Variable { var: Variable::Standard(var), form } => {
if let Some(val) = ctx.resolve_standard_variable(*form, *var) {
match var {
StandardVariable::URL => {
let str = val.to_string();
ctx.push_link(&val, str);
}
StandardVariable::DOI => {
let url = format!("https://doi.org/{}", val.to_str());
ctx.push_link(&val, url);
}
StandardVariable::PMID => {
let url = format!(
"https://www.ncbi.nlm.nih.gov/pubmed/{}",
val.to_str()
);
ctx.push_link(&val, url);
}
StandardVariable::PMCID => {
let url = format!(
"https://www.ncbi.nlm.nih.gov/pmc/articles/{}",
val.to_str()
);
ctx.push_link(&val, url);
}
_ => ctx.push_chunked(&val),
}
}
.unwrap_or_default()
.as_ref(),
),
}
TextTarget::Variable { var: Variable::Number(var), .. } => {
if let Some(n) = ctx.resolve_number_variable(*var) {
ctx.push_str(&n.to_str())
}
}
TextTarget::Variable { .. } => {}
TextTarget::Macro { name } => {
let len = ctx.writing.len();
let mac = ctx.style.get_macro(name);
Expand Down Expand Up @@ -98,6 +122,7 @@ impl RenderCsl for citationberg::Number {
let depth = ctx.push_elem(self.formatting);
let affix_loc = ctx.apply_prefix(&self.affixes);
let cidx = ctx.push_case(self.text_case);
let gender = ctx.gender(self.variable.into());

match value {
Some(MaybeTyped::Typed(num)) if num.will_transform() => {
Expand Down Expand Up @@ -129,7 +154,9 @@ impl RenderCsl for citationberg::Number {
};

if normal_num {
num.as_ref().with_form(ctx, self.form, ctx.ordinal_lookup()).unwrap();
num.as_ref()
.with_form(ctx, self.form, gender, ctx.ordinal_lookup())
.unwrap();
}
}
Some(MaybeTyped::Typed(num)) => write!(ctx, "{}", num).unwrap(),
Expand Down Expand Up @@ -348,11 +375,16 @@ fn render_date_part(
})
.unwrap_or_default() =>
{
let gender = date
.month
.and_then(OtherTerm::month)
.and_then(|m| ctx.gender(m.into()));

write!(
ctx,
"{}{}",
val,
ctx.ordinal_lookup().lookup(val).unwrap_or_default()
ctx.ordinal_lookup().lookup(val, gender).unwrap_or_default()
)
.unwrap();
}
Expand Down
23 changes: 13 additions & 10 deletions src/lang/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub(crate) mod name;

use std::{fmt::Write, mem};

use crate::types::{ChunkKind, StringChunk};
use crate::types::{FoldableKind, FoldableStringChunk};

/// Rules for the title case transformation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -375,17 +375,20 @@ impl CaseFolder {
}

/// Add a string chunk to the buffer.
pub fn push_chunk(&mut self, chunk: &StringChunk) {
pub fn push_verbatim(&mut self, value: &str) {
let conf = mem::replace(&mut self.case, Case::NoTransform);
self.last_reconfig = self.buf.len();
self.push_str(&value);
self.last_reconfig = self.buf.len();
self.case = conf;
}
/// Add a string chunk to the buffer.
pub fn push_chunk(&mut self, chunk: &FoldableStringChunk) {
match chunk.kind {
ChunkKind::Verbatim => {
let conf = mem::replace(&mut self.case, Case::NoTransform);
self.last_reconfig = self.buf.len();
self.push_str(&chunk.value);
self.last_reconfig = self.buf.len();
self.case = conf;
FoldableKind::Verbatim => {
self.push_verbatim(&chunk.value);
}
ChunkKind::Math => todo!(),
ChunkKind::Normal => self.push_str(&chunk.value),
FoldableKind::Normal => self.push_str(&chunk.value),
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/types/numeric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::fmt::Write;
use std::fmt::{self, Display};
use std::str::FromStr;

use citationberg::{NumberForm, OrdinalLookup};
use citationberg::{GrammarGender, NumberForm, OrdinalLookup};
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
Expand Down Expand Up @@ -159,6 +159,7 @@ impl Numeric {
&self,
buf: &mut T,
form: NumberForm,
gender: Option<GrammarGender>,
ords: OrdinalLookup<'_>,
) -> std::fmt::Result
where
Expand All @@ -167,11 +168,13 @@ impl Numeric {
let format = |n: i32, buf: &mut T| -> std::fmt::Result {
match form {
NumberForm::Ordinal => {
write!(buf, "{}{}", n, ords.lookup(n).unwrap_or_default())
write!(buf, "{}{}", n, ords.lookup(n, gender).unwrap_or_default())
}
NumberForm::LongOrdinal => match ords.lookup_long(n) {
Some(str) => buf.write_str(str),
None => write!(buf, "{}{}", n, ords.lookup(n).unwrap_or_default()),
None => {
write!(buf, "{}{}", n, ords.lookup(n, gender).unwrap_or_default())
}
},
NumberForm::Roman if n > 0 && n <= i16::MAX as i32 => {
write!(buf, "{:x}", numerals::roman::Roman::from(n as i16))
Expand Down
Loading

0 comments on commit d24d69a

Please sign in to comment.