Powershell - 比较两个大型XML文件的部分

时间:2016-03-02 15:37:57

标签: xml powershell compare

我有两个大的(> 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-Object&amp; Get-Content,但我遇到的问题之一是每个XML文件中的其他属性。两个XML文件都可能有

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

但区别在于xmlfile1可能具有不同的标准和值。标记属性而不是xmlfile2。这使得使用Compare-Object&amp; Get-Content标记一切。

使用Powershell,您将如何进行比较,忽略&#34;噪音&#34;属性,但只对<value>属性的PART匹配?

编辑

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

2 个答案:

答案 0 :(得分: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

就像我说的那样,答案可能会更有效,但这应该是有效的。

答案 1 :(得分:0)

我会使用与@ 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'

这只是使用正则表达式的概念验证。使用这样的文本解析解决方案(使用ex.hashtables来存储值),您可以使用StreamReader一次读取一行来最小化内存使用。