【问题标题】:Match all instances of text occurrences in Powershell Core匹配 Powershell Core 中出现的所有文本实例
【发布时间】:2020-04-22 18:02:08
【问题描述】:

我有以下 html 表格:

<!DOCTYPE html>
<html>
<head>
<style>
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}
</style>
</head>
<body>

<h2>HTML Table</h2>

<table>
  <tr>
    <th>Company</th>
    <th>Contact</th>
    <th>Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Maria Anders</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Centro comercial Moctezuma</td>
    <td>Francisco Chang</td>
    <td>Mexico</td>
  </tr>
  <tr>
    <td>Ernst Handel</td>
    <td>Roland Mendel</td>
    <td>Austria</td>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>Helen Bennett</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Yoshi Tannamuri</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Giovanni Rovelli</td>
    <td>Italy</td>
  </tr>
</table>

</body>

我想匹配所有出现的&lt;th&gt;table headers&lt;/th&gt;&lt;td&gt;table data&lt;/td&gt;

对于&lt;td&gt;table data&lt;/td&gt;,我已经成功调用了一个webrequest,得到了html文件,现在正在提取表格内容:

$Table = $Data.Content
$NumberOfColumns = ($Table | Select-String "<th>" -AllMatches).Matches.Count
$NumberOfRows = ($Table | Select-String "<td>" -AllMatches).Matches.Count

$AllMatches = @()
$Found = $Table -match "(?<=<td>)[a-zA-Z0-9 _-]{1,99}(?=</td>)"
ForEach ($Row in $NumberOfRows)
{
    If ($Found -eq $True)
    {
        $AllMatches += $Matches
    }
}
$AllMatches

我得到这个输出:

Name                     Value
----                          -----
0                              Alfreds Futterkiste

我想获取嵌入在thtd 中的所有匹配项的列表(我正在运行Powershell Core 6.2,因此ParsedHtml 方法不是一个选项。我想解析表格手动)。

非常感谢任何建议。

【问题讨论】:

  • Parsing HTML with regex is a hard job HTML 和正则表达式不是好朋友。使用解析器,它更简单、更快且更易于维护。 gallery.technet.microsoft.com/Powershell-Tip-Parsing-49eb8810
  • 这适用于 PSCore 6.2 吗?我不相信它依赖于 IE 解析器,还是我错了?
  • HTMLFile COM 对象应该可以在任何常规 Windows 系统上使用,但它不会为您提供最新的 DOM API 方法(querySelector 之类的东西将不可用)。
  • 除此之外,使用 HTML Agility Pack(或其他解析器,尽管我相信这是 .NET 中最成熟、最通用的解析器)是您唯一明智的选择。
  • 使用称职的 HTML 解析器。 stackoverflow.com/a/1732454/447901

标签: regex powershell


【解决方案1】:

如前所述,最好使用专用的 HTML 解析器来解析 HTML 文本,因为基于正则表达式的手动解析很脆弱,并且有 severe limitations

但是,鉴于 PowerShell Core (v6+) 没有内置的 HTML 解析,并且您的解析要求很简单,您可以摆脱基于正则表达式的解析在这种情况下

$Table = $Data.Content

# Get all <th> values (column names) and count them.
$colNames = [regex]::Matches($Table, '(?<=<th>).+?(?=</th>)').Value
$colCount = $colNames.Count

# Create an ordered hashtable with the column names as keys 
# to serve as the template for the output objects.
$oht = [ordered] @{}
foreach ($col in $colNames) { $oht[$col] = $null }

# Get all <td> values (row values).
$rowValues = [regex]::Matches($Table, '(?<=<td>).+?(?=</td>)').Value

# Construct custom objects whose properties are named for the column names
# and whose values are the row values.
$i = 0
foreach ($val in $rowValues) {
  # Assing the row value to the column-appropriate hashtable entry.
  $oht[$i % $colCount] = $val
  if ($i % $colCount -eq ($colCount - 1)) {
    # The last property for the row at hand was filled,
    # construct and output a custom object from the hashtable.
    [pscustomobject] $oht
  }
  ++$i
}

上面生成了一个[pscustomobject] 实例数组,它们按如下方式打印到显示器上(要在变量中捕获数组,只需使用$objs = foreach ($val in $rowValues) ...):

Company                      Contact          Country
-------                      -------          -------
Alfreds Futterkiste          Maria Anders     Germany
Centro comercial Moctezuma   Francisco Chang  Mexico
Ernst Handel                 Roland Mendel    Austria
Island Trading               Helen Bennett    UK
Laughing Bacchus Winecellars Yoshi Tannamuri  Canada
Magazzini Alimentari Riuniti Giovanni Rovelli Italy

如果您愿意按需安装第三方 HTML 解析器,这里是使用HTML Agility Pack NuGet 包的解决方案

注意:在 Windows 上,使用 1.4.6 版的 PackageManagement 模块,安装 NuGet 包可能会失败,抱怨依赖循环 - Unix 平台不受影响。如果需要,请从https://www.nuget.org/packages/HtmlAgilityPack/ 手动下载 NuGet 包。

# Install the HtmlAgilityPack NuGet package on demand.
if (-not (Get-Package -ea Ignore HtmlAgilityPack)) {
  # Make sure that NuGet is registered as a package source.
  if (-not (Get-PackageSource -ea Ignore nuget.org)) {
    $null = Register-PackageSource -ea Stop -ProviderName nuget -name nuget.org -Location https://www.nuget.org/api/v2 -Trusted
  }
  $null = Install-Package -ea Stop HtmlAgilityPack -Scope CurrentUser -Provider NuGet
}

# Load the HtmlAgilityPack assemblies into the current session.
Add-Type -ea Stop -Path ((Get-Package HtmlAgilityPack).Source + '/../lib/netstandard2.0/HtmlAgilityPack.dll')

$Table = $Data.Content

# Create an HTML DOM object and parse the HTML text into it.
$d = [HtmlAgilityPack.HtmlDocument]::new()
$d.LoadHtml($Table)

# Get all <th> values (column names) and count them.
$colNames = $d.DocumentNode.SelectNodes('html/body/table//th').InnerText
$colCount = $colNames.Count

# Create an ordered hashtable with the column names as keys 
# to serve as the template for the output objects.
$oht = [ordered] @{}
foreach ($col in $colNames) { $oht[$col] = $null }

# Get all <td> values (row values).
$rowValues = $d.DocumentNode.SelectNodes('html/body/table//td').InnerText

# Construct custom objects whose properties are named for the column names
# and whose values are the row values.
$i = 0
foreach ($val in $rowValues) {
  # Assing the row value to the column-appropriate hashtable entry.
  $oht[$i % $colCount] = $val
  if ($i % $colCount -eq ($colCount - 1)) {
    # The last property for the row at hand was filled,
    # construct and output a custom object from the hashtable.
    [pscustomobject] $oht
  }
  ++$i
}

输出同上。

注意.SelectNodes() 如何与 XPath 查询一起使用以提取感兴趣的节点。

【讨论】:

    【解决方案2】:

    如果你的表和示例中一样接近 XML,你可以直接使用[System.Net.WebUtility]::HtmlDecode 方法将其解析为 XML:

    If ($Data.Content -Match '<table>[\s\S]*<\/table>') {
        [xml]$Xml = [System.Net.WebUtility]::HtmlDecode($Matches[0])
        $Header = $Null
        $Xml.DocumentElement.SelectNodes('//tr') | ForEach-Object {
            If ($Null -eq $Header) {
                $Header = $_.GetElementsByTagName('th').'#text'
            } Else {
                $i = 0; $Property = [Ordered]@{}
                $_.GetElementsByTagName('td').'#text'.ForEach{ $Property[$Header[$i++]] = $_ }
                [PSCustomObject]$Property
            }
        }
    }
    

    (在 Windows 和 Raspbian 上使用 PowerShell Core 测试)

    【讨论】:

      猜你喜欢
      • 2020-07-23
      • 1970-01-01
      • 2016-09-23
      • 2018-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-12
      相关资源
      最近更新 更多