Skip to content

Excel: a percent column rejects European decimals that a number column accepts #143

Description

@dvejsada

Found while working on #126.

What happens. _apply_column_type() in xlsx_tools/helpers.py normalises thousands and decimal separators for the number and currency branches, but not for percent. The percent branch calls float() on the raw text, so a European decimal fails to parse and the cell silently stays text — no number, no percent format, no warning.

Reproduced against b36ebfb:

types:number       cell '4,3'          -> NUMBER 4.3
types:percent      cell '4,3'          -> text   '4,3'
types:number       cell '1.234,56'     -> NUMBER 1234.56
types:percent      cell '1.234,56'     -> text   '1.234,56'
types:currency:€   cell '1.234,56 €'   -> NUMBER 1234.56
types:percent      cell '4.3'          -> NUMBER 0.043

The same value, in the same spreadsheet, is a number in one column and a string in the next — decided only by which type the column declares. A 4,3% written by a European author lands as text, so it does not sum, chart or sort.

Why. helpers.py:963-966:

if type_lower.startswith('percent'):
    explicit = type_spec.split(':', 1)[1].strip() if ':' in type_spec else ''
    numeric_str = clean.rstrip('%').strip()
    try:
        cell.value = float(numeric_str) / 100

number (:930) and currency (:916) both pass their text through _strip_thousands_separators() first. That helper already handles English 1,234.56, European 1.234,56 and bare thousands 1,234, and its docstring records the 1,5 → 15 silent 10x error that motivated it. The percent branch never got the same treatment.

Fix. Route the percent branch through _strip_thousands_separators() like the other two:

numeric_str = _strip_thousands_separators(clean.rstrip('%').strip())

Two things to check while doing it, since they are the same omission one layer over:

  • resolve_cell() (:403-410) detects a trailing % outside the types: directive entirely and calls float(percent_body) directly — same gap, different entry point. An untyped 4,3% in a plain table has the same problem.
  • _percent_decimals() (:162-165) already does .replace(',', '.') before counting fraction digits, so it is more lenient than the coercion that feeds it. Once the coercion accepts 4,3%, confirm the two agree on the resulting format — a mismatch between a learner and its coercion is exactly what xlsx: a formula in a percent column gets a flat 0% format #126 fixed for a different pair.

Warning channel. Whatever the fix, a percent cell that cannot be parsed should say so rather than quietly becoming text — xlsx_tools/warnings.py exists for this since #114, and "the caller asked for a percent and got a string" is a warning-severity substitution by the rubric in AGENTS.md.

Tests. tests/test_xlsx_percent_columns.py covers the types: percent path; a European-decimal case belongs beside the existing ones, and should be confirmed to fail on the commit before the fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions