Whether v is WHOLLY a signed decimal number, optionally in exponent form:
-0.35, +1, -1.5e-3, -.5, -1.. Anchored at BOTH ends, so -0.35=cmd
is not one and 1,000 is not one either.
Lives here, in a package with no dependencies, because three different
callers need it and only two of them can afford @ifc-lite/export: the CSV
formula guard (packages/export/src/csv-cell.ts) exempts a genuine number
from the +/- trigger so a spreadsheet column still sums (#1772), and
@ifc-lite/lists needs the same decision in its own CSV writer.
rust/export/src/csv_cell.rs::is_wholly_numeric decides the same language on
the other side of the language boundary; what holds those two together is the
shared vector fixture, not a promise that they look alike.
The regex this replaces — /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/ —
is QUADRATIC on a failing match: \d+\.?\d* retries at every split point
before the engine gives up. This scan is linear, which is the whole reason it
is spelled out by hand. (For scale, on - + 60k digits + one letter the
regex took ~1.8s and the scan ~1ms; the ratio is the point, not the numbers.)
Cell text comes from IFC properties, so the input is attacker-controllable and
a leading - is exactly what reaches this check. \d is ASCII-only in JS, and
this scan matches that deliberately: a full-width or Arabic-Indic digit is NOT
a number here and stays guarded.
Whether
vis WHOLLY a signed decimal number, optionally in exponent form:-0.35,+1,-1.5e-3,-.5,-1.. Anchored at BOTH ends, so-0.35=cmdis not one and1,000is not one either.Lives here, in a package with no dependencies, because three different callers need it and only two of them can afford
@ifc-lite/export: the CSV formula guard (packages/export/src/csv-cell.ts) exempts a genuine number from the+/-trigger so a spreadsheet column still sums (#1772), and@ifc-lite/listsneeds the same decision in its own CSV writer.rust/export/src/csv_cell.rs::is_wholly_numericdecides the same language on the other side of the language boundary; what holds those two together is the shared vector fixture, not a promise that they look alike.The regex this replaces —
/^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/— is QUADRATIC on a failing match:\d+\.?\d*retries at every split point before the engine gives up. This scan is linear, which is the whole reason it is spelled out by hand. (For scale, on-+ 60k digits + one letter the regex took ~1.8s and the scan ~1ms; the ratio is the point, not the numbers.)Cell text comes from IFC properties, so the input is attacker-controllable and a leading
-is exactly what reaches this check.\dis ASCII-only in JS, and this scan matches that deliberately: a full-width or Arabic-Indic digit is NOT a number here and stays guarded.