问题描述
我有一个相当深的旅行数据 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>
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()
,然后它就是一个数据框。
谢谢!