【问题标题】:XML to CSV: Print lines for each child node and sometimes child node is missingXML 到 CSV:打印每个子节点的行,有时缺少子节点
【发布时间】:2016-01-12 23:06:14
【问题描述】:

我需要将下面的 XML 转换为 csv。这很棘手,原因有两个:1) 有多个 Support_Order_Detail 子元素,每个子元素需要单独一行 2) 第二条记录中没有 Support_Order_Detail 子元素。我在 Support_Order_Detail 上使用了前兄弟匹配,并使用 not[] 函数来处理没有 Support_Order_Detail 节点的记录。我已经包含了我一直在努力使用的 XSLT,它确实有效。我得到正确的输出。但是,我知道有更好的方法,但我的调试器中确实出现错误:

/Report_Data/Report_Entry[2] 的不明确规则匹配 匹配“Report_Data/Report_Entry[not(Support_Order_Detail)]” 和“Report_Data/Report_Entry”

我希望了解是否有人可以建议我不会有重复代码的地方,即两个模板。我想用 not[] 函数去掉那个。

Doe,Jane,Child Support,Mandatory,12345
Doe,Jane,Child Support,Mandatory,12345
Dole,Bob,Student Loan,Federal,56789

.

    <Report_Data>
      <Report_Entry>
          <Worker>
              <Last_Name>Doe</Last_Name>
              <First_Name>Jane</First_Name>
          </Worker>
          <Lien_Type>Support Order</Lien_Type>
          <Lien_Sub_Type>Mandatory</Lien_Sub_Type>
          <Support_Order_Detail>
              <Support_Type Descriptor="Current Child Support">
                  <ID type="Support_Type">CS</ID>
              </Support_Type>
          </Support_Order_Detail>
          <Support_Order_Detail>
              <Support_Type Descriptor="Past Due Child Support">
                  <ID type="Support_Type">PDCS</ID>
              </Support_Type>
          </Support_Order_Detail>
          <Case_ID>12345</Case_ID>
      </Report_Entry>
      <Report_Entry>
          <Worker>
              <Last_Name>Dole</Last_Name>
              <First_Name>Bob</First_Name>
          </Worker>
          <Lien_Type>Student Loan</Lien_Type>
          <Lien_Sub_Type>Federal</Lien_Sub_Type>
          <Case_ID>56789</wd:Case_ID>
      </Report_Entry>
    </Report_Data>

XSLT:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema"
     exclude-result-prefixes="xs"
     xmlns:xtt="urn:com.workday/xtt"
     version="2.0">


    <xsl:template match="/">
        <File xtt:separator="&#xd;&#xa;">
            <xtt:class xtt:name="dateformat" xtt:dateFormat="dd/MM/yyyy"/>
            <xsl:apply-templates/>
        </File>
    </xsl:template>

    <xsl:template match="Report_Data/Report_Entry"> 
        <xsl:apply-templates select="Support_Order_Detail"/>
    </xsl:template>


    <xsl:template match="Support_Order_Detail" >
        <Record xtt:separator=",">
            <LastName xtt:maxLength="14"><xsl:value-of select="preceding-sibling::Worker/Last_Name[1]"/></LastName>
            <FirstName xtt:maxLength="17"><xsl:value-of select="preceding-sibling::Worker/First_Name[1]"/></FirstName>
            <LienType>
            <xsl:choose>
                <xsl:when test="preceding-sibling::Lien_Type[1] = 'Support Order' and Support_Type/@Descriptor = 'Current Child Support'">
                    <xsl:text>Child Support</xsl:text>
                </xsl:when>
                <xsl:when test="preceding-sibling::Lien_Type[1] = 'Support Order' and Support_Type/@Descriptor = 'Past Due Child Support'">
                    <xsl:text>Child Support</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="Support_Type/@Descriptor"/>
                </xsl:otherwise>
            </xsl:choose>
            </LienType>
            <LienSubType><xsl:value-of select="preceding-sibling::Lien_Sub_Type[1]"/></LienSubType>
            <CaseID xtt:maxLength="20"><xsl:value-of select="following-sibling::Case_ID"/></CaseID> 
        </Record>
    </xsl:template>

    <xsl:template match="Report_Data/Report_Entry[not(Support_Order_Detail)]" >
        <Record xtt:separator=",">
            <LastName xtt:maxLength="14"><xsl:value-of select="Worker/Last_Name"/></LastName>
            <FirstName xtt:maxLength="17"><xsl:value-of select="Worker/First_Name"/></FirstName>
            <LienType><xsl:value-of select="Lien_Type"/></LienType>
            <LienSubType><xsl:value-of select="Lien_Sub_Type"/></LienSubType>
            <CaseID xtt:maxLength="20"><xsl:value-of select="Case_ID"/></CaseID> 
        </Record>
    </xsl:template>

</xsl:stylesheet> 

预期的 XML 结果:

    <Record xtt:separator=",">
        <LastName xtt:maxLength="14">Doe</LastName>
        <FirstName xtt:maxLength="17">Jane</FirstName>
        <LienType>Child Support</LienType>
        <LienSubType>Mandatory</LienSubType>
        <CaseID xtt:maxLength="20">12345</CaseID>
    </Record>
    <Record xtt:separator=",">
        <LastName xtt:maxLength="14">Doe</LastName>
        <FirstName xtt:maxLength="17">Jane</FirstName>
        <LienType>Child Support</LienType>
        <LienSubType>Mandatory</LienSubType>
        <CaseID xtt:maxLength="20">12345</CaseID>
    </Record>
    <Record xtt:separator=",">
        <LastName xtt:maxLength="14">Dole</LastName>
        <FirstName xtt:maxLength="17">Bob</FirstName>
        <LienType>Student Loan</LienType>
        <LienSubType>Federal</LienSubType>
        <CaseID xtt:maxLength="20">56789</CaseID>
    </Record>

【问题讨论】:

  • 这令人困惑,因为 (1) 您的样式表确实 生成 CSV 并且 (2) 它引用了您的 XML 中不存在的节点,例如CF_ADP_Site_ID.
  • @michael.hor257k Workday 扩展 xtt(XML 到文本)将在 Workday 应用程序中处理时将生成的 XML 转换为 csv。不过你是对的,上面 XSLT 的直接结果将创建一个新的 xml 文档,以允许 xtt 转换为文本。不应包含 CF_ADP_Site_ID 行。我发现了另一个错误。我还忘记在第一个模板中包含最后一行: 和最后一行:
  • 既然您询问的是 XSLT,我建议您发布您期望获得的 XML 结果作为您的 XSL 转换的结果。 --附言请不要在 cmets 中发布代码 - 改为编辑您的问题。
  • 是的,当然。第一次犯错......

标签: xml csv xslt


【解决方案1】:

所以我不知道您使用的是什么扩展或工具,但是在纯 XSLT 中执行此操作非常简单;匹配所有Report_Entry 元素,如果它们有任何Support_Order_Detail 元素,则循环遍历除last() 之外的所有元素(因为我们已经为此生成了一行):

(我稍微修改了这个以使用变量而不是重复选择)

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="text" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

    <xsl:template match="//Report_Entry">
        <xsl:variable name="row">
            <xsl:value-of select="Worker/Last_Name" />,<xsl:value-of select="Worker/First_Name" />,<xsl:value-of select="Lien_Type"/><xsl:value-of select="Lien_Sub_Type"/>,<xsl:value-of select="Case_ID"/>
            <xsl:text>&#13;</xsl:text>
        </xsl:variable>

        <xsl:value-of select="$row" />

        <xsl:for-each select="Support_Order_Detail[position() != last()]">            
            <xsl:value-of select="$row" />
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="text()"/>


</xsl:transform> 

http://xsltransform.net/bFN1y9B/1

【讨论】:

    【解决方案2】:

    我的调试器确实出错了:

    /Report_Data/Report_Entry[2] 的不明确规则匹配同时匹配 “Report_Data/Report_Entry[not(Support_Order_Detail)]”和 "Report_Data/Report_Entry"

    改一下就可以消除错误:

    <xsl:template match="Report_Data/Report_Entry">
    

    到:

    <xsl:template match="Report_Entry">
    

    同样,你可以替换:

    <xsl:template match="Report_Data/Report_Entry[not(Support_Order_Detail)]" >
    

    与:

    <xsl:template match="Report_Entry[not(Support_Order_Detail)]" >
    

    匹配模式不是选择表达式:它不需要包含路径,除非模式没有它是不明确的(例如,如果您在不同的位置或级别有 Report_Entry 并且需要区别对待它们。


    至于实现相同结果的“更好”代码,这在很大程度上是个人喜好问题。恕我直言,如果您有两种类型的 Report_Entry 并且每种类型都需要以不同方式处理,那么最好使用单独的模板来处理它们,除非差异很小(不是您的情况)。

    不过,我会考虑简化 xsl:choose 块。

    【讨论】:

    • 谢谢,@micheal.hor257k!感谢您对拥有两个模板的意见。我发现有两个模板更容易在心理上解析。但我很欣赏@Dan Field 解决方案。它更清洁 - 没有重复。我拥有的代码甚至比我发布的更复杂(当然),但总结了我想要完成的事情。正如你所建议的,我已经简化了我的 when 语句。再次感谢!
    • @Lengel 如果您的问题得到解答,请通过接受答案来关闭它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-04
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    • 2017-07-30
    • 1970-01-01
    相关资源
    最近更新 更多