use std::collections::BTreeMap;
use crate::blocks::{Block, BlockKind};
use crate::header_rows::tables_in;
const CURRENCY: [char; 4] = ['$', '\u{20ac}', '\u{a3}', '\u{a5}'];
pub(crate) fn move_trailing_signs(blocks: &mut [Block]) {
for (first, end) in tables_in(blocks) {
let mut at: BTreeMap<(u16, u16), usize> = BTreeMap::new();
for (i, block) in blocks.iter().enumerate().take(end).skip(first) {
if let BlockKind::TableCell {
row,
column,
row_span: 1,
column_span: 1,
..
} = block.kind
{
at.insert((row, column), i);
}
}
for (&(row, column), &i) in &at {
let Some(&right) = at.get(&(row, column + 1)) else {
continue;
};
let text = blocks[i].text.trim_end();
let Some(sign) = text.chars().last().filter(|c| CURRENCY.contains(c)) else {
continue;
};
let rest = &text[..text.len() - sign.len_utf8()];
if !rest.ends_with(char::is_whitespace) {
continue;
}
let before = rest.trim_end();
if before.is_empty() || !opens_with_figure(&blocks[right].text) {
continue;
}
let before = before.to_string();
let joined = format!("{sign}{}", blocks[right].text.trim_start());
blocks[i].text = before;
blocks[right].text = joined;
}
}
}
fn opens_with_figure(text: &str) -> bool {
text.trim_start().chars().next().is_some_and(|c| {
c.is_ascii_digit() || c == '(' || c == '\u{2014}' || c == '-' || c == '\u{2013}'
})
}
#[cfg(test)]
mod tests {
use super::*;
fn cell(row: u16, column: u16, text: &str) -> Block {
Block {
kind: BlockKind::TableCell {
row,
column,
row_span: 1,
column_span: 1,
header: false,
},
page: 0,
bbox: [
100.0 * column as f64,
700.0 - 12.0 * row as f64,
100.0 * column as f64 + 50.0,
710.0 - 12.0 * row as f64,
],
text: text.to_string(),
spans: Vec::new(),
index: 0,
continues_previous: false,
confidence: None,
grid: None,
table_origin: Default::default(),
trace_learned: None,
}
}
fn texts(blocks: &[Block]) -> Vec<&str> {
blocks.iter().map(|b| b.text.as_str()).collect()
}
#[test]
fn a_trailing_sign_moves_to_the_figure_on_its_right() {
let mut blocks = vec![
cell(0, 0, "Korea"),
cell(0, 1, "26% $"),
cell(0, 2, "2,962"),
cell(0, 3, "US$"),
cell(0, 4, "40"),
cell(1, 0, "Taiwan"),
cell(1, 1, "17% $"),
cell(1, 2, "(743)"),
cell(1, 3, "Cash $"),
cell(1, 4, "\u{2014}"),
];
move_trailing_signs(&mut blocks);
assert_eq!(
texts(&blocks),
vec![
"Korea",
"26%",
"$2,962",
"US$",
"40",
"Taiwan",
"17%",
"$(743)",
"Cash",
"$\u{2014}"
]
);
}
#[test]
fn a_sign_before_a_word_or_at_the_table_edge_stays() {
let mut blocks = vec![
cell(0, 0, "in $"),
cell(0, 1, "millions"),
cell(0, 2, "paid $"),
];
move_trailing_signs(&mut blocks);
assert_eq!(texts(&blocks), vec!["in $", "millions", "paid $"]);
}
#[test]
fn a_bare_sign_is_left_to_the_serializer() {
let mut blocks = vec![cell(0, 0, "$"), cell(0, 1, "12")];
move_trailing_signs(&mut blocks);
assert_eq!(texts(&blocks), vec!["$", "12"]);
}
}