【问题标题】:Powershell - compare parts of two large XML filesPowershell - 比较两个大型 XML 文件的部分内容
【发布时间】:2016-03-02 15:37:57
【问题描述】:

我有两个大型(> 100MB,每个数百万行)XML 文件,其结构如下。

<?xml version='1.0' encoding='UTF-8'?>
<index>
    <doc id='0'>
        <field name='PART' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>12345-678</val>
        </field>
        <field name='DESCRIPTION' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>Part XYX123 Description</val>
        </field>
        <field name='QTY' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>18</val>
        </field>
        <field name='VENDOR' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>ACME</val>
        </field>
        <field name='MFG' norm='-1' flags='Idfp--S--Ni08--------'>
            <val></val>
        </field>
    </doc>
    <doc id='1'>
        <field name='PART' norm='124' flags='Idfp--S--Ni08--------'>
            <val>ABCD-1234</val>
        </field>
        <field name='DESCRIPTION' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>PART ABCD Description</val>
        </field>
        <field name='QTY' norm='-1' flags='Idfp--S--Ni08--------'>
            <val>4</val>
        </field>
        <field name='VENDOR' norm='-1' flags='Idfp--S--Ni08--------'>
            <val></val>
        </field>
        <field name='MFG' norm='-1' flags='Idfp--S--Ni08--------'>
            <val></val>
        </field>
    </doc>
</index>

我需要找到在一个中但不在另一个中的项目,反之亦然。最初,我只想比较属性PART 值,但也想比较其他值(描述等)。

我想确定 xmlfile1 中是否有什么:

index/doc/field name=part/val - 12345-678

也在 xmlfile2 中。如果没有,请将其写入 text/csv 文件。

我尝试过使用Compare-ObjectGet-Content,但我遇到的问题之一是每个 XML 文件中的其他属性。两个 XML 文件都可能有

index/doc/field name=part/val - 12345-678

但不同之处在于 xmlfile1 的 norm & flags 属性值可能与 xmlfile2 不同。这使得使用 Compare-ObjectGet-Content 标记所有内容。

使用 Powershell,您将如何进行比较忽略“噪声”属性,但仅匹配 &lt;value&gt; 上的 PART 属性?

编辑

澄清一下 - 第二个 XML 文件与所示的几乎相同。但是 - 可能不同的是 XML 中的 &lt;doc id='0'&gt;&lt;field name='PART' 相同,但其他属性 norm='-1'flags='Idfp--S--Ni08--------'&gt; 可能不同。我想找到 PART 属性,忽略 field 中的其余属性,并确定 &lt;val&gt; 中的内容是否存在于第二个 XML 文件中。

【问题讨论】:

  • 你有xmlfile2的样本吗?你怎么知道要比较哪些元素?既然你说 PART-value MAY 是一样的,那就没用了。什么是静态的? doc-id 是静态的?
  • @FrodeF.- 为了清楚起见,我添加了更多信息。属性name 将是静态的,但其他属性(normflags)可能具有不同的值。其中属性name=PART,我想确定&lt;val&gt; 中的内容是否在XML 文件2 中。忽略该行中的其余属性。此外,&lt;doc id= 中的值可能不同(两者之间的顺序不同)。

标签: xml powershell compare


【解决方案1】:

很可能有一个更好的基于 XML 的答案来搜索 XPATH 或类似的东西(我无论如何都不是 XML 专家),但如果是我,我会做的是将它全部转换为对象数组。如果您不介意删除除字段名称和值之外的规范、标志或任何其他元素,那么您可以执行以下操作:

[xml]$File1 = Get-Content c:\path\to\file1.xml
[xml]$File2 = Get-Content c:\path\to\file2.xml
$File1Objs = ForEach($Item in $File1.index.doc){
    $Obj=[PSCustomObject]@{'id'=$Item.id}
    $Item.field|%{
        Add-Member -InputObject $Obj -NotePropertyName $_.Name -NotePropertyValue $_.val}
    $Obj
}
$File2Objs = ForEach($Item in $File2.index.doc){
    $Obj=[PSCustomObject]@{'id'=$Item.id}
    $Item.field|%{
        Add-Member -InputObject $Obj -NotePropertyName $_.Name -NotePropertyValue $_.val}
    $Obj
}
Compare-Object $File1Objs $File2Objs -Property Part -PassThru | Where{$_.SideIndicator -eq '<='}|Select * -Exclude SideIndicator | Export-CSV c:\temp\File1Only.txt
Compare-Object $File1Objs $File2Objs -Property Part -PassThru | Where{$_.SideIndicator -eq '=>'}|Select * -Exclude SideIndicator | Export-CSV c:\temp\File2Only.txt

就像我说的,可能有一个更有效的答案,但这应该是有效的。

【讨论】:

  • 我需要一个更有效的答案。对于 2 个大型 XML 文件,Get-Content 在我加载第一个 XML 文件之前会给出 Out of Memory 错误。适用于小文件。
【解决方案2】:

我会使用与@TheMadTechnician 的答案相同类型的解决方案,但它确实需要一些内存(至少在您有大文件时)。但是有一些方法可以优化它。你说它在Get-Content 内存不足。 Get-Content 创建一个对象数组,每行一个字符串。因为无论如何我们都要把它转换成一个 xml 文档,所以我们可以将文件作为一个简单的字符串来读取,这样可以节省大量内存。

如果仍然有问题,您可能需要在具有更多资源的计算机上运行此脚本。当我们可以将整个文件保存到内存开始时,XML 解析会更容易。

$xml = [xml]([System.IO.File]::ReadAllText("c:\path\to\file1.xml"))
$File1Objs = $xml.index.doc | ForEach-Object {
    $Obj = New-Object psobject -Property @{"ID" = $_.id}
    $_.field | ForEach-Object { Add-Member -InputObject $Obj -MemberType NoteProperty -Name $_.Name -Value $_.val }
    $Obj
}
#Throw out the garbage
$xml = $null
[gc]::Collect()

$xml = [xml]([System.IO.File]::ReadAllText("c:\path\to\file2.xml"))
$File2Objs = $xml.index.doc | ForEach-Object {
    $Obj = New-Object psobject -Property @{"ID" = $_.id}
    $_.field | ForEach-Object { Add-Member -InputObject $Obj -MemberType NoteProperty -Name $_.Name -Value $_.val }
    $Obj
}

#Throw out the garbage
$xml = $null
[gc]::Collect()

#One compare to save resources. Compare PART and Description-property (to show off multiple-property-comparison)
$comparison = Compare-Object $File1Objs $File2Objs -Property Part, Description -PassThru

$comparison | Where-Object { $_.SideIndicator -eq '<=' } | Select-Object -Property * -Exclude SideIndicator | Export-CSV -Path "c:\path\to\File1Only.txt" -NoTypeInformation
$comparison | Where-Object { $_.SideIndicator -eq '=>' } | Select-Object -Property * -Exclude SideIndicator | Export-CSV -Path "c:\path\to\File2Only.txt" -NoTypeInformation

您还可以使用基于哈希表的解决方案,在其中存储来自 file1 的值并将值与读取 file2 时的值进行比较。例如:

#Read as single string to save memory
$text = [System.IO.File]::ReadAllText("C:\users\frode\Test.txt")

#Hashtable to store PART-value from file1
$PART = @{}
#Regex to extract PART-value
[regex]::Matches($text,"(?s)doc id='(?<ID>.*?)'>.*?'PART' norm.*?val>(?<PART>.*?)<\/val>") |
ForEach-Object {
    #Store PART-value in hashtable with doc-id as key
    $PART.Add($_.Groups["ID"].Value,$_.Groups["PART"].Value)
}

$text = [System.IO.File]::ReadAllText("C:\users\frode\Test2.txt")
[regex]::Matches($text,"(?s)doc id='(?<ID>.*?)'>.*?'PART' norm.*?val>(?<PART>.*?)<\/val>") |
ForEach-Object {
    #Check if docid was in file1
    if($PART.ContainsKey($_.Groups["ID"].Value)) {
        #If in file1, check if value is different
        if($PART[$_.Groups["ID"].Value] -ne $_.Groups["PART"].Value) {
            "MISMATCH in DocID '$($_.Groups["ID"].Value)' - File1 PART: '$($PART[$_.Groups["ID"].Value])' - File2 PART: '$($_.Groups["PART"].Value)'"
        }
    }
}

输出:

MISMATCH in DocID '0' - File1 PART: '12345-678' - File2 PART: '12345-6789'
MISMATCH in DocID '1' - File1 PART: 'ABCD-1234' - File2 PART: 'ABCD-1235'

这只是一个使用正则表达式的概念验证。使用像这样的文本解析解决方案(使用前哈希表来存储值),您可以使用 StreamReader 一次读取一行以最大限度地减少内存使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-03
    • 2019-08-23
    相关资源
    最近更新 更多