我在从文档中选择一些特定的XML数据时遇到了一些麻烦。基础数据是营销活动。每个文档可以有多个事件。每个活动的内部都有多名与会者和注册人。我开始在SelectNodes()
循环中使用foreach
并在转换为CSV之前将其读入哈希表。
对于单个事件,这似乎工作正常,但对于多个事件,行不一致,偶数与其他记录数据不同步。我现在正在考虑将整个XML导出为CSV并让ETL工具从那里获得控制权。
我的理解存在差距,并且想知道是否有人知道如何将多个特定XML属性选择到CSV中,但它保持序列顺序。
我的PowerShell代码:
cls
[xml]$xml = Get-Content ("D:\sample.xml")
$dataTable = @()
$eventNodes = $xml.SelectNodes('//event')
foreach ($event in $eventNodes) {
$eventid = $event.eventid
$eventtitle = $event.eventtitle.InnerText
$eventtime = $event.eventtime
# get registrant data
$registrantNodes = $xml.SelectNodes('//registrant')
foreach ($registrant in $registrantNodes) {
$firstname = $registrant.firstname.InnerText
$lastname = $registrant.lastname.InnerText
$city = $registrant.city.InnerText
$state = $registrant.state.InnerText
$country = $registrant.country.InnerText
$company = $registrant.company.InnerText
$workphone = $registrant.workphone.InnerText
$email = $registrant.email.InnerText
# get attendee data
$attendeeNodes = $xml.SelectNodes('//attendee')
foreach ($attendee in $attendeeNodes) {
$attendedlive = $attendee.attendedlive.InnerText
$attendedarchive = $attendee.attendedarchive.InnerText
# put all data into holding table
$dataEntry = New-Object PSObject -Property @{
FirstName = $firstname;
LastName = $lastname;
City = $city;
State = $state;
Country = $country;
Company = $company;
WorkPhone = $workphone;
Email = $email;
AttendedLive = $attendedlive;
AttendedArchive = $attendedarchive;
EventID = $eventid;
EventTitle = $eventtitle;
EventTime = $eventtime;
Orginization = 'North America';
}
$dataTable += $dataEntry
}
}
}
# display holding table
$dataTable
$dataTable | Export-Csv -Force -Path "D:\output.csv" -NoTypeInformation
我上传了一个示例XML文件here。布局如下所示:
您的主要问题是所有XPath都是绝对路径 - 它们都从文档的根开始。当您查询//registrant
时,XML文档不会神奇地为您提供属于您在本特定代码行中所谓的“当前”事件的所有注册人。它将为您提供所有活动的所有注册人,因为这就是您所要求的。如果您想要相对结果,请使用相对导航,即以当前元素(XPath中的.
)开头的XPath,如本例所示。
您的第二个问题是注册人和与会者通过eventuserid
相互关联。您不能简单地查询任何注册人,您必须考虑该ID才能选择正确的ID。你的代码没有这样做,幸运的是它在XPath中非常简单。
你的第三个问题是你从上到下看整个任务。活动 - 注册人 - 与会者。这就是你的XML的结构,但实际上你需要每个与会者的CSV中有一个输出行,并为该人提供一些相关数据。因此,自下而上这样做是明智的:首先是与会者,然后是相应的注册人和事件。
考虑以下代码:
cls
$xml = New-Object xml
$xml.Load("D:\sample.xml")
$allAttendees = $xml.SelectNodes('//attendee') | ForEach-Object {
$attendee = $_
$event = $attendee.SelectSingleNode('./ancestor::event[1]')
$registrant = $event.SelectSingleNode("./registrants/registrant[eventuserid = '$($attendee.eventuserid)']")
New-Object PSObject -Property @{
FirstName = $registrant.firstname
LastName = $registrant.lastname
City = $registrant.city
State = $registrant.state
Country = $registrant.country
Company = $registrant.company
WorkPhone = $registrant.workphone
Email = $registrant.email
AttendedLive = $attendee.attendedlive
AttendedArchive = $attendee.attendedarchive
EventID = $event.eventid;
EventTitle = $event.eventtitle
EventTime = $event.eventtime
Orginization = 'North America';
}
}
$allAttendees | Export-Csv -Force -Path "D:\output.csv" -NoTypeInformation
笔记
.
开头以引用该节点。ForEach-Object
体生成一个对象数组而不需要将它们添加到临时变量的方式。这就是$allAttendees
的任务如何在上面工作。"...$($attendee.eventuserid)..."
结构。.InnerText
是多余的。 Powershell会自动为您完成。