将每个包含多个工作表的多个 Excel 工作簿转换为 CSV:如何提取由公式构造的 url?
Convert multiple Excel workbooks each with multiple worksheets to CSV: How do I extract a url that is constructed by a formula?
我正在编写 PowerShell
脚本以将多工作表 Excel
xlsx
工作簿的集合转换为单个 csv
文件。我想要获取的内容之一是由 HYPERLINK 公式创建的 hyperlink 的计算文本。例如,一个单元格包含 =HYPERLINK(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here")
我可以使用 $currentCell = $sheet.Cells.Item($r, $c)
获取单元格。我可以使用 $currentCell.Text
获取 link 文本 Click Here
我可以通过测试 $currentCell.HasFormula
来检测单元格是否有公式。我可以使用 $currentCell.Formula
获取公式并使用正则表达式解析它以检测它是否包含 HYPERLINK
公式。但是,我想要的是获得执行公式的结果。我可以使用 $currentCell.Calculate()
执行公式,但我不知道如何获取结果(当我将 $currentCell.Calculate() 的结果分配给变量时,变量最终作为 System.DBNull
).
如何以编程方式获取单元格 Calculate
方法的结果?
更新
在思考了 Benoît Mayer 的回答后,我意识到我不明白我自己问题的基础。我试图概括包含公式的单元格的处理,但这行不通。正在计算单元格的公式,即,当我提取单元格的文本(具有 HYPERLINK 和 CONCATENATE 公式的单元格)时,我得到 Click Here
这是执行公式的结果(例如 =HYPERLINK(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here")
).我需要检测和解析 HYPERLINK 和 CONCATENATE 公式,并使用 Benoit 描述的方法。
这是我的代码。它转换多个 Excel 工作簿,每个工作簿都有多个工作表,并提取我需要处理的工作表中特定公式的结果。请参阅第 136 和 145 行之后的代码。
**代码。 5 月 7 日更新,修复了错误并提供了检测特定公式数据并从中提取数据的代码 **
cls
#region Functions
Function Remove-WhiteSpaceFromNonQuoted($inString)
{
$quoted = $false
$newString = ""
for ($i = 0; $i -lt $inString.Length; $i++)
{
if ($inString[$i] -eq "`"")
{
$quoted = $quoted -xor $true
}
if (($inString[$i] -match "\S" -and !$quoted) -or ($quoted))
{
$newString = $newString + $inString[$i]
}
}
return $newString
}
#endregion
$sortedFieldNameList = New-Object -TypeName System.Collections.SortedList
$fqBookNames = New-Object -TypeName System.Collections.SortedList
$fqBookNames.Add("C:\foo\bar1.xlsx", "")
$fqBookNames.Add("C:\foo\bar2.xlsx", "")
$fqBookNames.Add("C:\foo\barN.xlsx", "")
$global:workBook = $null
$global:excel = $null
try
{
$global:excel = New-Object -Com Excel.Application
$global:excel.Visible = $false
write-host ("Scan for column names")
#Scan all sheets in all books and create an object with all the column names encountered
foreach ($fqBookName in $fqBookNames.Keys)
{
$global:workBook = $global:excel.Workbooks.Open($fqBookName)
foreach ($sheet in $global:workBook.Sheets)
{
$columnIndexMax = $sheet.UsedRange.Column + $sheet.UsedRange.Columns.Count - 1
write-host ("Workbook=" + $global:workBook.Name + ". Sheet=" + $sheet.Name)
$rowOne = $sheet.Rows(1)
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
$columnName = $rowOne.Cells($columnIndex).Text.Trim().ToUpper()
if ($columnName.Length -gt 0)
{
if (!$sortedFieldNameList.ContainsKey($columnName))
{
$sortedFieldNameList.Add($columnName, "")
}
}
else
{
break
}
}
}
$global:workBook.Close($false)
Clear-Variable workBook
}
#Create a class that represents the worst-case collection of columns that will be output to, e.g., a grid or CSV file
#
Invoke-Expression @"
Class ClsExportCsv {
$(($sortedFieldNameList.Keys).ForEach({"[string] `${$($_)}`n "}))
}
"@
#create array to hold list of rows that will be output to, e.g., a grid or CSV file
$itemList = New-Object System.Collections.ArrayList
$itemList.clear()
write-host ("Scan for data")
foreach ($fqBookName in $fqBookNames.Keys)
{
$global:workBook = $global:excel.Workbooks.Open($fqBookName)
foreach ($sheet in $global:workBook.Sheets)
{
write-host -NoNewline ("Workbook=" + $global:workBook.Name + ". Sheet=" + $sheet.Name + ". Rows=")
$columnNameLookup = @{}
$columnNameLookup.Clear()
$columnIndexMax = $sheet.UsedRange.Column + $sheet.UsedRange.Columns.Count - 1
$rowOne = $sheet.Rows(1)
#create column name index lookup table for this sheet
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
$columnNameLookup.Add($columnIndex, $rowOne.Cells($columnIndex).Text.Trim().ToUpper())
}
for ($rowIndex = 2; $rowIndex -le $sheet.Cells.EntireRow.Count; $rowIndex++)
{
$rowCurrent = $sheet.Rows($rowIndex)
if (($rowCurrent.Cells(1).Text).Length -gt 0)
{
$listRow = New-Object -TypeName ClsExportCsv
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
if (($columnNameLookup.$columnIndex).Length -gt 0)
{
$cellObject = $rowCurrent.Cells($columnIndex)
$textFromFormula = ""
if ($cellObject.HasFormula)
{
$formulaNoWhiteSpace = Remove-WhiteSpaceFromNonQuoted -inString $cellObject.Formula
#detect and parse cells with =HYPERLINK(CONCATENATE("http://xxxx.aspx?pid=",A2),"Click Here")
if ($formulaNoWhiteSpace -match '^(?:\=HYPERLINK\(CONCATENATE\(\")(?<URL>.*)(?:\"\,)(?<A1>.*)(?:\)\,.*)$')
{
if (($Matches["URL"] -ne $null) -and ($Matches["A1"] -ne $null))
{
$textFromFormula = ($Matches["URL"] + $sheet.Range($Matches["A1"]).Text)
}
}
#detect and parse cells with =HYPERLINK("http://xxxx","Click Here")
if ($formulaNoWhiteSpace -match '^(?:\=HYPERLINK\(\")(?<URL>.*)(?:\"\,\".*\"\))$')
{
if ($Matches["URL"] -ne $null)
{
$textFromFormula = $Matches["URL"]
}
}
}
if ($textFromFormula.Length -eq 0)
{
$listRow.($columnNameLookup.$columnIndex) = $rowCurrent.Cells($columnIndex).Text.Trim()
}
else
{
$listRow.($columnNameLookup.$columnIndex) = $textFromFormula
}
} # if (($columnNameLookup.$columnIndex).Length -gt 0)
} # for ($columnIndex = 1; ...
$itemList.Add($listRow) | out-null
}
else
{
write-host ($rowIndex - 2).ToString()
break
}
} # for ($rowIndex = 2; .....
} # foreach ($sheet in $global:workBook.Sheets)
$global:workBook.Close($false)
Clear-Variable workBook
}
$global:excel.Quit()
Clear-Variable excel
$itemList | Export-CSV -LiteralPath "C:\Users\foo\combined.csv" -NoTypeInformation -Encoding UTF8 -Delimiter ',' $itemList | Out-GridView -Title "Rows"
}
finally
{
if ($global:excel -ne $null)
{
if ($global:workBook -ne $null)
{
$global:workBook.Close($false)
}
$global:excel.Quit()
Clear-Variable excel
}
}
似乎无法直接获取 Concatenate 函数生成的地址,例如 。
为什么使用如下正则表达式的解决方案不合适?
$split = $currentCell.Formula -split 'CONCATENATE' | Select -Last 1 | %{$_ -replace `
'[" ()]','' -split ','}
$calculatedResult = $split[0] + $sheet.Range("$($split[1])").Text
我正在编写 PowerShell
脚本以将多工作表 Excel
xlsx
工作簿的集合转换为单个 csv
文件。我想要获取的内容之一是由 HYPERLINK 公式创建的 hyperlink 的计算文本。例如,一个单元格包含 =HYPERLINK(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here")
我可以使用 $currentCell = $sheet.Cells.Item($r, $c)
获取单元格。我可以使用 $currentCell.Text
获取 link 文本 Click Here
我可以通过测试 $currentCell.HasFormula
来检测单元格是否有公式。我可以使用 $currentCell.Formula
获取公式并使用正则表达式解析它以检测它是否包含 HYPERLINK
公式。但是,我想要的是获得执行公式的结果。我可以使用 $currentCell.Calculate()
执行公式,但我不知道如何获取结果(当我将 $currentCell.Calculate() 的结果分配给变量时,变量最终作为 System.DBNull
).
如何以编程方式获取单元格 Calculate
方法的结果?
更新
在思考了 Benoît Mayer 的回答后,我意识到我不明白我自己问题的基础。我试图概括包含公式的单元格的处理,但这行不通。正在计算单元格的公式,即,当我提取单元格的文本(具有 HYPERLINK 和 CONCATENATE 公式的单元格)时,我得到 Click Here
这是执行公式的结果(例如 =HYPERLINK(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here")
).我需要检测和解析 HYPERLINK 和 CONCATENATE 公式,并使用 Benoit 描述的方法。
这是我的代码。它转换多个 Excel 工作簿,每个工作簿都有多个工作表,并提取我需要处理的工作表中特定公式的结果。请参阅第 136 和 145 行之后的代码。
**代码。 5 月 7 日更新,修复了错误并提供了检测特定公式数据并从中提取数据的代码 **
cls
#region Functions
Function Remove-WhiteSpaceFromNonQuoted($inString)
{
$quoted = $false
$newString = ""
for ($i = 0; $i -lt $inString.Length; $i++)
{
if ($inString[$i] -eq "`"")
{
$quoted = $quoted -xor $true
}
if (($inString[$i] -match "\S" -and !$quoted) -or ($quoted))
{
$newString = $newString + $inString[$i]
}
}
return $newString
}
#endregion
$sortedFieldNameList = New-Object -TypeName System.Collections.SortedList
$fqBookNames = New-Object -TypeName System.Collections.SortedList
$fqBookNames.Add("C:\foo\bar1.xlsx", "")
$fqBookNames.Add("C:\foo\bar2.xlsx", "")
$fqBookNames.Add("C:\foo\barN.xlsx", "")
$global:workBook = $null
$global:excel = $null
try
{
$global:excel = New-Object -Com Excel.Application
$global:excel.Visible = $false
write-host ("Scan for column names")
#Scan all sheets in all books and create an object with all the column names encountered
foreach ($fqBookName in $fqBookNames.Keys)
{
$global:workBook = $global:excel.Workbooks.Open($fqBookName)
foreach ($sheet in $global:workBook.Sheets)
{
$columnIndexMax = $sheet.UsedRange.Column + $sheet.UsedRange.Columns.Count - 1
write-host ("Workbook=" + $global:workBook.Name + ". Sheet=" + $sheet.Name)
$rowOne = $sheet.Rows(1)
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
$columnName = $rowOne.Cells($columnIndex).Text.Trim().ToUpper()
if ($columnName.Length -gt 0)
{
if (!$sortedFieldNameList.ContainsKey($columnName))
{
$sortedFieldNameList.Add($columnName, "")
}
}
else
{
break
}
}
}
$global:workBook.Close($false)
Clear-Variable workBook
}
#Create a class that represents the worst-case collection of columns that will be output to, e.g., a grid or CSV file
#
Invoke-Expression @"
Class ClsExportCsv {
$(($sortedFieldNameList.Keys).ForEach({"[string] `${$($_)}`n "}))
}
"@
#create array to hold list of rows that will be output to, e.g., a grid or CSV file
$itemList = New-Object System.Collections.ArrayList
$itemList.clear()
write-host ("Scan for data")
foreach ($fqBookName in $fqBookNames.Keys)
{
$global:workBook = $global:excel.Workbooks.Open($fqBookName)
foreach ($sheet in $global:workBook.Sheets)
{
write-host -NoNewline ("Workbook=" + $global:workBook.Name + ". Sheet=" + $sheet.Name + ". Rows=")
$columnNameLookup = @{}
$columnNameLookup.Clear()
$columnIndexMax = $sheet.UsedRange.Column + $sheet.UsedRange.Columns.Count - 1
$rowOne = $sheet.Rows(1)
#create column name index lookup table for this sheet
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
$columnNameLookup.Add($columnIndex, $rowOne.Cells($columnIndex).Text.Trim().ToUpper())
}
for ($rowIndex = 2; $rowIndex -le $sheet.Cells.EntireRow.Count; $rowIndex++)
{
$rowCurrent = $sheet.Rows($rowIndex)
if (($rowCurrent.Cells(1).Text).Length -gt 0)
{
$listRow = New-Object -TypeName ClsExportCsv
for ($columnIndex = 1; $columnIndex -le $columnIndexMax; $columnIndex++)
{
if (($columnNameLookup.$columnIndex).Length -gt 0)
{
$cellObject = $rowCurrent.Cells($columnIndex)
$textFromFormula = ""
if ($cellObject.HasFormula)
{
$formulaNoWhiteSpace = Remove-WhiteSpaceFromNonQuoted -inString $cellObject.Formula
#detect and parse cells with =HYPERLINK(CONCATENATE("http://xxxx.aspx?pid=",A2),"Click Here")
if ($formulaNoWhiteSpace -match '^(?:\=HYPERLINK\(CONCATENATE\(\")(?<URL>.*)(?:\"\,)(?<A1>.*)(?:\)\,.*)$')
{
if (($Matches["URL"] -ne $null) -and ($Matches["A1"] -ne $null))
{
$textFromFormula = ($Matches["URL"] + $sheet.Range($Matches["A1"]).Text)
}
}
#detect and parse cells with =HYPERLINK("http://xxxx","Click Here")
if ($formulaNoWhiteSpace -match '^(?:\=HYPERLINK\(\")(?<URL>.*)(?:\"\,\".*\"\))$')
{
if ($Matches["URL"] -ne $null)
{
$textFromFormula = $Matches["URL"]
}
}
}
if ($textFromFormula.Length -eq 0)
{
$listRow.($columnNameLookup.$columnIndex) = $rowCurrent.Cells($columnIndex).Text.Trim()
}
else
{
$listRow.($columnNameLookup.$columnIndex) = $textFromFormula
}
} # if (($columnNameLookup.$columnIndex).Length -gt 0)
} # for ($columnIndex = 1; ...
$itemList.Add($listRow) | out-null
}
else
{
write-host ($rowIndex - 2).ToString()
break
}
} # for ($rowIndex = 2; .....
} # foreach ($sheet in $global:workBook.Sheets)
$global:workBook.Close($false)
Clear-Variable workBook
}
$global:excel.Quit()
Clear-Variable excel
$itemList | Export-CSV -LiteralPath "C:\Users\foo\combined.csv" -NoTypeInformation -Encoding UTF8 -Delimiter ',' $itemList | Out-GridView -Title "Rows"
}
finally
{
if ($global:excel -ne $null)
{
if ($global:workBook -ne $null)
{
$global:workBook.Close($false)
}
$global:excel.Quit()
Clear-Variable excel
}
}
似乎无法直接获取 Concatenate 函数生成的地址,例如
为什么使用如下正则表达式的解决方案不合适?
$split = $currentCell.Formula -split 'CONCATENATE' | Select -Last 1 | %{$_ -replace `
'[" ()]','' -split ','}
$calculatedResult = $split[0] + $sheet.Range("$($split[1])").Text