mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
fix(xfdf): keep merged spreadsheet cells from starting a formula
Merge Data Files into Spreadsheet writes field names and values from other people's files as CSV cells, escaping only the delimiters. A cell starting with =, +, - or @ is evaluated as a formula when the CSV is opened in a spreadsheet program. Such cells, after any leading whitespace, are now written with a leading apostrophe; finite numbers such as -12.5 are left as they are.
This commit is contained in:
+17
-1
@@ -1287,6 +1287,10 @@ pub fn data_values(bytes: &[u8]) -> Result<Vec<(String, Vec<String>)>, DataError
|
||||
|
||||
/// Merge Data Files into Spreadsheet: one CSV row per file, one column per field name (in the
|
||||
/// order they first appear).
|
||||
///
|
||||
/// The names and values come from other people's files, and spreadsheet programs evaluate a cell
|
||||
/// that starts with `=`, `+`, `-` or `@` (after any leading whitespace) as a formula. Such a cell
|
||||
/// is written with a leading `'`, which keeps it text; a plain number such as `-12.5` is not.
|
||||
pub fn merge_csv(files: &[Vec<(String, Vec<String>)>]) -> String {
|
||||
let mut columns: Vec<&str> = Vec::new();
|
||||
for f in files {
|
||||
@@ -1296,7 +1300,19 @@ pub fn merge_csv(files: &[Vec<(String, Vec<String>)>]) -> String {
|
||||
}
|
||||
}
|
||||
}
|
||||
let q = |s: &str| if s.contains([',', '"', '\n', '\r', '\t']) { format!("\"{}\"", s.replace('"', "\"\"")) } else { s.to_string() };
|
||||
let literal = |s: &str| {
|
||||
let t = s.trim_start();
|
||||
let formula = match t.chars().next() {
|
||||
Some('=' | '@') => true,
|
||||
Some('+' | '-') => !t.parse::<f64>().is_ok_and(f64::is_finite),
|
||||
_ => false,
|
||||
};
|
||||
if formula { format!("'{s}") } else { s.to_string() }
|
||||
};
|
||||
let q = |s: &str| {
|
||||
let s = literal(s);
|
||||
if s.contains([',', '"', '\n', '\r', '\t']) { format!("\"{}\"", s.replace('"', "\"\"")) } else { s }
|
||||
};
|
||||
let mut out = columns.iter().map(|c| q(c)).collect::<Vec<_>>().join(",");
|
||||
out.push('\n');
|
||||
for f in files {
|
||||
|
||||
@@ -1702,3 +1702,18 @@ fn an_imported_rect_only_or_transparent_redaction_mark_is_drawn_for_review() {
|
||||
assert!(mark.get(b"CA").is_none() && mark.get(b"ca").is_none(), "the mark can be drawn transparent: {mark:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merged_spreadsheet_cells_never_start_a_formula() {
|
||||
let row = vec![
|
||||
("=HYPERLINK(\"http://x\")".to_string(), vec!["@SUM(A1)".to_string()]),
|
||||
("Total".to_string(), vec!["-2+3".to_string()]),
|
||||
("Plus".to_string(), vec!["\t+1+cmd".to_string()]),
|
||||
("Balance".to_string(), vec!["-12.5".to_string()]),
|
||||
("\tName".to_string(), vec!["Ada".to_string()]),
|
||||
];
|
||||
let csv = merge_csv(&[row]);
|
||||
let lines: Vec<&str> = csv.lines().collect();
|
||||
assert_eq!(lines[0], "\"'=HYPERLINK(\"\"http://x\"\")\",Total,Plus,Balance,\"\tName\"");
|
||||
assert_eq!(lines[1], "'@SUM(A1),'-2+3,\"'\t+1+cmd\",-12.5,Ada");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user