diff --git a/Export-Excel.Impl.Tests.ps1 b/Export-Excel.Impl.Tests.ps1 new file mode 100644 index 00000000..5d25b77a --- /dev/null +++ b/Export-Excel.Impl.Tests.ps1 @@ -0,0 +1,725 @@ +Set-StrictMode -Version Latest +Import-Module $PSScriptRoot -Force -Scope Global + +# Bring New-CellData helpers into scope. +. (Join-Path $PSScriptRoot "$(Split-Path -Leaf $PSCommandPath)".Replace(".Tests.ps1", ".ps1")) + +function New-TestWorkbook { + $testWorkbook = Join-Path $PSScriptRoot test.xlsx + if (Test-Path $testWorkbook) { + rm $testWorkbook -Force + } + $testWorkbook +} + +function Remove-TestWorkbook { + New-TestWorkbook | Out-Null +} + +function Get-DateFormatDefault { + "mmm/dd/yyyy hh:mm:ss" +} + +Describe "DoubleTryParse" { + Context "Parsing decimal strings" { + + $commaCulture = (0.3 | Out-String -Stream) -eq "0,3" + + It "Converts 0.1 to 0.1 with Any/InvariantInfo" { + $double = 0 + # https://msdn.microsoft.com/en-us/library/system.globalization.numberstyles(v=vs.110).aspx + [double]::TryParse("0.1", [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$double) | Should Be $true + $double | Should Be 0.1 + "$double" | Should Be "0.1" + if ($commaCulture -eq $true) { + $double | Out-String -Stream | Should Be "0,1" + } + } + It "Converts 0,1 to 1 with Any/InvariantInfo" { + $double = 0 + [double]::TryParse("0,1", [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$double) | Should Be $true + $double | Should Be 1 + "$double" | Should Be "1" + $double | Out-String -Stream | Should Be "1" + } + It "Converts 0,3 to 3 with Any/InvariantInfo" { + $double = 0 + [double]::TryParse("0,3", [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$double) | Should Be $true + $double | Should Be 3 + "$double" | Should Be "3" + if ($commaCulture -eq $true) { + $double | Out-String -Stream | Should Be "3" + } + } + + if ($commaCulture -eq $true) { + It "Converts 0,3 to 0,3 with Any/CurrentInfo" { + $double = 0 + [double]::TryParse("0,3", [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::CurrentInfo, [ref]$double) | Should Be $true + $double | Should Be 0.3 + "$double" | Should Be "0.3" + $double | Out-String -Stream | Should Be "0,3" + } + It "Fails to convert 0.3 to 0,3 with Any/CurrentInfo" { + $double = 0 + [double]::TryParse("0.3", [System.Globalization.NumberStyles]::Any, [System.Globalization.NumberFormatInfo]::CurrentInfo, [ref]$double) | Should Be $false + $double | Should Be 0 + "$double" | Should Be "0" + } + } + } +} + +Describe "NewCellData" { + + Context "Piping [string] inputs" { + + It "Converts numeric strings to [double]" { + New-CellData -TargetData "12345" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 12345 + $_.Format | Should Be "General" + } + } + + It "Leaves numeric strings with leading zeroes as strings" { + New-CellData -TargetData "012345" | % { + $_.Value -is [string] | Should Be $true + $_.Value | Should Be "012345" + $_.Format | Should Be "General" + } + } + + It "Numeric strings with leading zeroes that are non-integers are treated as numbers" { + New-CellData -TargetData "0.01" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 0.01 + $_.Format | Should Be "General" + } + } + + It "Leaves numeric strings as text when using -ForceText switch" { + New-CellData -TargetData "12345" -ForceText | % { + $_.Value -is [string] | Should Be $true + $_.Value | Should Be "12345" + $_.Format | Should Be "General" + } + } + + It "Converts date strings to the default date format" { + $date = Get-Date + New-CellData -TargetData "$date" | % { + $_.Value -is [DateTime] | Should Be $true + "$($_.Value)" | Should Be "$date" + $_.Format | Should Be (Get-DateFormatDefault) + } + } + + It "Leaves numeric strings with starting and trailing whitespace as strings" { + New-CellData -TargetData " 12345" | % { + $_.Value -is [string] | Should Be $true + $_.Value | Should Be " 12345" + $_.Format | Should Be "General" + } + New-CellData -TargetData "12345 " | % { + $_.Value -is [string] | Should Be $true + $_.Value | Should Be "12345 " + $_.Format | Should Be "General" + } + } + + It "Keeps percentage strings as text" { + New-CellData -TargetData "90%" | % { + $_.Value -is [string] | Should Be $true + $_.Value | Should Be "90%" + $_.Format | Should Be "General" + } + } + + $nfiZa = [CultureInfo]::GetCultureInfo("en-ZA").NumberFormat + + It "Converts 12,34 to 12.34 with -NumberFormatInfo ZA" { + New-CellData -TargetData "12,34" -NumberFormatInfo $nfiZa | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 12.34 + $_.Format | Should Be "General" + } + } + + It "Converts 12.34 to 12.34" { + New-CellData -TargetData "12.34" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 12.34 + $_.Format | Should Be "General" + } + } + + It "Converts 12,345 to 12.345 with -NumberFormatInfo ZA" { + New-CellData -TargetData "12,345" -NumberFormatInfo $nfiZa | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 12.345 + $_.Format | Should Be "General" + } + } + + It "Converts 12.345 to 12.345" { + New-CellData -TargetData "12.345" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 12.345 + $_.Format | Should Be "General" + } + } + + It "Converts 0,1 to 0.1 with -NumberFormatInfo ZA" { + New-CellData -TargetData "0,1" -NumberFormatInfo $nfiZa | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 0.1 + $_.Format | Should Be "General" + } + } + + It "Converts 0.1 to 0.1" { + New-CellData -TargetData "0.1" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 0.1 + $_.Format | Should Be "General" + } + } + + It "Converts 0,01 to 0.01 with -NumberFormatInfo ZA" { + New-CellData -TargetData "0,01" -NumberFormatInfo $nfiZa | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 0.01 + $_.Format | Should Be "General" + } + } + + It "Converts 0.01 to 0.01" { + New-CellData -TargetData "0.01" | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 0.01 + $_.Format | Should Be "General" + } + } + + } + + Context "Piping [DateTime] inputs" { + It "Outputs the same [DateTime]" { + $date = Get-Date + New-CellData -TargetData $date | % { + $_.Value -is [DateTime] | Should Be $true + $_.Value | Should Be $date + $_.Format | Should Be (Get-DateFormatDefault) + } + } + } + + Context "Piping numeric value type inputs" { + It "Outputs [int] for [int] input" { + New-CellData -TargetData 123 | % { + $_.Value -is [int] | Should Be $true + $_.Value | Should Be 123 + $_.Format | Should Be "General" + } + } + It "Outputs [double] for [double] input" { + New-CellData -TargetData ([double]123) | % { + $_.Value -is [double] | Should Be $true + $_.Value | Should Be 123 + $_.Format | Should Be "General" + } + } + It "Outputs [long] for [long] input" { + New-CellData -TargetData ([long]123) | % { + $_.Value -is [long] | Should Be $true + $_.Value | Should Be 123 + $_.Format | Should Be "General" + } + } + } + + Context "Piping other value type inputs" { + It "Outputs [bool] for [bool] input" { + New-CellData -TargetData $true | % { + $_.Value -is [bool] | Should Be $true + $_.Value | Should Be $true + "$($_.Value)" | Should Be "True" + $_.Format | Should Be "General" + } + } + } + + Context "Piping CSV data" { + $csvData = @" + Name, ID, Age, Birthday + Aa, 123, 82, 12 January 1984 + BB, 012, 34, 12 August 1955 + CC, 901, 44, 30 May 1801 +"@ | ConvertFrom-Csv + It "Converts property values to appropriate types" { + $csvData | Select-TargetData Name | New-CellData | % { + $_.Value -is [string] | Should Be $true + $_.Format | Should Be "General" + } + $csvData | Select-TargetData ID | New-CellData -ForceText | % { + $_.Value -is [string] | Should Be $true + $_.Format | Should Be "General" + } + $csvData | Select-TargetData Age | New-CellData | % { + $_.Value -is [double] | Should Be $true + $_.Format | Should Be "General" + } + $csvData | Select-TargetData Age | New-CellData -IgnoreText | % { + $_.Value -is [string] | Should Be $true + $_.Format | Should Be "General" + } + $csvData | Select-TargetData Birthday | New-CellData | % { + $_.Value -is [DateTime] | Should Be $true + $_.Format | Should Be (Get-DateFormatDefault) + } + $csvData | Select-TargetData Birthday | New-CellData -ForceText | % { + $_.Value -is [string] | Should Be $true + $_.Format | Should Be "General" + } + } + } + + Context "Piping Get-Process data" { + $process = Get-Process powershell + It "Converts property values to appropriate types" { + $process | Select-TargetData StartTime | New-CellData | % { + $_.Value -is [DateTime] | Should Be $true + $_.Format | Should Be (Get-DateFormatDefault) + } + $process | Select-TargetData Id | New-CellData | % { + $_.Value -is [int] | Should Be $true + $_.Format | Should Be "General" + } + $process | Select-TargetData ProcessName | New-CellData | % { + $_.Value -is [string] | Should Be $true + $_.Format | Should Be "General" + } + $process | Select-TargetData Handles | New-CellData | % { + $_.Value -is [int] | Should Be $true + $_.Format | Should Be "General" + } + } + } + + Context "With Export-Excel and PsCustomObject" { + $workbook = New-TestWorkbook + $process = Get-Process powershell | Select-Object Id, StartTime, PriorityClass, TotalProcessorTime + $xlPkg = $process | Export-Excel $workbook -PassThru + It "Produces correctly formatted sheet for Get-Process" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A2:A"] # Id + $col | % { + $_.Value -is [int] | Should Be $true + $_.Style.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B2:B"] # StartTime + $col | Select-Object -ExpandProperty Value | % { + $_ -is [DateTime] | Should Be $true + } + $col | % { + if ($_.Value -ne $null) { + $_.Style.NumberFormat.Format | Should Be (Get-DateFormatDefault) + } + else { + $_.Style.NumberFormat.Format | Should Be "General" + } + } + $col = $ws.Cells["C2:C"] # PriorityClass + $col | Select-Object -ExpandProperty Value | % { + $_ -is [System.Diagnostics.ProcessPriorityClass] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["D2:D"] # TotalProcessorTime + $col | Select-Object -ExpandProperty Value | % { + $_ -is [TimeSpan] | Should Be $true + } + $col | % { + if ($_.Value -ne $null) { + $_.Style.NumberFormat.Format | Should Be "hh:mm:ss" + } + else { + $_.Style.NumberFormat.Format | Should Be "General" + } + } + } + $xlPkg.Save() + $xlPkg.Dispose() + + $columnOptions = @{ + "*" = @{ ForceText = $true } + } + $xlPkg = $process | Export-Excel $workbook -PassThru -ColumnOptions $columnOptions + It "Responds to -ColumnOptions" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A:D"] + $col | Select-Object | % { + $_.Style.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + } + + Context "With Export-Excel and [valuetype]" { + $workbook = New-TestWorkbook + $xlPkg = "12 January 1984" | Export-Excel $workbook -PassThru + It "Produces [datetime] for date/time [string]" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A1"] # First cell. + $col | Select-Object -ExpandProperty Value | % { + $_ -is [DateTime] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be (Get-DateFormatDefault) + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $xlPkg = "0123" | Export-Excel $workbook -PassThru + It "Produces [string] for numeric [string] with leading zeroes" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A1"] # First cell. + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + $_ | Should Be "0123" + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $xlPkg = "123" | Export-Excel $workbook -PassThru + It "Produces [double] for numeric [string]" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A1"] # First cell. + $col | Select-Object -ExpandProperty Value | % { + $_ -is [double] | Should Be $true + $_ | Should Be 123 + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $xlPkg = "123", 456, "034", $true, (Get-Date), [long]678, "12 January 1984" | Export-Excel $workbook -PassThru + It "Supports multi-valuetype array (with automatic string conversions)" { + $ws = $xlPkg.Workbook.WorkSheets[1] + & { + $col = $ws.Cells["A1"] + $col.Value -is [double] | Should Be $true # Automatic conversion from string to double. + $col.Value | Should Be 123 + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A2"] + $col.Value -is [int] | Should Be $true # No conversion. + $col.Value | Should Be 456 + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A3"] + $col.Value -is [string] | Should Be $true # Automatic conversion chose to remain as string. + $col.Value | Should Be "034" + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A4"] + $col.Value -is [bool] | Should Be $true + $col.Value | Should Be $true # No conversion. + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A5"] + $col.Value -is [datetime] | Should Be $true # No conversion. + $col.Style.NumberFormat.Format | Should Be (Get-DateFormatDefault) + } + & { + $col = $ws.Cells["A6"] + $col.Value -is [long] | Should Be $true + $col.Value | Should Be 678 # No conversion. + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A7"] + $col.Value -is [datetime] | Should Be $true # Automatic conversion from string to datetime. + $col.Style.NumberFormat.Format | Should Be (Get-DateFormatDefault) + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $columnOptions = @{ + 1 = @{ IgnoreText = $true } + } + $xlPkg = "123", 456, "034", $true, (Get-Date), [long]678, "12 January 1984" | Export-Excel $workbook -ColumnOptions $columnOptions -PassThru + It "Supports multi-valuetype array (with no string conversions)" { + $ws = $xlPkg.Workbook.WorkSheets[1] + & { + $col = $ws.Cells["A1"] + $col.Value -is [string] | Should Be $true + $col.Value | Should Be "123" # No automatic conversion of strings. + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A2"] + $col.Value -is [int] | Should Be $true + $col.Value | Should Be 456 + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A3"] + $col.Value -is [string] | Should Be $true + $col.Value | Should Be "034" # No automatic conversion of strings. + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A4"] + $col.Value -is [bool] | Should Be $true + $col.Value | Should Be $true + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A5"] + $col.Value -is [datetime] | Should Be $true + $col.Style.NumberFormat.Format | Should Be (Get-DateFormatDefault) + } + & { + $col = $ws.Cells["A6"] + $col.Value -is [long] | Should Be $true + $col.Value | Should Be 678 + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A7"] + $col.Value -is [string] | Should Be $true + $col.Value | Should Be "12 January 1984" # No automatic conversion of strings. + $col.Style.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + + $columnOptions = @{ + 1 = @{ IgnoreText = $true } + } + $xlPkg = "123", "456", "034" | Export-Excel $workbook -ColumnOptions $columnOptions -PassThru + It "Produces [string] for numeric [string] with -ColumnOptions" { + $ws = $xlPkg.Workbook.WorkSheets[1] + & { + $col = $ws.Cells["A1"] + $col.Value -is [string] | Should Be $true + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A2"] + $col.Value -is [string] | Should Be $true + $col.Style.NumberFormat.Format | Should Be "General" + } + & { + $col = $ws.Cells["A3"] + $col.Value -is [string] | Should Be $true + $col.Style.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $xlPkg = ([long]123) | Export-Excel $workbook -PassThru + It "Produces [long] for [long] input" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A1"] # First cell. + $col | Select-Object -ExpandProperty Value | % { + $_ -is [long] | Should Be $true + $_ | Should Be 123 + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $xlPkg = 123 | Export-Excel $workbook -PassThru + It "Produces [int] for [int] input" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A1"] # First cell. + $col | Select-Object -ExpandProperty Value | % { + $_ -is [int] | Should Be $true + $_ | Should Be 123 + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + } + + Context "With Export-Excel and CSV data" { + $workbook = New-TestWorkbook + $csvData = @" +Name, ID, Age, Birthday +Aa, 123, 82, 12 January 1984 +BB, 012, 34, 12 August 1955 +CC, 901, 44, 30 May 1901 +"@ | ConvertFrom-Csv + $xlPkg = $csvData | Export-Excel $workbook -DateTimeFormat "mmm/dd/yyyy" -PassThru + It "Produces Excel data with correct formatting" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A2:A"] # Name + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B1"] # ID + $col | Select-Object -ExpandProperty Value | % { + $_ | Should Be "ID" + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B2"] + $col | Select-Object -ExpandProperty Value | % { + $_ | Should Be 123 + $_ -is [double] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B3"] + $col | Select-Object -ExpandProperty Value | % { + $_ | Should Be "012" + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B4"] + $col | Select-Object -ExpandProperty Value | % { + $_ | Should Be 901 + $_ -is [double] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["C2:C"] # Age + $col | Select-Object -ExpandProperty Value | % { + $_ -is [double] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["D2:D"] # Birthday + $col | Select-Object -ExpandProperty Value | % { + $_ -is [DateTime] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "mmm/dd/yyyy" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $columnOptions = @{ + ID = @{ IgnoreText = $true} + 3 = @{ IgnoreText = $true } + Birthday = @{ DateTimeFormat = "mmm/dd/yyyy" } + } + + $xlPkg = $csvData | Export-Excel $workbook -ColumnOptions $columnOptions -PassThru + It "Produces Excel data with -ColumnOptions" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["B2:B"] # ID + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["C2:C"] # Age + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["D2:D"] # Birthday + $col | % { + $_.Style.NumberFormat.Format | Should Be "mmm/dd/yyyy" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + + $columnOptions = @{ + "*" = @{ IgnoreText = $true } + } + + $xlPkg = $csvData | Export-Excel $workbook -ColumnOptions $columnOptions -PassThru + It "Produces Excel data with -ColumnOptions *" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $col = $ws.Cells["A2:A"] # Name + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["B2:B"] # ID + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["C2:C"] # Age + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + $col = $ws.Cells["D2:D"] # Birthday + $col | Select-Object -ExpandProperty Value | % { + $_ -is [string] | Should Be $true + } + $col | Select-Object -ExpandProperty Style | % { + $_.NumberFormat.Format | Should Be "General" + } + } + $xlPkg.Save() + $xlPkg.Dispose() + # Invoke-Item $workbook + } + + Remove-TestWorkbook +} + diff --git a/Export-Excel.Impl.ps1 b/Export-Excel.Impl.ps1 new file mode 100644 index 00000000..c2649f04 --- /dev/null +++ b/Export-Excel.Impl.ps1 @@ -0,0 +1,489 @@ +Set-StrictMode -Version Latest + +<# +.SYNOPSIS + +A filter that selects a property as a new name. + +#> +filter Select-TargetData([string]$Property) { + $prop = $_ | Select-Object -ExpandProperty $Property + [PSCustomObject]@{ TargetData = $prop } +} + +<# + +.SYNOPSIS + +A helper function that creates a cell value format pair. + +#> +function New-ValueFormatPair { + [CmdletBinding()] + param( + [object]$Value, + [string]$Format + ) + [PSCustomObject][ordered]@{ Value = $Value; Format = $Format; } +} + +<# + +.SYNOPSIS + +A helper function that parses a double and returns some other helpful information. + +.LINK + +http://stackoverflow.com/questions/25211148/detect-iformatprovider-or-cultureinfo-from-string-using-vb-net + +#> +function New-NumberFromText { + [CmdletBinding()] + param( + [string]$Text, + # https://msdn.microsoft.com/en-us/library/system.globalization.numberstyles(v=vs.110).aspx + [System.Globalization.NumberStyles]$NumberStyles=[System.Globalization.NumberStyles]::Any, + # https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo(v=vs.110).aspx + [System.Globalization.NumberFormatInfo]$NumberFormatInfo=[System.Globalization.NumberFormatInfo]::InvariantInfo, + [switch]$Offer, + [string[]]$OfferCultures=@("en-US", "en-GB") + ) + + $result = [PSCustomObject]@{ + Text = $Text + Number = 0 + ParseOkay = $false + NumberStyles = $NumberStyles + NumberToString = $null + GoodMatch = $false + } + + $double = 0 + if ([double]::TryParse($result.Text, $result.NumberStyles, $NumberFormatInfo, [ref]$double)) { + $result.Number = $double + $result.ParseOkay = $true + $result.NumberToString = $result.Number.ToString($NumberFormatInfo) + if ($result.Text -eq $result.NumberToString) { + # Cheaper test for well-formed $Text values. + $result.GoodMatch = $true + } + else { + + # More expensive test for trickier cases. + + # The $NumberToString value will typically have more info than the + # $Text value, due to limitations in the binary representation of + # numbers. In some cases a $Text value of "0,3" produces a + # $NumberToString value of "3", which is not a good match for the + # $Text value. Guard against this. The $Text value might also + # include other symbols, like +, - or currency, that will not be + # included in NumberToString. Ideally, the incoming $Text value + # should already be cleaned up as much as possible. + + if ($result.NumberToString -like "*$($result.Text)*") { + $result.GoodMatch = $true + } + } + } + + # Offer recommendations. + + if ($result.ParseOkay -and !$result.GoodMatch -and $Offer.IsPresent) { + + Write-Warning "Information loss detected. Got number '$($result.Number)' with format '$($result.NumberToString)' for input '$Text'. Use -Verbose for NumberFormatInfo recommendations." + + if ($VerbosePreference -ne "SilentlyContinue") { + # Check invariant culture. + if ($NumberFormatInfo -ne ([System.Globalization.NumberFormatInfo]::InvariantInfo)) { + $nft = New-NumberFromText -Text $Text -NumberStyles $NumberStyles -NumberFormatInfo ([System.Globalization.NumberFormatInfo]::InvariantInfo) + Write-Verbose "For number '$($nft.Number)' with format '$($nft.NumberToString)' use NumberFormatInfo 'InvariantInfo'." + } + + # Check current culture. + if ($NumberFormatInfo -ne ([System.Globalization.NumberFormatInfo]::CurrentInfo)) { + $nft = New-NumberFromText -Text $Text -NumberStyles $NumberStyles -NumberFormatInfo ([System.Globalization.NumberFormatInfo]::CurrentInfo) + Write-Verbose "For number '$($nft.Number)' with format '$($nft.NumberToString)' use NumberFormatInfo 'CurrentInfo'." + } + + # Check user provided cultures. + $OfferCultures | % { + $culture = $_ + $nfi = [CultureInfo]::GetCultureInfo($culture).NumberFormat + $nft = New-NumberFromText -Text $Text -NumberStyles $NumberStyles -NumberFormatInfo $nfi + Write-Verbose "For number '$($nft.Number)' with format '$($nft.NumberToString)' use NumberFormatInfo '[CultureInfo]::GetCultureInfo('$culture').NumberFormat'." + } + } + } + + # Write-Verbose $result + $result +} + +<# +.SYNOPSIS + +Tests to see if an object is a numeric type. + +#> +function Test-NumericType { + [CmdletBinding()] + param( + [object]$Object + ) + if ($Object -is [ValueType]) { + $Object -is [double] -or $Object -is [int16] -or $Object -is [int32] -or $Object -is [int64] ` + -or $Object -is [sbyte] -or $Object -is [uint16] -or $Object -is [uint32] -or $Object -is [uint64] ` + -or $Object -is [float] -or $Object -is [byte] -or $Object -is [decimal] + } + else { + $false + } +} + +<# +.SYNOPSIS + +Create a new column options cache object. + +.DESCRIPTION + +The properties of the returned object are: + +Cache - Stores the overrides for each column. Calculated once. + +Options - A reference to the incoming table. Used to create the cache. + +Prototype - The prototype for cache entries. It contains the default values. + +#> +function New-ColumnOptionsCache { + [CmdletBinding()] + param( + [hashtable]$Table=@{}, + # Number + [string]$NumberFormat="General", + # https://msdn.microsoft.com/en-us/library/system.globalization.numberstyles(v=vs.110).aspx + [System.Globalization.NumberStyles]$NumberStyles=[System.Globalization.NumberStyles]::Any, + # https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo(v=vs.110).aspx + [System.Globalization.NumberFormatInfo]$NumberFormatInfo=[System.Globalization.NumberFormatInfo]::InvariantInfo, + # DateTime + [string]$DateTimeFormat="mmm/dd/yyyy hh:mm:ss", + # https://msdn.microsoft.com/en-us/library/system.globalization.datetimestyles(v=vs.110).aspx + [System.Globalization.DateTimeStyles]$DateTimeStyles=[System.Globalization.DateTimeStyles]::None, + # https://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo(v=vs.110).aspx + [System.Globalization.DateTimeFormatInfo]$DateTimeFormatInfo=[System.Globalization.DateTimeFormatInfo]::InvariantInfo, + # TimeSpan + [string]$TimeSpanFormat="hh:mm:ss" + ) + [PSCustomObject]@{ + Cache = [ordered]@{} + Options = $Table + Prototype = [ordered]@{ + IgnoreText = $false + ForceText = $false + DateTimeFormat = $DateTimeFormat + NumberFormat = $NumberFormat + NumberStyles = $NumberStyles + DateTimeStyles = $DateTimeStyles + NumberFormatInfo = $NumberFormatInfo + DateTimeFormatInfo = $DateTimeFormatInfo + TimeSpanFormat = $TimeSpanFormat + TargetData = $null + } + } +} +<# +.SYNOPSIS + +Returns the column options for a particular column, given a cache created by +New-ColumnOptionsCache. Column options are strongly associated with the column +index. + +.PARAMETER CacheObject + +A cache object created by New-ColumnOptionsCache. + +.PARAMETER ColumnIndex + +The index of the column whose options we are requesting. + +.PARAMETER ColumnName + +The optional name of the property associated with the column. + +#> +function Get-ColumnOptions { + [CmdletBinding()] + param( + [Parameter(Mandatory=$true, HelpMessage="A cache object created by New-ColumnOptionsCache.")] + [PSCustomObject]$CacheObject, + [Parameter(Mandatory=$true, HelpMessage="The index of the column whose options we are requesting.")] + [string]$ColumnIndex, + [string]$ColumnName, + [object]$TargetData + ) + + $colOpts = [PSCustomObject]($CacheObject.Cache[$ColumnIndex]) + if ($colOpts -eq $null) { + $colOpts = [PSCustomObject]($CacheObject.Prototype) + if ($CacheObject.Options -ne $null) { + # The cache contains no options associated for the particular column + # index. Let's create the options for it based on the cache prototype. + $colOptsTable = $CacheObject.Options + foreach ($colPatternOptsPair in $colOptsTable.GetEnumerator()) { + # Iterate through every option pair in the options table, and find + # all options associated with the given $ColumnIndex and + # $ColumnName. Use those options to update our prototype. If an + # option exists in the option table that does not exist in the + # prototype, then throw an error, but make sure that handle + # -ErrorAction SilentlyContinue won't break anything. + $colPattern = $colPatternOptsPair.Name + if ($ColumnIndex -like $colPattern) { + # An option entry was found for the $ColumnIndex. + $colPatternOptsTable = $colPatternOptsPair.Value + foreach ($patternOptsPair in $colPatternOptsTable.GetEnumerator()) { + $optName = $patternOptsPair.Name + $colOpts.$optName = $patternOptsPair.Value + } + } + elseif ($ColumnName -like $colPattern) { + # An option entry was found for the $ColumnName. + $colPatternOptsTable = $colPatternOptsPair.Value + foreach ($patternOptsPair in $colPatternOptsTable.GetEnumerator()) { + $optName = $patternOptsPair.Name + $colOpts.$optName = $patternOptsPair.Value + } + } + } + } + + $CacheObject.Cache[$ColumnIndex] = $colOpts + + Write-Verbose "Column options for '$ColumnIndex'/'$ColumnName': $colOpts)" + } + $newOpts = [PSCustomObject]$colOpts + $newOpts.TargetData = $TargetData + $newOpts +} + +<# +.SYNOPSIS + +This function determines the desired cell value and format for the incoming +data. Incoming strings can be interpreted as [DateTime], [double], etc, unless +the -IgnoreText switch is used. Incoming objects that are not strings, can +have their formatting detected to some degree, or they can be converted to +string without further interpretation using the -ForceText switch. This +function found inspiration from the LoadFrom and ConvertData methods of +EPPlus/ExcelRangeBase.cs. + +.PARAMETER ForceText + +This means that incoming objects (string or non-string) will be treated as +text, and their string values will not be interpreted. The cell value will be +a string and the format will be "General" Useful when piping non-string +objects, like [DateTime] etc. + +.PARAMETER IgnoreText + +This means that incoming string objects will not be converted to their +interpreted type and they won't be formatted. The cell value will be a string +and the format will be "General". Useful when you don't like the interpreted +results or have to keep strings as strings. + +.EXAMPLE + +PS> "123" | New-CellData | Select-Object -ExpandProperty Value + +Returns [double] 123. + +.EXAMPLE + +PS> "0123" | New-CellData | Select-Object -ExpandProperty Value + +Returns [string] 0123. + +.EXAMPLE + +PS> "123" | New-CellData -ForceText | Select-Object -ExpandProperty Value + +Returns [string] "123". + +.EXAMPLE + +PS> "1/1/13 1:10" | New-CellData | Select-Object -ExpandProperty Value + +Returns [datetime]. + +.EXAMPLE + +PS> Get-Date | New-CellData + +Returns [datetime]. + +.LINK + +https://epplus.codeplex.com/wikipage?title=FAQ +https://epplus.codeplex.com/SourceControl/latest#EPPlus/ExcelRangeBase.cs +https://epplus.codeplex.com/SourceControl/latest#EPPlus/ExcelTextFormat.cs +https://epplus.codeplex.com/SourceControl/latest#EPPlus/Style/ExcelNumberFormat.cs +http://stackoverflow.com/questions/29473920/how-to-set-cell-data-type +http://stackoverflow.com/questions/24933947/excel-date-column-returning-int-using-epplus +http://stackoverflow.com/questions/28591763/epplus-how-to-know-the-format-of-the-worksheet-cell +http://stackoverflow.com/questions/23816043/epplus-date-cell-datatype-not-working +http://stackoverflow.com/questions/9859610/how-to-set-column-type-when-using-epplus +http://www.bartsp34ks.nl/news/powershell-how-to-create-a-net-datatable-and-save-it-to-xml-file/ +https://github.com/RamblingCookieMonster/PowerShell/blob/master/Out-DataTable.ps1 +https://www.ablebits.com/office-addins-blog/2015/03/26/excel-convert-text-date/ +https://support.office.com/en-us/article/Format-numbers-as-dates-or-times-418bd3fe-0577-47c8-8caa-b4d30c528309 +http://stackoverflow.com/questions/10928030/in-powershell-how-can-i-test-if-a-variable-holds-a-numeric-value +http://stackoverflow.com/questions/16806849/number-format-in-excel-showing-value-without-multiplying-with-100 +https://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.invariantinfo(v=vs.110).aspx +https://msdn.microsoft.com/en-us/library/91hfhz89(v=vs.110).aspx + +#> +function New-CellData { + [CmdletBinding()] + param( + [Parameter(ValueFromPipelineByPropertyName=$true)] + [object[]]$TargetData, + # https://msdn.microsoft.com/en-us/library/system.globalization.numberstyles(v=vs.110).aspx + [Parameter(ValueFromPipelineByPropertyName=$true)] + [System.Globalization.NumberStyles]$NumberStyles=[System.Globalization.NumberStyles]::Any, + # https://msdn.microsoft.com/en-us/library/system.globalization.datetimestyles(v=vs.110).aspx + [Parameter(ValueFromPipelineByPropertyName=$true)] + [System.Globalization.DateTimeStyles]$DateTimeStyles=[System.Globalization.DateTimeStyles]::None, + # https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo(v=vs.110).aspx + [Parameter(ValueFromPipelineByPropertyName=$true)] + [System.Globalization.NumberFormatInfo]$NumberFormatInfo=[System.Globalization.NumberFormatInfo]::InvariantInfo, + # https://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo(v=vs.110).aspx + [Parameter(ValueFromPipelineByPropertyName=$true)] + [System.Globalization.DateTimeFormatInfo]$DateTimeFormatInfo=[System.Globalization.DateTimeFormatInfo]::InvariantInfo, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [string]$NumberFormat="General", + # https://support.office.com/en-us/article/Format-a-date-the-way-you-want-8e10019e-d5d8-47a1-ba95-db95123d273e?ui=en-US&rs=en-US&ad=US&fromAR=1 + [Parameter(ValueFromPipelineByPropertyName=$true)] + [string]$DateTimeFormat="mmm/dd/yyyy hh:mm:ss", + [Parameter(ValueFromPipelineByPropertyName=$true)] + [string]$TimeSpanFormat="hh:mm:ss", + [Parameter(ValueFromPipelineByPropertyName=$true)] + [switch]$ForceText, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [switch]$IgnoreText + ) + begin { + Set-StrictMode -Version Latest + } + process { + Set-StrictMode -Version Latest + + if (($TargetData -eq $null) -or ($TargetData.Count -eq 0)) { + if ($ForceText.IsPresent) { + New-ValueFormatPair -Value "" -Format "General" + } + else { + New-ValueFormatPair -Value $null -Format "General" + } + } + else { + foreach ($itemObject in $TargetData) { + # Write-Verbose "TargetData is $itemObject" + + if ($ForceText.IsPresent) { + $itemObject = $itemObject.ToString() + } + + $out = $null + if ($itemObject -is [ValueType]) { + if ($itemObject -is [DateTime]) { + # https://msdn.microsoft.com/en-us/library/system.datetime.tooadate.aspx + $out = New-ValueFormatPair -Value $itemObject -Format $DateTimeFormat + } + elseif ($itemObject -is [TimeSpan]) { + $out = New-ValueFormatPair -Value $itemObject -Format $TimeSpanFormat + } + elseif (Test-NumericType -Object $itemObject) { + $out = New-ValueFormatPair -Value $itemObject -Format $NumberFormat + } + } + elseif ($itemObject -is [string] -and !$ForceText.IsPresent -and !$IgnoreText.IsPresent) { + + # Is $itemObject a double? + if ($out -eq $null) { + $out = & { + $decSep = $NumberFormatInfo.NumberDecimalSeparator + if ($itemObject -notmatch "^[0][^\$decSep]+|^[\s]+|[\s]+$") { + + # "001" is not a valid number, but "0.01" is. + # "123" can be interpeted as a number, but the + # values " 123" or "123 " are intentional + # strings due to the whitespace. Try to + # support these cases. + + # We also want to support different cultures + # (i.e. 0.1 vs 0,1 or 1,200 vs 1.200). This + # can already be explicitly set in the + # parameters. It makes sense that users of the + # library will be working with data formatted + # in their CurrentCulture. Ideally, when + # converting an initial string to double, the + # reverse conversion from double to string + # should lead to the same initial string. If + # it doesn't, issue a warning, so that the + # user can know that they should reformat + # their data or use a different + # $NumberFormatInfo value. + + # We also want to support + and -, and perhaps + # even currency symbols. See $NumberStyles + # documentation for the built-in method of + # handling these situations. We might need to + # extract the $NumberFormat from the string in + # these cases. + + # If the conversion produces a value that is + # not reversible, then don't prefer the + # conversion, but retain the original + # unconverted value. + + # Offer recommendations so that the user can + # try more optimal $NumberFormatInfo + # configurations. + + $nftUser = New-NumberFromText -Text $itemObject -NumberStyles $NumberStyles -NumberFormatInfo $NumberFormatInfo -Offer + if ($nftUser.ParseOkay) { + New-ValueFormatPair -Value $nftUser.Number -Format $NumberFormat + } + } + else { + Write-Warning "Leading zeros or whitespace detected. Value '$itemObject' will not be treated as a number." + } + } + } + + # Is $itemObject a DateTime? + if ($out -eq $null) { + $out = & { + $dateTime = 0 + if ([DateTime]::TryParse($itemObject, $DateTimeFormatInfo, $DateTimeStyles, [ref]$dateTime)) { + # https://msdn.microsoft.com/en-us/library/system.datetime.tooadate.aspx + New-ValueFormatPair -Value $dateTime -Format $DateTimeFormat + } + } + } + + # TODO: Is $itemObject a TimeSpan? + } + + if ($out -eq $null) { + $out = New-ValueFormatPair -Value $itemObject -Format "General" + } + + $out + } + } + } + end { + } +} diff --git a/Export-Excel.Tests.ps1 b/Export-Excel.Tests.ps1 index b6f7ecf0..1e1d2f99 100644 --- a/Export-Excel.Tests.ps1 +++ b/Export-Excel.Tests.ps1 @@ -1,6 +1,6 @@ # Contributed by https://github.com/W1M0R -Import-Module ImportExcel -Force +Import-Module $PSScriptRoot -Force -Scope Global function New-TestWorkbook { $testWorkbook = "$($PSScriptRoot)\test.xlsx" diff --git a/Export-Excel.ps1 b/Export-Excel.ps1 index ea305b25..df5b3bf4 100644 --- a/Export-Excel.ps1 +++ b/Export-Excel.ps1 @@ -13,6 +13,7 @@ function Export-Excel { Remove-Item "c:\temp\test.xlsx" -ErrorAction Ignore Get-Service | Export-Excel "c:\temp\test.xlsx" -Show -IncludePivotTable -PivotRows status -PivotData @{status='count'} #> + [CmdletBinding()] param( [Parameter(Mandatory=$true)] $Path, @@ -57,10 +58,18 @@ function Export-Excel { $StartRow=1, $StartColumn=1, [Switch]$PassThru, - [string]$Numberformat="General" + [string]$NumberFormat="General", + [string]$DateTimeFormat="mmm/dd/yyyy hh:mm:ss", + [hashtable]$ColumnOptions ) Begin { + # Import the Export-Excel implementation helpers. + . $PSScriptRoot\Export-Excel.Impl.ps1 + + # Create the options cache that will be used to format columns. + $colOptCache = New-ColumnOptionsCache -Table $ColumnOptions -DateTimeFormat $DateTimeFormat -NumberFormat $NumberFormat + $script:Header = $null if($KillExcel) { Get-Process excel -ErrorAction Ignore | Stop-Process @@ -125,7 +134,7 @@ function Export-Excel { Process { if($firstTimeThru) { $firstTimeThru=$false - $isDataTypeValueType = $TargetData.GetType().name -match "string|bool|byte|char|decimal|double|float|int|long|sbyte|short|uint|ulong|ushort" + $isDataTypeValueType = $TargetData.GetType().name -match $pattern } if($isDataTypeValueType) { @@ -133,18 +142,14 @@ function Export-Excel { $targetCell = $ws.Cells[$Row, $ColumnIndex] - $r=$null - $cellValue=$TargetData - if([Double]::TryParse($cellValue,[System.Globalization.NumberStyles]::Any,[System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$r)) { - $targetCell.Value = $r - $targetCell.Style.Numberformat.Format=$Numberformat - } else { - $targetCell.Value = $cellValue - } + # Write-Verbose "At column '$ColumnIndex' with data '$TargetData' and type '$($TargetData.GetType())'." - switch ($TargetData.$Name) { - {$_ -is [datetime]} {$targetCell.Style.Numberformat.Format = "m/d/yy h:mm"} - } + $opts = Get-ColumnOptions -CacheObject $colOptCache -ColumnIndex $ColumnIndex -TargetData $TargetData + # Write-Verbose "Using options '$opts'." + $cellData = $opts | New-CellData + $targetCell.Value = $cellData.Value + $targetCell.Style.NumberFormat.Format = $cellData.Format + # Write-Verbose "Cell data is '$cellData' with type '$($cellData.Value.GetType())'." $ColumnIndex += 1 $Row += 1 @@ -174,23 +179,17 @@ function Export-Excel { $targetCell = $ws.Cells[$Row, $ColumnIndex] - $cellValue=$TargetData.$Name + $opts = Get-ColumnOptions -CacheObject $colOptCache -ColumnIndex $ColumnIndex -ColumnName $Name -TargetData ($TargetData.$Name) + # Write-Verbose "Using options for column '$Name': $opts" + $cellData = $opts | New-CellData + # Write-Verbose "cellData is '$cellData'." + $cellValue = $cellData.Value - if($cellValue -is [string] -and $cellValue.StartsWith('=')) { + if ($cellValue -is [string] -and $cellValue.StartsWith('=')) { $targetCell.Formula = $cellValue } else { - - $r=$null - if([Double]::TryParse($cellValue,[System.Globalization.NumberStyles]::Any,[System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$r)) { - $targetCell.Value = $r - $targetCell.Style.Numberformat.Format=$Numberformat - } else { - $targetCell.Value = $cellValue - } - } - - switch ($TargetData.$Name) { - {$_ -is [datetime]} {$targetCell.Style.Numberformat.Format = "m/d/yy h:mm"} + $targetCell.Value = $cellData.Value + $targetCell.Style.NumberFormat.Format = $cellData.Format } #[ref]$uriResult=$null diff --git a/Issues.Tests.ps1 b/Issues.Tests.ps1 new file mode 100644 index 00000000..9a6ad75e --- /dev/null +++ b/Issues.Tests.ps1 @@ -0,0 +1,187 @@ +Set-StrictMode -Version Latest +Import-Module $PSScriptRoot -Force -Scope Global + +function New-TestWorkbook { + $testWorkbook = "$($PSScriptRoot)\issues.xlsx" + + Remove-Item $testWorkbook -ErrorAction Ignore + $testWorkbook +} + +function Remove-TestWorkbook { + New-TestWorkbook | Out-Null +} + +Describe "Issues" { + + $workbook = New-TestWorkbook + + Context "Keep numbers as strings formatted as text #92" { + # https://github.com/dfinke/ImportExcel/issues/92 + + $xlPkg = [pscustomobject]@{PhoneNumber=[string]"01234123456"} | Export-Excel -Path $workbook -PassThru + It "Keeps numeric strings with leading zeroes as text" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A2"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "01234123456" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + + $columnOptions = @{ PhoneNumber = @{ IgnoreText = $true } } + $xlPkg = [pscustomobject]@{PhoneNumber=[string]"1234123456"} | Export-Excel -Path $workbook -ColumnOptions $columnOptions -PassThru + It "Can ignore the automatic conversion of strings using -ColumnOptions and IgnoreText" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A2"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "1234123456" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + + $columnOptions = @{ PhoneNumber = @{ ForceText = $true } } + $xlPkg = [pscustomobject]@{PhoneNumber=1234123456} | Export-Excel -Path $workbook -ColumnOptions $columnOptions -PassThru + It "Can force numbers as strings using -ColumnOptions and ForceText" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A2"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "1234123456" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + } + + Context "Use localized date format in Export-Excel #52" { + # https://github.com/dfinke/ImportExcel/issues/52 + + $cultureShortDatePattern = [CultureInfo]::CurrentCulture.DateTimeFormat.ShortDatePattern + + $xlPkg = "$(Get-Date)" | Export-Excel -Path $workbook -DateTimeFormat $cultureShortDatePattern -PassThru + It "Can accept localized date format with -DateTimeFormat" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A1"] + $cell.Value -is [DateTime] | Should Be $true + $cell.Style.NumberFormat.Format | Should Be $cultureShortDatePattern + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + + $columnOptions = @{ 1 = @{ DateTimeFormat = $cultureShortDatePattern } } + $xlPkg = "$(Get-Date)" | Export-Excel -Path $workbook -ColumnOptions $columnOptions -PassThru + It "Can accept localized date format with -ColumnOptions and DateTimeFormat" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A1"] + $cell.Value -is [DateTime] | Should Be $true + $cell.Style.NumberFormat.Format | Should Be $cultureShortDatePattern + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + } + + Context "Only length in the Excel sheet, not the string #41" { + # https://github.com/dfinke/ImportExcel/issues/41 + + $xlPkg = "test", "c:\whatever", "d:\whatever" | Export-Excel -Path $workbook -PassThru + It "Can accept an array of strings" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A1"] + $cell.Value | Should Be "test" + $cell = $ws.Cells["A2"] + $cell.Value | Should Be "c:\whatever" + $cell = $ws.Cells["A3"] + $cell.Value | Should Be "d:\whatever" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + + } + + Context "Numbers with leading zeros treated as numbers, not text #33" { + # https://github.com/dfinke/ImportExcel/issues/33 + + $csvData = +@" +a,b,c +01,002,3 +00u812,05150,abc +123,456,098 +0123,456,098 +"@ | ConvertFrom-Csv + + $xlPkg = $csvData | Export-Excel -Path $workbook -PassThru + It "Can accept CSV data with numeric strings that have leading zeroes" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cell = $ws.Cells["A2"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "01" + $cell = $ws.Cells["A3"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "00u812" + $cell = $ws.Cells["A4"] + $cell.Value -is [double] | Should Be $true + $cell.Value | Should Be 123 + $cell = $ws.Cells["A5"] + $cell.Value | Should Be "0123" + $cell = $ws.Cells["B2"] + $cell.Value | Should Be "002" + $cell = $ws.Cells["B3"] + $cell.Value | Should Be "05150" + $cell = $ws.Cells["B4"] + $cell.Value -is [double] | Should Be $true + $cell.Value | Should Be 456 + $cell = $ws.Cells["B5"] + $cell.Value -is [double] | Should Be $true + $cell.Value | Should Be 456 + $cell = $ws.Cells["C4"] + $cell.Value | Should Be "098" + $cell = $ws.Cells["C5"] + $cell.Value | Should Be "098" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + + $columnOptions = @{ "*" = @{ IgnoreText = $true } } + $xlPkg = $csvData | Export-Excel -Path $workbook -ColumnOptions $columnOptions -PassThru + It "Can skip automatic conversion of CSV strings using -ColumnOptions and IgnoreText" { + $ws = $xlPkg.Workbook.WorkSheets[1] + $cols = $ws.Cells["A2:C"] # Skip the headings. + $cols | % { + $cell = $_ + if ($cell -ne $null) { + $cell.Value -is [string] | Should Be $true + } + } + + $cell = $ws.Cells["A2"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "01" + $cell = $ws.Cells["A3"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "00u812" + $cell = $ws.Cells["A4"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "123" + $cell = $ws.Cells["A5"] + $cell.Value | Should Be "0123" + $cell = $ws.Cells["B2"] + $cell.Value | Should Be "002" + $cell = $ws.Cells["B3"] + $cell.Value | Should Be "05150" + $cell = $ws.Cells["B4"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "456" + $cell = $ws.Cells["B5"] + $cell.Value -is [string] | Should Be $true + $cell.Value | Should Be "456" + $cell = $ws.Cells["C4"] + $cell.Value | Should Be "098" + $cell = $ws.Cells["C5"] + $cell.Value | Should Be "098" + } + $xlPkg.Save(); $xlPkg.Dispose(); + # Invoke-Item $workbook; throw; + } + + Remove-TestWorkbook +}