在不同深度访问相同 XML 的多个元素以转换为 R tibble

问题描述

我有一个相当深的旅行数据 xml 文件,我已经在这里匿名了。我想提取多个航段的优惠券状态并将它们附加到行程 ID。我在使用 xml2 包时遇到了非常困难的时间,我认为原因是我的一些 XML 数据以文本终止,而一些以属性终止。我尝试将 xml 转换为带有 as_list() 的列表。我也尝试从 xml_find_all() 开始,但是无论我搜索哪个节点(例如,票务或优惠券应该可以工作),都会得到 0 的节点集。数据如下:

<?xml version="1.0" encoding="UTF-8"?>
<eTicketCouponRS xmlns="http://webse" xmlns:ns4="http://s" xmlns:stl="http://se" Version="2.0.0">
   <stl:ApplicationResults status="Complete">
      <stl:Success timeStamp="2021-06-16T11:39:52-05:00" />
   </stl:ApplicationResults>
   <TicketingInfos>
      <TicketingInfo>
         <Ticketing AgencyCity="DCA" AgentWorkArea="A" IATA_Number="0952" IssuingAgent="A" PrimeHostID="1S" PseudoCityCode="5SE0" TransactionDateTime="2021-06-16T11:39">
            <CouponData informationSource="S" IssueDate="2021-03-29" NumBooklets="1" TicketMedia="E" TicketMode="63">
               <AirItineraryPricingInfo>
                  <fareCalculation>
                     <Text>SAN AA X/E/DFW AA TYO M0.00NUC0.00END ROE1.00    XFSAN4.5DFW4.5</Text>
                  </fareCalculation>
                  <ItinTotalfare>
                     <Basefare Amount="0.00" CurrencyCode="USD" />
                     <Taxes>
                        <Tax Amount="19.10" TaxCode="US" />
                        <Tax Amount="5.60" TaxCode="AY" />
                        <Tax Amount="9.00" TaxCode="XF" />
                     </Taxes>
                     <Totalfare Amount=".70" CurrencyCode="USD" />
                  </ItinTotalfare>
                  <PassengerTypeQuantity Code="GV1" />
               </AirItineraryPricingInfo>
               <Coupons>
                  <Coupon CodedStatus="OK" Number="1" StatusCode="RFND">
                     <FlightSegment DepartureDateTime="2021-08-13T06:15" FlightNumber="2535" RPH="1" ResBookDesigCode="V">
                        <DestinationLocation LocationCode="DFW" />
                        <fareBasis Code="VCA" />
                        <MarketingAirline Code="AA" FlightNumber="2535" />
                        <OperatingAirline Code="AA" />
                        <OriginLocation LocationCode="SAN" />
                     </FlightSegment>
                  </Coupon>
                  <Coupon CodedStatus="OK" Number="2" StatusCode="RFND">
                     <FlightSegment ConnectionInd="X" DepartureDateTime="2021-08-13T12:20" FlightNumber="175" RPH="2" ResBookDesigCode="V">
                        <DestinationLocation LocationCode="HND" />
                        <fareBasis Code="VCA" />
                        <MarketingAirline Code="AA" FlightNumber="175" />
                        <OperatingAirline Code="AA" />
                        <OriginLocation LocationCode="DFW" />
                        <fareTypeClass>PG</fareTypeClass>
                        <fareTypeRule>OW-GO</fareTypeRule>
                     </FlightSegment>
                  </Coupon>
               </Coupons>
               <CustomerInfo>
                  <Customer>
                     <Invoice Number="126" />
                     <Payment ApprovalID="03" RPH="1" ReferenceNumber="XXXXXXXXXXXX" Type="CC">
                        <CC_Info>
                           <PaymentCard Code="VI" ExpirationDate="XX-XX" />
                        </CC_Info>
                     </Payment>
                     <PersonName NameReference="PCS" PassengerType="GV1">
                        <Givenname>VER</Givenname>
                        <Surname>DE</Surname>
                     </PersonName>
                  </Customer>
               </CustomerInfo>
               <ItineraryRef CustomerIdentifier="R5" ID="EXAMPLE" />
            </CouponData>
         </Ticketing>
      </TicketingInfo>
   </TicketingInfos>
</eTicketCouponRS>

我有大约 100 个单独加载并拉出一个包含以下列的小表格: SuccTimeStamp TransacTimeStamp ItineraryID CouponNumber StatusCode Origin Destination OperatingAirline FlightNumber。

您可以看到这些元素中的每一个都位于 xml 的不同深度,并且每个旅行行程都有不同数量的优惠券,从 1 到 10 不等。我还在 hrbrmstr 上找到了一篇有用的帖子,从 2018 年开始帮助某人,但我无法获得类似的解决方案来“查看”我的节点,而且我不确定这是我的代码还是我的 xml 数据。

感谢任何帮助!

解决方法

对于需要扁平化以满足最终使用需求(例如 R)的嵌套 XML 文件,请考虑 XSLT,这是一种旨在转换 XML 文件的专用语言。您可以使用 xslt 包(xml2 的姐妹)在 R 中运行 XSLT 1.0 脚本。或者,您可以使用专用的 XSLT processor 并让 R 使用 system() 调用外部程序。与 SQL 一样,XSLT 是一种工业化的、可移植的语言,不限于 R。

在 XSLT 中,由于您的粒度是优惠券,您可以从 <Coupon> 级别提取并使用 ancestor:: XPath ax 来检索更高级别的信息。由于需要默认命名空间,因此使用了长长的 <xsl:element>IATA_Number 假定为 ItineraryID

XSLT (另存为 .xsl 文件,一个特殊的 .xml 文件)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                              xmlns:w="http://webse"
                              xmlns:stl="http://se">
    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/w:eTicketCouponRS">
     <xsl:copy>
       <xsl:apply-templates select="descendant::w:Coupon"/>
     </xsl:copy>
    </xsl:template>
    
    <xsl:template match="w:Coupon">
     <xsl:copy>
          <xsl:element name="SuccTimeStamp" namespace="http://webse">
              <xsl:value-of select="ancestor::w:eTicketCouponRS/stl:ApplicationResults/stl:Success/@timeStamp"/>
          </xsl:element>
          <xsl:element name="TransacTimeStamp" namespace="http://webse">
              <xsl:value-of select="ancestor::w:Ticketing/@TransactionDateTime"/>              
          </xsl:element>
          <xsl:element name="ItineraryID" namespace="http://webse">
              <xsl:value-of select="ancestor::w:Ticketing/@IATA_Number"/>              
          </xsl:element>
          <xsl:element name="CouponNumber" namespace="http://webse">
              <xsl:value-of select="@Number"/>
          </xsl:element>
          <xsl:element name="StatusCode" namespace="http://webse">
              <xsl:value-of select="@CodedStatus"/>
          </xsl:element>
          <xsl:element name="Origin" namespace="http://webse">
              <xsl:value-of select="w:FlightSegment/w:OriginLocation/@LocationCode"/>
          </xsl:element>
          <xsl:element name="Destination" namespace="http://webse">
              <xsl:value-of select="w:FlightSegment/w:DestinationLocation/@LocationCode"/>
          </xsl:element>
          <xsl:element name="OperatingAirline" namespace="http://webse">
              <xsl:value-of select="w:FlightSegment/w:OperatingAirline/@Code"/>
          </xsl:element>
          <xsl:element name="FlightNumber" namespace="http://webse">
              <xsl:value-of select="w:FlightSegment/@FlightNumber"/>
          </xsl:element>
     </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

Online Demo

R

library(xml2)
library(xslt)

# LOAD XML AND XSLT
doc <- read_xml("/path/to/Input.xml")
style <- read_xml("/path/to/Style.xsl",package = "xslt")

# RUN TRANSFORMATION AND SEE OUTPUT
flat_xml <- xml_xslt(doc,style)
cat(as.character(flat_xml))

# RETRIEVE data NODES
nmsp <- c(w = "http://webse")
recs <- xml2::xml_find_all(flat_xml,"//w:Coupon",ns=nmsp)

# BIND EACH CHILD TEXT AND NAME
df_list <- lapply(recs,function(r) {
  vals <- xml2::xml_children(r)
  
  data.frame(rbind(setNames(c(xml2::xml_text(vals)),c(xml2::xml_name(vals)))))
})

# COMBINE ALL DFS
final_df <- do.call(rbind.data.frame,df_list)
rm(recs,df_list)
final_df
#               SuccTimeStamp TransacTimeStamp ItineraryID CouponNumber StatusCode Origin Destination OperatingAirline FlightNumber
# 1 2021-06-16T11:39:52-05:00 2021-06-16T11:39        0952            1         OK    SAN         DFW               AA         2535
# 2 2021-06-16T11:39:52-05:00 2021-06-16T11:39        0952            2         OK    DFW         HND               AA          175

以上针对单个 XML 运行。对于 100 个单独的文件,将上面包装在用户定义的方法中,并在最后运行 lapply 以获取用于主连接的 XML 数据帧列表。在循环外加载一次 XSLT,因为它不会改变,假设 XML 文件保持相同的结构。

style <- read_xml("/path/to/Style.xsl",package = "xslt")

xml_to_df <- function(xml_file) { ... }
xml_dfs <- lapply(list_of_xml_files,xml_to_df)

master_df <- do.call(rbind.data.frame,xml_dfs)
,

谢谢冻糕!我能够修改您提供的模板 xsl。 xsl 表似乎可以很好地“解析”所有内容!在我将它“展平”后,我只需要做几次as_list()as_tibble()unnest(),然后它就是一个数据框。

谢谢!