我有一个包含各种元素的XML文件。属性。有些将是共同的,但不是每个节点都有所有(或相同)的节点。示例XML如下:
<?xml version='1.0' encoding='UTF-8'?>
<index>
<doc id='0'>
<field name='IDTREE' norm='124' flags='Idfp--S--Ni08--------'>
<val>-</val>
</field>
<field name='role' norm='114' flags='Idfp--S--Ni08--------'>
<val>administrators</val>
</field>
<field name='internalid' norm='117' flags='Idfp--S--Ni08--------'>
<val>123456</val>
</field>
<field name='version' norm='124' flags='Idfp--S--Ni08--------'>
<val>test</val>
</field>
<field name='id' norm='124' flags='Idfp--S--Ni08--------'>
<val>myname-123456-test</val>
</field>
<field name='siteId' norm='124' flags='Idfp--S--Ni08--------'>
<val>myname</val>
</field>
</doc>
<doc id='1'>
<field name='internalid' norm='117' flags='Idfp--S--Ni08--------'>
<val>98765</val>
</field>
<field name='version' norm='124' flags='Idfp--S--Ni08--------'>
<val>dev</val>
</field>
<field name='category' norm='113' flags='Idfp--S--Ni08--------'>
<val>biography</val>
</field>
<field name='display' norm='120' flags='Idfp--S--Ni08--------'>
<val>false</val>
</field>
<field name='publisher' norm='124' flags='Idfp--S--Ni08--------'>
<val>-</val>
</field>
<field name='id' norm='124' flags='Idfp--S--Ni08--------'>
<val>myname-98765-dev</val>
</field>
<field name='siteId' norm='124' flags='Idfp--S--Ni08--------'>
<val>myname</val>
</field>
</doc>
</index>
我想要做的是将这个(非常大的)XML文件转换为可以导入Excel(或SQL)的文本文件(管道分隔)。我希望输出像:
id|siteId|version|internalid|role|IDTREE|category|display|publisher myname-123456-test|myname|test|123456|administrators|-||| myname-98765-dev|myname|dev|98765|||biography|false|-
我想我需要对XML数据进行2次传递,第一次获取列名称,第二次将数据添加到相应的字段以输出到文本文件。
我知道每个文档至少有4个相同的字段节点:id,siteId,version和internalid。其他一切都会有所不同。
我最初的想法是通过XML进行1次传递,将字段的名称属性添加到哈希表中。在第2遍,我将使用哈希表循环通过&amp;将每个字段分配到输出上的适当位置。
我现在正在使用它来读取XML文件。
$f = [System.Xml.XmlReader]::Create("C:\Test\MyXMLFile.xml")
while ($f.read()) {
switch ($f.NodeType) {
([System.Xml.XmlNodeType]::Element) {
if ($f.Name -eq "doc") {
$e = [System.Xml.Linq.XElement]::ReadFrom($f)
$nbr = [String] $e.Attribute("id").Value
$fields = $e.Descendants("field")
foreach ($fld in $fields) {
$z = $fld.FirstAttribute.Value
$z1 = $fld.Element("val").Value
}
# write output
}
}
}
}
有没有比我考虑的方式更好的方法呢?
答案 0 :(得分:3)
正如您自己指出的那样,Ansgar has already shown,最好的程序是:
话虽这么说,如果您正在使用HUGE xml文件,那么使用XmlReader的方法可能比解析整个文件更快,内存更少。
我会简化您当前的代码,并将其拆分为两个相似但不同的操作。
让我们从第1步开始,收集字段名称:
# Import the XElement-to-XML linq assembly
Add-Type -AssemblyName System.Xml.Linq |Out-Null
function Get-FieldNames
{
param(
[string]$Path = "C:\Test\MyXMLFile.xml",
[switch]$AsHashTable
)
# Create reader
$xmlReader = [System.Xml.xmlReader]::Create($Path)
# Set up a dictionary
$hashTable = [ordered]@{}
# Read through the file
while ($xmlReader.Read())
{
# Only interested in the <doc> elements
if($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $xmlReader.Name -eq "doc")
{
$docElement = [System.Xml.Linq.XElement]::ReadFrom($xmlReader)
foreach ($field in $docElement.Descendants("field"))
{
# Grab name of each field entry and set dictionary entry
$fieldName = $field.Attribute("name").Value
$hashTable[$fieldName] = $null
}
}
}
if($AsHashTable)
{
return $hashTable
}
else
{
return $hashTable.Keys
}
}
现在我们可以使用第一个函数为属性表创建一个模板,以后我们可以使用New-Object -Property
:
$objectTemplate = Get-FieldNames -AsHashTable
大!全部设置为解析实际值。与以前完全相同的策略:
function Get-XMLFieldValues
{
param(
[string]$Path = "C:\dev\test\huge.xml",
[hashtable]$Template
)
# Create reader
$xmlReader = [System.Xml.xmlReader]::Create($Path)
# Read through the file
while ($xmlReader.Read())
{
# Only interested in the <doc> elements
if($xmlReader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $xmlReader.Name -eq "doc")
{
$docElement = [System.Xml.Linq.XElement]::ReadFrom($xmlReader)
# This is important - clone the template HashTable, don't reuse it
$objectProperties = $Template.Clone()
foreach ($field in $docElement.Descendants("field"))
{
# Grab name of the current field entry
$fieldName = $field.Attribute("name").Value
# Assign the value from the <val> child node
$objectProperties[$fieldName] = $($field.Descendants("val")|Select-Object -First 1).Value
}
# Create and emit a psobject
New-Object psobject -Property $objectProperties
}
}
}
将它与步骤1中的哈希表相结合,等等:
Get-XMLFieldValues -Template $objectTemplate |ft -AutoSize
现在您需要做的就是将输出传输到Export-Csv -Delimite '|'
而不是Format-Table
答案 1 :(得分:1)
我可能会这样做:
[xml]$xml = Get-Content 'C:\Test\MyXMLFile.xml'
# transform XML to list of custom objects
$docs = $xml.SelectNodes('//doc') | ForEach-Object {
$props = @{}
$_.Field | ForEach-Object { $props[$_.name] = $_.val }
New-Object -Type PSObject -Property $props
}
# get list of unique property names
$props = $docs | ForEach-Object {
Get-Member -InputObject $_ -Type NoteProperty
} | Select-Object -Expand Name -Unique
# add missing properties to objects
$docs | ForEach-Object {
$doc = $_
$props | Where-Object {
$_.PSObject.Properties.Name -notcontains $_
} | ForEach-Object {
$doc | Add-Member -Type NoteProperty -Name $_ -Value ''
}
}
# export object list to CSV
$docs | Export-Csv 'C:\Test\MyXMLFile.csv' -Delimiter '|' -NoType