2017-12-28 3 views
0

문서에서 특정 XML 데이터를 선택하는 데 문제가 있습니다. 기본 데이터는 마케팅 이벤트입니다. 문서 당 여러 개의 이벤트가있을 수 있습니다. 각 이벤트에는 여러 명의 참석자와 등록자가 있습니다. foreach 루프 안에 SelectNodes()을 사용하여 시작한 다음이를 CSV로 변환하기 전에 해시 테이블로 읽습니다.특정 노드 속성 선택

단일 이벤트의 경우에는 정상적으로 작동하는 것처럼 보였지만 여러 이벤트의 경우 행이 일관되지 않았기 때문에 eventid가 다른 레코드 데이터와 동기화되지 않았습니다. 이제 전체 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을 업로드했습니다. 레이아웃은 다음과 같습니다

XML layout

+1

이렇게 XML 파일을로드하지 마십시오. PowerShell에서 XML 파일을로드하는 올바른 방법은'$ xml = New-Object xml; $ xml.Load ($ 경로)'. 이 방법을 사용하면 XML 파일 인코딩을 자동으로 자동 감지합니다. 'Get-Content'를 사용하면 파일 인코딩이'Get-Content'의 기본값과 일치하지 않을 때 데이터가 깨집니다. 이것은 똑똑하지 않은 손가락 교차와 같습니다. – Tomalak

답변

1

기본 문제는 모든 XPath의 절대 경로가 있다는 것입니다 - 그들은 모두 문서의 루트에서 시작합니다. //registrant을 쿼리하면 XML 문서는이 특정 코드 행에서 "현재"이벤트로 생각하는 것에 속하는 모든 등록자를 마술처럼 제공하지 않습니다. 귀하가 요청하신 내용이므로 모든 이벤트에 대해 등록자에게 알려 드릴 것입니다. 이 경우와 같이 상대적인 결과가 필요한 경우 상대 경로 (즉, XPath의 현재 요소 (.)로 시작하는 XPath)를 사용합니다.

두 번째 문제는 등록자와 참석자가 eventuserid으로 서로 관련되어 있다는 것입니다. 단순히 등록자를 쿼리 할 수는 없으므로 올바른 ID를 선택하려면 해당 ID를 고려해야합니다. 코드가 그렇게하지 않는다. 다행히 XPath에서는 매우 간단하다.

3 차적인 문제는 전체 작업을 하향식으로 보는 것입니다. 이벤트 - 등록자 - 참석자. 그것이 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 

노트

  • 모든 XPath의 특정 노드에서 호출하고 해당 노드를 참조하는 .로 시작됩니다

    이 코드를 생각해 보자.
  • PowerShell에서 스크립트 블록 내에서 생성하고 변수에 저장하지 않은 모든 값은 해당 스크립트 블록의 반환 값에 포함됩니다. 이것이 ForEach-Object 본문이 임시 변수에 추가 할 필요없이 개체 배열을 생성하는 방법입니다. 이것이 위의 $allAttendees에 대한 할당 방법입니다.
  • about : XPath 조건 자와 XPath 축, 그리고 "...$($attendee.eventuserid)..." 구문에 익숙하지 않은 경우 Powershell에서 문자열 보간법이 어떻게 작동하는지 알아보십시오.
  • .InnerText을 명시 적으로 사용하는 것은 불필요합니다. Powershell이 ​​자동으로 그렇게 할 것입니다.
+0

안녕하세요 Tomalak, 매우 자세한 답장과 답변을 보내 주셔서 감사합니다. XML 읽기와 코드 작업은 블로그 게시물의 스크랩을 기반으로 한 첫 번째 작업이었습니다.하향식 로직 문제는 실제로 나를 괴롭혔다. 나는 XML이 순차적으로 읽힐 것이고 행으로 로딩하면 시퀀스를 처리 할 것이라고 생각했다. 그리고 XPath에 대한 팁을 주셔서 감사합니다. 축이나 술어에 대해 들어 보지 못했기 때문에 지금 가서 약간의 독서를하겠습니다. 게시 한 코드가 이제 합리적입니다. 내가 가진 지식 격차를 해결할 수 있도록 도와 주신 것을 진심으로 환영하며 다시 한번 감사드립니다. –

+0

대단히 환영합니다. 의견을 보내 주셔서 감사합니다. 문서를 순차적으로 읽는 XML API가 있지만 Powershell에서 기본적으로 사용하는 API는 그렇게하지 않습니다. 이 모든 것을 하나의 큰 트리로 RAM으로 읽어 들여 XPath로 분기를 따라 트리를 탐색 할 수 있습니다. 이 방법은 RAM에 쉽게 들어갈 수있는 XML 파일에 적합합니다. GB 크기의 XML 파일을 만들면 순차적 API가 유용 해집니다. Axes에 대한이 그래픽 표현이 도움이된다는 것을 알았습니다. https://our.umbraco.org/documentation/reference/templating/macros/xslt/xpath-axes-and-their-shortcuts – Tomalak