mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-03 06:51:50 -06:00
Render month and weekday names in XFA date patterns
Found while testing crates/xfa/src/data.rs:265-281 (iso_to_pattern, with date_tokens at :242 and pattern_to_iso at :285; pattern built in crates/xfa/src/layout.rs:396 date_pattern) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
924c4da4ac
commit
84ee69b507
+59
-3
@@ -238,7 +238,33 @@ fn date_pattern_of(doc: &Document, f: &FieldDatum) -> Option<String> {
|
||||
Some(body[..body.find('"')?].to_string())
|
||||
}
|
||||
|
||||
/// The tokens of a date pattern: `yyyy`, `yy`, `mm`, `m`, `dd`, `d`, or a literal character.
|
||||
const MONTHS: [&str; 12] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
const WEEKDAYS: [&str; 7] = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
|
||||
/// The weekday (0 = Sunday) of a proleptic Gregorian date.
|
||||
fn weekday(y: u32, m: u32, d: u32) -> usize {
|
||||
const T: [i64; 12] = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
|
||||
let y = i64::from(y) - i64::from(m < 3);
|
||||
let t = T.get((m as usize).wrapping_sub(1)).copied().unwrap_or(0);
|
||||
((y + y / 4 - y / 100 + y / 400 + t + i64::from(d)).rem_euclid(7)) as usize
|
||||
}
|
||||
|
||||
/// The index and length of the name in `names` that `text` starts with (ignoring case),
|
||||
/// cut to `abbr` letters when that is set.
|
||||
fn name_at(text: &str, names: &[&str], abbr: bool) -> Option<(usize, usize)> {
|
||||
names.iter().enumerate().find_map(|(i, n)| {
|
||||
let n = if abbr { n.get(..3)? } else { n };
|
||||
text.get(..n.len()).filter(|t| t.eq_ignore_ascii_case(n)).map(|_| (i, n.len()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether `token` is a run of the date letters (`y`, `m`, `d`) rather than a literal.
|
||||
fn is_date_run(token: &str) -> bool {
|
||||
token.starts_with(['y', 'm', 'd'])
|
||||
}
|
||||
|
||||
/// The tokens of a date pattern: runs of `y`, `m` and `d` (`yyyy`, `yy`, `mmmm`, `mmm`, `mm`,
|
||||
/// `m`, `dddd`, `ddd`, `dd`, `d` are understood), or a literal character.
|
||||
fn date_tokens(pattern: &str) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let chars: Vec<char> = pattern.chars().collect();
|
||||
@@ -260,8 +286,8 @@ fn date_tokens(pattern: &str) -> Vec<String> {
|
||||
out
|
||||
}
|
||||
|
||||
/// `2001-02-03` shown in `pattern` (`mm/dd/yyyy` → `02/03/2001`). Unknown patterns and
|
||||
/// non-dates come back as they are.
|
||||
/// `2001-02-03` shown in `pattern` (`mm/dd/yyyy` → `02/03/2001`, `mmm d, yyyy` →
|
||||
/// `Feb 3, 2001`). Patterns with a token this doesn't know and non-dates come back as they are.
|
||||
pub fn iso_to_pattern(iso: &str, pattern: &str) -> String {
|
||||
let parts: Vec<&str> = iso.trim().splitn(3, '-').collect();
|
||||
let [y, m, d] = parts[..] else { return iso.to_string() };
|
||||
@@ -275,6 +301,21 @@ pub fn iso_to_pattern(iso: &str, pattern: &str) -> String {
|
||||
"m" => out.push_str(&m.to_string()),
|
||||
"dd" => out.push_str(&format!("{d:02}")),
|
||||
"d" => out.push_str(&d.to_string()),
|
||||
"mmm" | "mmmm" => {
|
||||
let Some(name) = m.checked_sub(1).and_then(|i| MONTHS.get(i as usize)) else { return iso.to_string() };
|
||||
match t.len() {
|
||||
3 => out.push_str(name.get(..3).unwrap_or(name)),
|
||||
_ => out.push_str(name),
|
||||
}
|
||||
}
|
||||
"ddd" | "dddd" => {
|
||||
let name = WEEKDAYS.get(weekday(y, m, d)).copied().unwrap_or_default();
|
||||
match t.len() {
|
||||
3 => out.push_str(name.get(..3).unwrap_or(name)),
|
||||
_ => out.push_str(name),
|
||||
}
|
||||
}
|
||||
other if is_date_run(other) => return iso.to_string(),
|
||||
other => out.push_str(other),
|
||||
}
|
||||
}
|
||||
@@ -285,6 +326,7 @@ pub fn iso_to_pattern(iso: &str, pattern: &str) -> String {
|
||||
pub fn pattern_to_iso(text: &str, pattern: &str) -> Option<String> {
|
||||
let text = text.trim();
|
||||
let (mut y, mut m, mut d) = (None, None, None);
|
||||
let mut day_name = None;
|
||||
let mut rest = text;
|
||||
let tokens = date_tokens(pattern);
|
||||
for (i, t) in tokens.iter().enumerate() {
|
||||
@@ -310,6 +352,17 @@ pub fn pattern_to_iso(text: &str, pattern: &str) -> Option<String> {
|
||||
_ => d = Some(v),
|
||||
}
|
||||
}
|
||||
"mmm" | "mmmm" => {
|
||||
let (i, len) = name_at(rest, &MONTHS, t.len() == 3)?;
|
||||
m = Some(i as u32 + 1);
|
||||
rest = rest.get(len..)?;
|
||||
}
|
||||
"ddd" | "dddd" => {
|
||||
let (i, len) = name_at(rest, &WEEKDAYS, t.len() == 3)?;
|
||||
day_name = Some(i);
|
||||
rest = rest.get(len..)?;
|
||||
}
|
||||
run if is_date_run(run) => return None,
|
||||
lit => {
|
||||
rest = rest.strip_prefix(lit)?;
|
||||
}
|
||||
@@ -320,6 +373,9 @@ pub fn pattern_to_iso(text: &str, pattern: &str) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
let (y, m, d) = (y?, m?, d?);
|
||||
if day_name.is_some_and(|w| w != weekday(y, m, d)) {
|
||||
return None;
|
||||
}
|
||||
(1..=12).contains(&m).then(|| format!("{y:04}-{m:02}-{d:02}")).filter(|_| (1..=31).contains(&d))
|
||||
}
|
||||
|
||||
|
||||
@@ -336,6 +336,22 @@ fn dates_convert_between_iso_and_acrobat_patterns() {
|
||||
assert_eq!(pattern_to_iso("hello", "mm/dd/yyyy"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn month_and_weekday_names_in_date_patterns() {
|
||||
// 2001-02-03 was a Saturday.
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "mmm d, yyyy"), "Feb 3, 2001");
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "mmmm d, yyyy"), "February 3, 2001");
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "ddd, mmm d"), "Sat, Feb 3");
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "dddd, mmmm dd, yyyy"), "Saturday, February 03, 2001");
|
||||
assert_eq!(pattern_to_iso("Feb 3, 2001", "mmm d, yyyy").as_deref(), Some("2001-02-03"));
|
||||
assert_eq!(pattern_to_iso("February 3, 2001", "mmmm d, yyyy").as_deref(), Some("2001-02-03"));
|
||||
assert_eq!(pattern_to_iso("Saturday, February 03, 2001", "dddd, mmmm dd, yyyy").as_deref(), Some("2001-02-03"));
|
||||
assert_eq!(pattern_to_iso("Smarch 3, 2001", "mmm d, yyyy"), None);
|
||||
// A token the converter can't render leaves the value as it is.
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "mmmmm d"), "2001-02-03");
|
||||
assert_eq!(pattern_to_iso("Feb 3", "mmmmm d"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_fills_the_fields_and_adds_rows() {
|
||||
let form = layout_xml(&template_with_data(1, DATA)).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user