首页 > 解决方案 > 在不同深度访问相同 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 数据。

任何帮助表示赞赏!

标签: rxmlxml2

解决方案


对于需要为最终使用需求(例如 R)进行展平的嵌套 XML 文件,请考虑XSLT,这是一种旨在转换 XML 文件的专用语言。您可以使用该包在 R 中运行 XSLT 1.0 脚本xslt(sister to xml2)。或者,您可以使用专用的XSLT 处理器并让 R 使用system(). 与 SQL 一样,XSLT 是一种行业的可移植语言,不仅限于 R。

在 XSLT 中,由于您的粒度是优惠券,您可以从<Coupon>级别中提取并使用ancestor::XPath 轴来检索更高级别的信息。由于需要默认命名空间,因此使用了长篇大论<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)

推荐阅读