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

access multiple elements of same XML at different depths to convert to 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 个要单独加载,然后拉出一个小的 table,其中包含以下列: SuccTimeStamp TransacTimeStamp ItineraryID CouponNumber StatusCode 出发目的地 OperatingAirline FlightNumber。

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

感谢任何帮助!

对于嵌套的 XML 文件,您需要将其展平以满足最终使用需求(例如 R),请考虑 XSLT, the special-purpose language designed to transform XML files. You can run XSLT 1.0 scripts in R using the xslt package (sister to xml2). Alternatively, you can use a dedicated 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 数据帧列表。假设 XML 文件保留相同的结构,在循环外加载一次 XSLT,因为它不会改变。

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 sheet 似乎可以很好地“解析”所有内容!在我把它“扁平化”之后,我所要做的就是 as_list()as_tibble()unnest() 几次,然后它就是一个数据框。

谢谢!