关于powershell:将多个Excel工作簿(每个工作簿都有多个工作表)转换为CSV:如何提取由公式构成的网址?

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(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here")

我可以用$currentCell = $sheet.Cells.Item($r, $c)抓牢牢房。我可以使用$currentCell.Text来获取链接文本Click Here,可以通过测试$currentCell.HasFormula来检测单元格是否具有公式。我可以使用$currentCell.Formula抓取公式,并使用正则表达式对其进行解析以检测其包含HYPERLINK公式。但是,我想要获得的是执行公式的结果。我可以使用$currentCell.Calculate()执行公式,但无法弄清楚如何保留结果(当我将$ currentCell.Calculate()的结果分配给变量时,该变量最终成为)。

如何以编程方式获取单元格的Calculate方法的结果?

更新

考虑了贝诺·梅耶(Beno?t Mayer)的回答后,我意识到我不理解我自己问题的基础。我试图对包含公式的单元格的处理进行一般化,但这是行不通的。正在计算单元格的公式,即,当我提取单元格的文本(具有HYPERLINK和CONCATENATE公式的单元格)时,我得到Click Here,这是执行公式的结果(例如=HYPERLINK(CONCATENATE("http://foo/bar.aspx?pid=",A2),"Click Here"))。我需要检测并解析HYPERLINK和CONCATENATE公式,并使用Beno?t描述的方法。

这是我的代码。它可以转换多个Excel工作簿,每个工作簿都有多个工作表,并提取我需要处理的工作表中特定公式的结果。请参见第136和145行之后的代码。

**代码。在5/7上进行了更新,提供了错误修复和代码,以检测和提取特定公式中的数据**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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
    #https://stackoverflow.com/questions/49117127/create-a-class-with-dynamic-property-names-in-powershell
    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函数生成的地址,例如,请参见从Excel超链接公式提取URL。

为什么使用正则表达式的解决方案(例如以下解决方案)不合适?

1
2
3
$split = $currentCell.Formula -split 'CONCATENATE' | Select -Last 1 | %{$_ -replace `
'[" ()]','' -split ','}
$calculatedResult = $split[0] + $sheet.Range("$($split[1])").Text