Skip to content

Commit

Permalink
Merge pull request #1305 from thkn-hofa/master
Browse files Browse the repository at this point in the history
TotalSettings improvements
  • Loading branch information
dfinke authored Nov 19, 2022
2 parents 3f772ce + ba374ec commit c848497
Show file tree
Hide file tree
Showing 15 changed files with 3,994 additions and 4,793 deletions.
11 changes: 6 additions & 5 deletions Examples/Tables/SalesData-WithTotalRow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ OrderId,Category,Sales,Quantity,Discount
$xlfile = "$PSScriptRoot\TotalsRow.xlsx"
Remove-Item $xlfile -ErrorAction SilentlyContinue

$TotalSettings = @{
$TableTotalSettings = @{
Quantity = 'Sum'
Category = @{
# Count the number of categories not equal to Electronics
Custom = '=COUNTIF([Category],"<>Electronics")'
Category = '=COUNTIF([Category],"<>Electronics")' # Count the number of categories not equal to Electronics
Sales = @{
Function = '=SUMIF([Category],"<>Electronics",[Sales])'
Comment = "Sum of sales for everything that is NOT Electronics"
}
}

$data | Export-Excel -Path $xlfile -TableName Sales -TableStyle Medium10 -TotalSettings $TotalSettings -AutoSize -Show
$data | Export-Excel -Path $xlfile -TableName Sales -TableStyle Medium10 -TableTotalSettings $TableTotalSettings -AutoSize -Show
13 changes: 7 additions & 6 deletions Examples/Tables/TotalsRow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ try {Import-Module $PSScriptRoot\..\..\ImportExcel.psd1} catch {throw ; return}
$r = Get-ChildItem C:\WINDOWS\system32 -File

$TotalSettings = @{
Length = "Sum"
Name = "Count"
Extension = @{
# You can create the formula in an Excel workbook first and copy-paste it here
# This syntax can only be used for the Custom type
Custom = "=COUNTIF([Extension];`".exe`")"
# You can create the formula in an Excel workbook first and copy-paste it here
# This syntax can only be used for the Custom type
Extension = "=COUNTIF([Extension];`".exe`")"
Length = @{
Function = "=SUMIF([Extension];`".exe`";[Length])"
Comment = "Sum of all exe sizes"
}
}

$r | Export-Excel -TableName system32files -TableStyle Medium10 -TotalSettings $TotalSettings -Show
$r | Export-Excel -TableName system32files -TableStyle Medium10 -TableTotalSettings $TotalSettings -Show
1 change: 1 addition & 0 deletions ImportExcel.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Check out the How To Videos https://www.youtube.com/watch?v=U3Ne_yX4tYo&list=PL5
'Remove-Worksheet',
'Select-Worksheet',
'Send-SQLDataToExcel',
'Set-CellComment',
'Set-CellStyle',
'Set-ExcelColumn',
'Set-ExcelRange',
Expand Down
50 changes: 38 additions & 12 deletions Public/Add-ExcelTable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Add-ExcelTable {
[Switch]$ShowHeader ,
[Switch]$ShowFilter,
[Switch]$ShowTotal,
[hashtable]$TotalSettings,
[hashtable]$TableTotalSettings,
[Switch]$ShowFirstColumn,
[Switch]$ShowLastColumn,
[Switch]$ShowRowStripes,
Expand Down Expand Up @@ -51,16 +51,28 @@ function Add-ExcelTable {
}
#it seems that show total changes some of the others, so the sequence matters.
if ($PSBoundParameters.ContainsKey('ShowHeader')) {$tbl.ShowHeader = [bool]$ShowHeader}
if ($PSBoundParameters.ContainsKey('TotalSettings')) {
if ($PSBoundParameters.ContainsKey('TableTotalSettings') -And $Null -ne $TableTotalSettings) {
$tbl.ShowTotal = $true
foreach ($k in $TotalSettings.keys) {
foreach ($k in $TableTotalSettings.keys) {

# Get the Function to be added in the totals row
if ($TableTotalSettings[$k] -is [HashTable]) {
If ($TableTotalSettings[$k].Keys -contains "Function") {
$TotalFunction = $TableTotalSettings[$k]["Function"]
}
Else { Write-Warning -Message "TableTotalSettings parameter for column '$k' needs a key 'Function'" }
}
else {
$TotalFunction = [String]($TableTotalSettings[$k])
}

# Add the totals row
if (-not $tbl.Columns[$k]) {Write-Warning -Message "Table does not have a Column '$k'."}
elseif ($TotalSettings[$k] -is [HashTable] -and $TotalSettings[$k].Keys.Count -eq 1 -and $TotalSettings[$k].Keys[0] -eq "Custom") {
$formula = $TotalSettings[$k][($TotalSettings[$k].Keys[0])] | Select -First 1
elseif ($TotalFunction -match "^=") {
### A function in Excel uses ";" between parameters but the OpenXML parameter separator is ","
### Only replace semicolon when it's NOT somewhere between quotes quotes.
# Get all text between quotes
$QuoteMatches = [Regex]::Matches($formula,"`"[^`"]*`"|'[^']*'")
$QuoteMatches = [Regex]::Matches($TotalFunction,"`"[^`"]*`"|'[^']*'")
# Create array with all indexes of characters between quotes (and the quotes themselves)
$QuoteCharIndexes = $(
Foreach ($QuoteMatch in $QuoteMatches) {
Expand All @@ -69,21 +81,35 @@ function Add-ExcelTable {
)

# Get all semicolons
$SemiColonMatches = [Regex]::Matches($formula, ";")
$SemiColonMatches = [Regex]::Matches($TotalFunction, ";")
# Replace the semicolons of which the index is not in the list of quote-text indexes
Foreach ($SemiColonMatch in $SemiColonMatches.Index) {
If ($QuoteCharIndexes -notcontains $SemiColonMatch) {
$formula = $formula.remove($SemiColonMatch,1).Insert($SemiColonMatch,",")
$TotalFunction = $TotalFunction.remove($SemiColonMatch,1).Insert($SemiColonMatch,",")
}
}

# Configure the formula. The TotalsRowFunction is automatically set to "Custom" when the TotalsRowFormula is set.
$tbl.Columns[$k].TotalsRowFormula = $formula
$tbl.Columns[$k].TotalsRowFormula = $TotalFunction
}
elseif ($TotalFunction -notin @("Average", "Count", "CountNums", "Max", "Min", "None", "StdDev", "Sum", "Var") ) {
Write-Warning -Message "'$($TotalFunction)' is not a valid total function."
}
elseif ($TotalSettings[$k] -notin @("Average", "Count", "CountNums", "Max", "Min", "None", "StdDev", "Sum", "Var") ) {
Write-Warning -Message "'$($TotalSettings[$k])' is not a valid total function."
else {$tbl.Columns[$k].TotalsRowFunction = $TotalFunction}

# Set comment on totals row
If ($TableTotalSettings[$k] -is [HashTable] -and $TableTotalSettings[$k].Keys -contains "Comment" -and ![String]::IsNullOrEmpty($TableTotalSettings[$k]["Comment"])) {
$ColumnLetter = [officeOpenXml.ExcelAddress]::GetAddressCol(($tbl.columns | ? { $_.name -eq $k }).Id, $False)
$CommentRange = "{0}{1}" -f $ColumnLetter, $tbl.Address.End.Row

$CellCommentParams = @{
Worksheet = $tbl.WorkSheet
Range = $CommentRange
Text = $TableTotalSettings[$k]["Comment"]
}

Set-CellComment @CellCommentParams
}
else {$tbl.Columns[$k].TotalsRowFunction = $TotalSettings[$k]}
}
}
elseif ($PSBoundParameters.ContainsKey('ShowTotal')) {$tbl.ShowTotal = [bool]$ShowTotal}
Expand Down
16 changes: 3 additions & 13 deletions Public/Export-Excel.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
[Alias('Table')]
$TableName,
[OfficeOpenXml.Table.TableStyles]$TableStyle = [OfficeOpenXml.Table.TableStyles]::Medium6,
[HashTable]$TotalSettings,
[HashTable]$TableTotalSettings,
[Switch]$BarChart,
[Switch]$PieChart,
[Switch]$LineChart ,
Expand Down Expand Up @@ -212,12 +212,7 @@
$row ++
$null = $ws.Cells[$row, $StartColumn].LoadFromDataTable($InputObject, $false )
if ($TableName -or $PSBoundParameters.ContainsKey('TableStyle')) {
if ($PSBoundParameters.ContainsKey('TotalSettings')) {
Add-ExcelTable -Range $ws.Cells[$ws.Dimension] -TableName $TableName -TableStyle $TableStyle -TotalSettings $TotalSettings
}
Else {
Add-ExcelTable -Range $ws.Cells[$ws.Dimension] -TableName $TableName -TableStyle $TableStyle
}
Add-ExcelTable -Range $ws.Cells[$ws.Dimension] -TableName $TableName -TableStyle $TableStyle -TableTotalSettings $TableTotalSettings
}
}
else {
Expand Down Expand Up @@ -430,12 +425,7 @@
if ($null -ne $TableName -or $PSBoundParameters.ContainsKey('TableStyle')) {
#Already inserted Excel table if input was a DataTable
if ($InputObject -isnot [System.Data.DataTable]) {
if ($PSBoundParameters.ContainsKey('TotalSettings')) {
Add-ExcelTable -Range $ws.Cells[$dataRange] -TableName $TableName -TableStyle $TableStyle -TotalSettings $TotalSettings
}
else {
Add-ExcelTable -Range $ws.Cells[$dataRange] -TableName $TableName -TableStyle $TableStyle
}
Add-ExcelTable -Range $ws.Cells[$dataRange] -TableName $TableName -TableStyle $TableStyle -TableTotalSettings $TableTotalSettings
}
}
elseif ($AutoFilter) {
Expand Down
70 changes: 70 additions & 0 deletions Public/Set-CellComment.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope='Function', Target='Set*', Justification='Does not change system state')]
param()

function Set-CellComment {
[CmdletBinding(DefaultParameterSetName = "Range")]
param(
[Parameter(Mandatory = $True, ParameterSetName = "ColumnLetter")]
[Parameter(Mandatory = $True, ParameterSetName = "ColumnNumber")]
[Parameter(Mandatory = $False, ParameterSetName = "Range")]
[OfficeOpenXml.ExcelWorkSheet]$Worksheet,

[Parameter(Mandatory = $True, ParameterSetName = "Range", ValueFromPipeline = $true,Position=0)]
[Alias("Address")]
$Range,

[Parameter(Mandatory = $True, ParameterSetName = "ColumnLetter")]
[Parameter(Mandatory = $True, ParameterSetName = "ColumnNumber")]
[Int]$Row,

[Parameter(Mandatory = $True, ParameterSetName = "ColumnLetter")]
[String]$ColumnLetter,

[Parameter(Mandatory = $True, ParameterSetName = "ColumnNumber")]
[Int]$ColumnNumber,

[Parameter(Mandatory = $True)]
[String]$Text
)

If ($PSCmdlet.ParameterSetName -eq "Range") {
Write-Verbose "Using 'Range' Parameter Set"
if ($Range -is [Array]) {
$null = $PSBoundParameters.Remove("Range")
$Range | Set-CellComment @PSBoundParameters
}
else {
#We should accept, a worksheet and a name of a range or a cell address; a table; the address of a table; a named range; a row, a column or .Cells[ ]
if ($Range -is [OfficeOpenXml.Table.ExcelTable]) {$Range = $Range.Address}
elseif ($Worksheet -and $Range -is [string]) {
# Convert range as string to OfficeOpenXml.ExcelAddress
$Range = [OfficeOpenXml.ExcelAddress]::new($Range)
}
elseif ($Range -is [string]) {Write-Warning -Message "The range parameter you have specified also needs a worksheet parameter." ;return}
#else we assume $Range is a OfficeOpenXml.ExcelAddress
}
}
ElseIf ($PSCmdlet.ParameterSetName -eq "ColumnNumber") {
$Range = [OfficeOpenXml.ExcelAddress]::new($Row, $ColumnNumber, $Row, $ColumnNumber)
}
ElseIf ($PSCmdlet.ParameterSetName -eq "ColumnLetter") {
$Range = [OfficeOpenXml.ExcelAddress]::new(("{0}{1}" -f $ColumnLetter,$Row))
}

If ($Range -isnot [Array]) {
Foreach ($c in $Worksheet.Cells[$Range]) {
write-verbose $c.address
Try {
If ($Null -eq $c.comment) {
[Void]$c.AddComment($Text, "ImportExcel")
}
Else {
$c.Comment.Text = $Text
$c.Comment.Author = "ImportExcel"
}
$c.Comment.AutoFit = $True
}
Catch { "Could not add comment to cell {0}: {1}" -f $c.Address, $_.ToString() }
}
}
}
28 changes: 19 additions & 9 deletions __tests__/Export-Excel.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -692,15 +692,17 @@ Describe ExportExcel -Tag "ExportExcel" {
$processes = Get-Process | Where-Object { $_.StartTime } | Select-Object -First 50

# Export as table with a totals row with a set of possibilities
$TotalSettings = @{
$TableTotalSettings = @{
Id = "COUNT"
WS = "SUM"
Handles = "AVERAGE"
CPU = @{
Custom = '=COUNTIF([CPU];"<1")'
CPU = '=COUNTIF([CPU];"<1")'
NPM = @{
Function = '=SUMIF([Name];"=Chrome";[NPM])'
Comment = "Sum of Non-Paged Memory (NPM) for all chrome processes"
}
}
$Processes | Export-Excel $path -TableName "processes" -TotalSettings $TotalSettings
$Processes | Export-Excel $path -TableName "processes" -TableTotalSettings $TableTotalSettings
$TotalRows = $Processes.count + 2 # Column header + Data (50 processes) + Totals row
$Excel = Open-ExcelPackage -Path $path
$ws = $Excel.Workbook.Worksheets[1]
Expand All @@ -716,22 +718,30 @@ Describe ExportExcel -Tag "ExportExcel" {
$WScolumn = $ws.Tables[0].Columns | Where-Object { $_.Name -eq "WS" }
$HandlesColumn = $ws.Tables[0].Columns | Where-Object { $_.Name -eq "Handles" }
$CPUColumn = $ws.Tables[0].Columns | Where-Object { $_.Name -eq "CPU" }
$NPMColumn = $ws.Tables[0].Columns | Where-Object { $_.Name -eq "NPM" }

# Testing column properties
$IDcolumn | Select-Object -ExpandProperty TotalsRowFunction | Should -Be "Count"
$WScolumn | Select-Object -ExpandProperty TotalsRowFunction | Should -Be "Sum"
$HandlesColumn | Select-Object -ExpandProperty TotalsRowFunction | Should -Be "Average"
$CPUColumn | Select-Object -ExpandProperty TotalsRowFunction | Should -Be "Custom"
$CPUColumn | Select-Object -ExpandProperty TotalsRowFormula | Should -Be 'COUNTIF([CPU],"<1")'
$NPMColumn | Select-Object -ExpandProperty TotalsRowFunction | Should -Be "Custom"
$NPMColumn | Select-Object -ExpandProperty TotalsRowFormula | Should -Be 'SUMIF([Name],"=Chrome",[NPM])'

# Testing actual cell properties
$CountAddress = "{0}{1}" -f (Get-ExcelColumnName -ColumnNumber $IDcolumn.Id).ColumnName, $TotalRows
$SumAddress = "{0}{1}" -f (Get-ExcelColumnName -ColumnNumber $WScolumn.Id).ColumnName, $TotalRows
$AverageAddress = "{0}{1}" -f (Get-ExcelColumnName -ColumnNumber $HandlesColumn.Id).ColumnName, $TotalRows
$CustomAddress = "{0}{1}" -f (Get-ExcelColumnName -ColumnNumber $CPUColumn.Id).ColumnName, $TotalRows

$ws.Cells[$CountAddress].Formula | Should -Be "SUBTOTAL(103,processes[Id])"
$ws.Cells[$SumAddress].Formula | Should -Be "SUBTOTAL(109,processes[Ws])"
$ws.Cells[$AverageAddress].Formula | Should -Be "SUBTOTAL(101,processes[Handles])"
$ws.Cells[$CustomAddress].Formula | Should -Be 'COUNTIF([CPU],"<1")'
$CustomCommentAddress = "{0}{1}" -f (Get-ExcelColumnName -ColumnNumber $NPMColumn.Id).ColumnName, $TotalRows

$ws.Cells[$CountAddress].Formula | Should -Be "SUBTOTAL(103,processes[Id])"
$ws.Cells[$SumAddress].Formula | Should -Be "SUBTOTAL(109,processes[Ws])"
$ws.Cells[$AverageAddress].Formula | Should -Be "SUBTOTAL(101,processes[Handles])"
$ws.Cells[$CustomAddress].Formula | Should -Be 'COUNTIF([CPU],"<1")'
$ws.Cells[$CustomCommentAddress].Formula | Should -Be 'SUMIF([Name],"=Chrome",[NPM])'
$ws.Cells[$CustomCommentAddress].Comment.Text | Should -Not -BeNullOrEmpty
}

AfterEach {
Expand Down
5 changes: 3 additions & 2 deletions __tests__/Path.tests.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
Describe "Test reading relative paths" {
BeforeAll {
$script:xlfileName = "TestR.xlsx"
@{data = 1 } | Export-Excel (Join-Path $PWD "TestR.xlsx")
If ([String]::IsNullOrEmpty($PWD)) { $PWD = $PSScriptRoot }
@{data = 1 } | Export-Excel (Join-Path $PWD "TestR.xlsx")
}

AfterAll {
Remove-Item (Join-Path $PWD "$($script:xlfileName)")
Remove-Item (Join-Path $PWD "$($script:xlfileName)")
}

It "Should read local file".PadRight(90) {
Expand Down
44 changes: 44 additions & 0 deletions __tests__/Set-CellComment.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
if (-not (Get-command Import-Excel -ErrorAction SilentlyContinue)) {
Import-Module $PSScriptRoot\..\ImportExcel.psd1
}

Describe "Test setting comment on cells in different ways" -Tag SetCellComment {
BeforeAll {
$data = ConvertFrom-Csv @"
OrderId,Category,Sales,Quantity,Discount
1,Cosmetics,744.01,07,0.7
2,Grocery,349.13,25,0.3
3,Apparels,535.11,88,0.2
4,Electronics,524.69,60,0.1
5,Electronics,439.10,41,0.0
6,Apparels,56.84,54,0.8
7,Electronics,326.66,97,0.7
8,Cosmetics,17.25,74,0.6
9,Grocery,199.96,39,0.4
10,Grocery,731.77,20,0.3
"@

$Excel = $data | Export-Excel -PassThru
$ws = $Excel.Workbook.Worksheets | Select-Object -First 1
}

AfterAll {
Close-ExcelPackage $Excel
}

It "Should add comments to multiple cells".PadRight(87) {
Set-CellComment -Range "A1" -Worksheet $ws -Text "This was added with a single cell range"
Set-CellComment -Range "A2:C2" -Worksheet $ws -Text "This was added with a multiple cell range"
Set-CellComment -ColumnLetter A -Row 3 -Worksheet $ws -Text "This was added using a column letter and rownumber"
Set-CellComment -ColumnNumber 1 -Row 4 -Worksheet $ws -Text "This was added using a column number and row number"

Set-CellComment -Range "B2" -Worksheet $ws -Text "This demonstrates an overwrite of a previously set comment"

$ws.Cells["A1"].Comment.Text | Should -BeExactly "This was added with a single cell range"
$ws.Cells["A2"].Comment.Text | Should -BeExactly "This was added with a multiple cell range"
$ws.Cells["B2"].Comment.Text | Should -BeExactly "This demonstrates an overwrite of a previously set comment"
$ws.Cells["C2"].Comment.Text | Should -BeExactly "This was added with a multiple cell range"
$ws.Cells["A3"].Comment.Text | Should -BeExactly "This was added using a column letter and rownumber"
$ws.Cells["A4"].Comment.Text | Should -BeExactly "This was added using a column number and row number"
}
}
Loading

0 comments on commit c848497

Please sign in to comment.