Xml XSLT多个可重复的子节点

Xml XSLT多个可重复的子节点,xml,csv,xslt,xslt-1.0,Xml,Csv,Xslt,Xslt 1.0,我正在处理一组记录,这些记录具有多个可重复的不同子节点类型。最终目标是创建一个CSV映射,由于嵌套,每个XML记录可以映射到许多CSV行项目 我知道如何为每个使用来创建多个输出行,但我遇到了麻烦,因为有两种不同的情况需要循环 在以下示例中,App是基本记录,SST\u Interval是可重复的(1个或多个),并且ReplacementPart可以有0个或多个PartTypes 我想提取具有以下格式的CSV app|u id | base|u vehicle|id | sst|u interva

我正在处理一组记录,这些记录具有多个可重复的不同子节点类型。最终目标是创建一个CSV映射,由于嵌套,每个XML记录可以映射到许多CSV行项目

我知道如何为每个使用
来创建多个输出行,但我遇到了麻烦,因为有两种不同的情况需要循环

在以下示例中,
App
是基本记录,
SST\u Interval
是可重复的(1个或多个),并且
ReplacementPart
可以有0个或多个
PartType
s

我想提取具有以下格式的CSV

app|u id | base|u vehicle|id | sst|u interval|u id | sst|u interval|u month | part|u type|id

对于下面显示的记录,结果如下所示

915152|18287|646|12|10007
915152|18287|646|12|12277
915152|18287|646|12|18159
915152|18287|32523|24|10007
915152|18287|32523|24|12277
915152|18287|32523646|24|18159
这是记录

<App action="A" id="915152" ref="568874">
    <BaseVehicle id="18287" />
    <EngineVIN id="25" />
    <Note id="8722" vehicleattribute="no" />
    <Position id="1" />
    <MOTOR_Operation id="551841">
        <SkillCode>G</SkillCode>
        <Base_MOTOR_EWT minutes="1" />
        <SST_Interval id="646">
            <SST_IndicatorImage><![CDATA[sstgm140001]]></SST_IndicatorImage>
            <SST_IndicatorText><![CDATA[Change Engine Oil Soon]]></SST_IndicatorText>
            <SST_Frequency id="7" />
            <SST_IntervalMonth><![CDATA[12]]></SST_IntervalMonth>
            <SST_SevereService id="2080" />
            <SST_Note1 id="5117" />
        </SST_Interval>
        <SST_Interval id="32523">
            <SST_IndicatorImage><![CDATA[sstgm140001]]></SST_IndicatorImage>
            <SST_IndicatorText><![CDATA[Change Engine Oil Soon]]></SST_IndicatorText>
            <SST_Frequency id="7" />
            <SST_IntervalMonth><![CDATA[24]]></SST_IntervalMonth>
            <SST_SevereService id="2080" />
            <SST_Note1 id="5117" />
        </SST_Interval>
        <ReplacementPart>
            <PartType id="10007" servicetype_id="1" />
            <PartType id="12277" servicetype_id="1" />
            <PartType id="18159" servicetype_id="1" />
        </ReplacementPart>
    </MOTOR_Operation>
</App>

G
这是我尝试过的XSLT。如果你能给我指出正确的方向,我将非常感激

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="Header|Footer">
  </xsl:template>

  <xsl:template match="App">
    <xsl:apply-templates select="MOTOR_Operation/SST_Interval"/>
  </xsl:template>

  <xsl:template match="PartType">
    <xsl:apply-templates select="@id" mode="csv"/>
  </xsl:template>

  <xsl:template match="SST_Interval">
    <xsl:variable name="tmp">
      <xsl:apply-templates select="ancestor::App/@id" mode="csv"/>
      <xsl:apply-templates select="@id" mode="csv-nl"/>
    </xsl:variable>
    <xsl:for-each select="ancestor::App/MOTOR_Operation/ReplacementPart/PartType">
      <xsl:copy-of select="$tmp"/>
      <xsl:apply-templates select="." />
    </xsl:for-each>

  </xsl:template>

  <xsl:template match="text()|@*" mode="csv">
    <xsl:value-of select="concat(., '|')" />
  </xsl:template>

  <xsl:template match="text()|@*" mode="csv-nl">
    <xsl:value-of select="concat(., '&#xa;')" />
  </xsl:template>

</xsl:stylesheet>

我想你已经不远了。您只需更改
tmp
变量的定义以包含更多字段,并对所有字段使用模式
csv
,然后将模板匹配
PartType
更改为使用
csv nl

试试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="UTF-8"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="Header|Footer">
  </xsl:template>

  <xsl:template match="App">
    <xsl:apply-templates select="MOTOR_Operation/SST_Interval"/>
  </xsl:template>

  <xsl:template match="PartType">
    <xsl:apply-templates select="@id" mode="csv-nl"/>
  </xsl:template>

  <xsl:template match="SST_Interval">
    <xsl:variable name="tmp">
      <xsl:apply-templates select="ancestor::App/@id" mode="csv"/>
      <xsl:apply-templates select="ancestor::App/BaseVehicle/@id" mode="csv"/>
      <xsl:apply-templates select="@id" mode="csv"/>
      <xsl:apply-templates select="SST_IntervalMonth" mode="csv"/>
    </xsl:variable>
    <xsl:for-each select="ancestor::App/MOTOR_Operation/ReplacementPart/PartType">
      <xsl:copy-of select="$tmp"/>
      <xsl:apply-templates select="." />
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="text()|@*" mode="csv">
    <xsl:value-of select="concat(., '|')" />
  </xsl:template>

  <xsl:template match="text()|@*" mode="csv-nl">
    <xsl:value-of select="concat(., '&#xa;')" />
  </xsl:template>
</xsl:stylesheet>

我认为你已经不远了。您只需更改
tmp
变量的定义以包含更多字段,并对所有字段使用模式
csv
,然后将模板匹配
PartType
更改为使用
csv nl

试试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="UTF-8"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="Header|Footer">
  </xsl:template>

  <xsl:template match="App">
    <xsl:apply-templates select="MOTOR_Operation/SST_Interval"/>
  </xsl:template>

  <xsl:template match="PartType">
    <xsl:apply-templates select="@id" mode="csv-nl"/>
  </xsl:template>

  <xsl:template match="SST_Interval">
    <xsl:variable name="tmp">
      <xsl:apply-templates select="ancestor::App/@id" mode="csv"/>
      <xsl:apply-templates select="ancestor::App/BaseVehicle/@id" mode="csv"/>
      <xsl:apply-templates select="@id" mode="csv"/>
      <xsl:apply-templates select="SST_IntervalMonth" mode="csv"/>
    </xsl:variable>
    <xsl:for-each select="ancestor::App/MOTOR_Operation/ReplacementPart/PartType">
      <xsl:copy-of select="$tmp"/>
      <xsl:apply-templates select="." />
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="text()|@*" mode="csv">
    <xsl:value-of select="concat(., '|')" />
  </xsl:template>

  <xsl:template match="text()|@*" mode="csv-nl">
    <xsl:value-of select="concat(., '&#xa;')" />
  </xsl:template>
</xsl:stylesheet>

您在问题中介绍的XSLT完全按照它应该的方式工作。主要问题在于,您在错误的位置使用了模式
csv nl
。此外,没有提供基本的vehicle ID和interval month字段,但是添加这些字段看起来非常简单


更新: 关于可选字段,尽管可以使用XSL的条件构造,但更自然的方法是只允许相关字段的转换不产生任何结果。这里唯一的诀窍是,对于您的应用程序,您必须始终输出分隔符,但这是由于您插入分隔符的特殊方法造成的

在该框架内工作的一个相当干净的方法是无条件地调用一个命名模板,该模板转换可选元素并添加分隔符。我已经更新了下面的样式表,以演示基本车辆id的这一点


不过,请允许我提出一些简化建议:

  • 当输出方法为text时,不需要
    省略xml声明
  • 您不需要从输入元素中剥离空间,因为您实际上没有使用任何文本节点;只有属性节点参与输出。还请注意,空白剥离仅适用于仅限空白的文本节点
  • 当输入中没有任何元素时,或者当样式表和输入的结构导致这些节点从一开始就没有被转换时,您不需要抑制
    元素
  • PartType
    提供模板有点冗长,因为在选择它进行转换时,您只需要它的
    id
    属性
  • 利用模板参数可以使代码更干净
  • 在不需要xsl:for each
的地方,最好避免使用xsl:for each。通常,您只需将模板
xsl:apply-templates
应用于相同的节点即可
  • 作为个人风格的选择,我倾向于避免使用
    concat()
    函数,在该函数中,我可以简单地提供一系列
    xsl:value of
    和/或
    xsl:text
    元素
  • 考虑到所有这些因素,我建议您在样式表上使用以下变体:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="text" encoding="UTF-8"/>
    
      <xsl:template match="App">
        <xsl:apply-templates select="MOTOR_Operation/SST_Interval"/>
      </xsl:template>
    
      <xsl:template match="SST_Interval">
        <xsl:variable name="tmp">
          <xsl:apply-templates select="ancestor::App/@id" mode="csv"/>
          <xsl:call-template name="optional-vehicle"/>
          <xsl:apply-templates select="@id" mode="csv"/>
          <xsl:apply-templates select="SST_IntervalMonth" mode="csv"/>
        </xsl:variable>
        <xsl:apply-templates select="../ReplacementPart/PartType/@id" mode="csv-nl">
          <xsl:with-param name="prefix" select="$tmp"/>
        </xsl:apply-templates>
      </xsl:template>
    
      <xsl:template name="optional-vehicle">
        <xsl:value-of select="ancestor::App/BaseVehicle/@id"/>
        <xsl:text>|</xsl:text>
      </xsl:template>
    
      <xsl:template match="node()|@*" mode="csv">
        <xsl:value-of select="." />
        <xsl:text>|</xsl:text>
      </xsl:template>
    
      <xsl:template match="node()|@*" mode="csv-nl">
        <xsl:param name="prefix"/>
        <xsl:value-of select="$prefix"/>
        <xsl:value-of select="." />
        <xsl:text>&#xa;</xsl:text>
      </xsl:template>
    
    </xsl:stylesheet>
    
    
    |
    |
    
    ;
    
    您在问题中介绍的XSLT完全按照它应该的方式工作。主要问题在于,您在错误的位置使用了模式
    csv nl
    。此外,没有提供基本的vehicle ID和interval month字段,但是添加这些字段看起来非常简单


    更新: 关于可选字段,尽管可以使用XSL的条件构造,但更自然的方法是只允许相关字段的转换不产生任何结果。这里唯一的诀窍是,对于您的应用程序,您必须始终输出分隔符,但这是由于您插入分隔符的特殊方法造成的

    在该框架内工作的一个相当干净的方法是无条件地调用一个命名模板,该模板转换可选元素并添加分隔符。我已经更新了下面的样式表,以演示基本车辆id的这一点


    不过,请允许我提出一些简化建议:

    • 当输出方法为text时,不需要
      省略xml声明
    • 您不需要从输入元素中剥离空间,因为您实际上没有使用任何文本节点;只有属性节点参与输出。还请注意,空白剥离仅适用于仅限空白的文本节点
    • 当输入中没有任何元素时,或者当
      <?xml version="1.0" encoding="UTF-8" ?>
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text" encoding="UTF-8"/>
      
        <xsl:template match="App">
          <xsl:apply-templates select="MOTOR_Operation/SST_Interval"/>
        </xsl:template>
      
        <xsl:template match="SST_Interval">
          <xsl:variable name="tmp">
            <xsl:apply-templates select="ancestor::App/@id" mode="csv"/>
            <xsl:call-template name="optional-vehicle"/>
            <xsl:apply-templates select="@id" mode="csv"/>
            <xsl:apply-templates select="SST_IntervalMonth" mode="csv"/>
          </xsl:variable>
          <xsl:apply-templates select="../ReplacementPart/PartType/@id" mode="csv-nl">
            <xsl:with-param name="prefix" select="$tmp"/>
          </xsl:apply-templates>
        </xsl:template>
      
        <xsl:template name="optional-vehicle">
          <xsl:value-of select="ancestor::App/BaseVehicle/@id"/>
          <xsl:text>|</xsl:text>
        </xsl:template>
      
        <xsl:template match="node()|@*" mode="csv">
          <xsl:value-of select="." />
          <xsl:text>|</xsl:text>
        </xsl:template>
      
        <xsl:template match="node()|@*" mode="csv-nl">
          <xsl:param name="prefix"/>
          <xsl:value-of select="$prefix"/>
          <xsl:value-of select="." />
          <xsl:text>&#xa;</xsl:text>
        </xsl:template>
      
      </xsl:stylesheet>
      
      <xsl:stylesheet version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="text" encoding="UTF-8" />
      
      <xsl:template match="App">
          <xsl:variable name="common1">
              <xsl:value-of select="@id"/>
              <xsl:text>|</xsl:text>
              <xsl:value-of select="BaseVehicle/@id"/>
              <xsl:text>|</xsl:text>
          </xsl:variable>
          <xsl:for-each select="MOTOR_Operation/SST_Interval">
              <xsl:variable name="common2">
                  <xsl:value-of select="$common1"/>
                  <xsl:value-of select="@id"/>
                  <xsl:text>|</xsl:text>
                  <xsl:value-of select="SST_IntervalMonth"/>
                  <xsl:text>|</xsl:text>
              </xsl:variable>
              <xsl:variable name="part-types" select="../ReplacementPart/PartType" />
              <xsl:for-each select="$part-types">
                  <xsl:value-of select="$common2"/>
                  <xsl:value-of select="@id"/>
                  <xsl:text>&#10;</xsl:text>
              </xsl:for-each>
              <xsl:if test="not($part-types)">
                  <xsl:value-of select="$common2"/>
                  <xsl:text>&#10;</xsl:text>
              </xsl:if>
          </xsl:for-each>
      </xsl:template>
      
      </xsl:stylesheet>