Private/ConvertFrom-VellumPdfNumberText.ps1
|
function ConvertFrom-VellumPdfNumberText { <# .SYNOPSIS Reads a string that consists only of a number as a [decimal], or returns $null. .DESCRIPTION A pure function (no document, no current culture) used by the table and list cmdlets to recognise numbers in string input. The result is a [decimal] that keeps the scale of the text ('10.00' stays 10.00), or $null when the text is not a number or cannot be read with certainty. Without -InputCulture the separators are detected from the text (see Get-VellumPdfNumberShape for the rules): '1.444,50' and '1,976.08' are read by their last separator, '1.234.567' is grouping, '55433.34' and '66443,85' are decimals, and '007', '10.0.0.1', '1 234', 'EUR5', '5%' and '1e5' are not numbers. An ambiguous text such as '1,234' or '1.234' (one separator, exactly three digits after it, at most three before) is read by -Hint, the decimal mark that other values showed (see Get-VellumPdfNumberHint); without a hint it returns $null. With -InputCulture the text is read strictly with that culture's decimal and grouping separators, no detection and no hint: 'de-DE' reads '1.234' as 1234 and '12.34' not at all. Leading zeros in a multi-digit integer part and invalid grouping are still refused. .PARAMETER Text The text to read. .PARAMETER InputCulture Read the text with this culture instead of detecting the separators. .PARAMETER Hint The decimal mark ('.' or ',') that other values of the same column or list revealed; only used to resolve an ambiguous text. #> [CmdletBinding()] [OutputType([decimal])] param( [AllowNull()] [AllowEmptyString()] [string]$Text, [AllowNull()] [cultureinfo]$InputCulture, [ValidateSet('.', ',')] [string]$Hint ) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $trimmed = $Text.Trim() $result = [decimal]0 $styles = [System.Globalization.NumberStyles]::AllowLeadingSign -bor [System.Globalization.NumberStyles]::AllowDecimalPoint $invariant = [cultureinfo]::InvariantCulture if ($InputCulture) { $format = $InputCulture.NumberFormat $decimalSeparator = [regex]::Escape($format.NumberDecimalSeparator) $groupSeparator = $format.NumberGroupSeparator # Culture group separators that are spaces (fr-FR uses U+202F) are also typed as a plain or no-break space. $groupPattern = if ($groupSeparator -eq '') { $null } elseif ([string]::IsNullOrWhiteSpace($groupSeparator)) { '[ \u00a0\u202f]' } else { [regex]::Escape($groupSeparator) } $signs = @('+', '-', $format.PositiveSign, $format.NegativeSign) | Where-Object { $_ } | Select-Object -Unique | ForEach-Object { [regex]::Escape($_) } $integerPattern = if ($groupPattern) { "[0-9]+(?:$groupPattern[0-9]+)*" } else { '[0-9]+' } $match = [regex]::Match($trimmed, "^(?<sign>$($signs -join '|'))?(?<int>$integerPattern)(?:$decimalSeparator(?<frac>[0-9]+))?$") if (-not $match.Success) { return $null } # @(...) around the if: an if-expression would unroll a one-element array into a string. $groups = @(if ($groupPattern) { [regex]::Split($match.Groups['int'].Value, $groupPattern) } else { $match.Groups['int'].Value }) if ($groups.Count -gt 1) { if ($groups[0].Length -gt 3 -or $groups[0][0] -eq '0') { return $null } for ($i = 1; $i -lt $groups.Count; $i++) { if ($groups[$i].Length -ne 3) { return $null } } } elseif ($groups[0].Length -gt 1 -and $groups[0][0] -eq '0') { return $null } $negative = $match.Groups['sign'].Success -and $match.Groups['sign'].Value -in @('-', $format.NegativeSign) $normalized = ($negative ? '-' : '') + ($groups -join '') + $(if ($match.Groups['frac'].Success) { '.' + $match.Groups['frac'].Value }) if ([decimal]::TryParse($normalized, $styles, $invariant, [ref]$result)) { return $result } return $null } $shape = Get-VellumPdfNumberShape -Text $trimmed if ($shape.Kind -eq 'None') { return $null } if ($shape.Kind -eq 'Ambiguous') { if (-not $Hint) { return $null } # The hint is the decimal mark: the separator is a decimal mark when it is the hint, grouping otherwise. $normalized = $shape.Sign + $(if ($shape.Separator -eq $Hint) { "$($shape.Left).$($shape.Right)" } else { $shape.Left + $shape.Right }) } else { $normalized = $shape.Sign + $shape.Integer + $(if ($shape.Fraction -ne '') { '.' + $shape.Fraction }) } if ([decimal]::TryParse($normalized, $styles, $invariant, [ref]$result)) { return $result } $null } |