2016-03-02 5 views
2

У меня есть две большие (> 100 МБ, несколько миллионов строк каждая) XML-файлы, которые структурированы следующим образом.Powershell - сравнить части двух больших файлов 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 & Get-Content, но одной из проблем, с которыми я столкнулся, являются другие атрибуты, которые содержатся в каждом файле XML. Оба XML файлов могут иметь

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

, но разница в том, что xmlfile1 может иметь разные значения для нормы & флагов атрибутов, чем xmlfile2. Который делает с использованием Compare-Object & Get-Content флаг все.

Использование Powershell, как бы вы сделали сравнение, игнорируете атрибуты «шум», но отвечаете на <value> только для атрибута PART?

EDIT

Для уточнения - второй файл XML будет почти идентичен показанному. Однако, что может быть иначе: <doc id='0'> в любом из XML, <field name='PART' будет таким же, но другие атрибуты norm='-1' и flags='Idfp--S--Ni08--------'> могут быть разными. Я хотел бы найти атрибут PART, игнорировать остальные атрибуты в field и определить, существует ли содержимое в <val> во втором XML-файле.

+0

У вас есть образец xmlfile2? Как бы вы узнали, какие элементы сравнивать? Поскольку вы говорите, что значение PART МОЖЕТ быть одинаковым, тогда это бесполезно. Что статично? Статический doc-id? –

+0

@ FrodeF.- Я добавил дополнительную информацию для ясности. Атрибут 'name' будет статичным, а остальные (' norm', 'flags') могут иметь разные значения. Если атрибут 'name = PART', я хочу определить, находится ли что-то в' 'в XML-файле 2. Игнорируйте остальные атрибуты в этой строке. Кроме того, значение в '

Как я уже сказал, там вполне может быть более эффективным ответ, но это должно быть эффективным, тем не менее.

+0

Мне нужен более эффективный ответ. С двумя большими XML-файлами 'Get-Content' выдает ошибку« Out of Memory », прежде чем я смогу загрузить первый файл XML. Прекрасно работает для небольших файлов. –

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' 

Это просто концепция доказательства правильности использования регулярных выражений. С помощью такого синтаксического анализа текста (используя exhtables для хранения значений) вы можете использовать StreamReader для чтения одной строки за раз, чтобы минимизировать использование памяти.

Смежные вопросы