简体   繁体   English

XSLT多个可重复的子节点

[英]XSLT multiple repeatable child nodes

I am working with a set of records that have multiple different child node types that are repeatable. 我正在处理具有多个可重复的不同子节点类型的记录集。 The end goal is to create a CSV mapping, and because of the nesting, each XML record can map to many CSV line items. 最终目标是创建CSV映射,由于嵌套,每个XML记录可以映射到许多CSV订单项。

I understand how to use for-each to create multiple output lines, but I am having trouble because there are two different cases to loop over. 我知道如何使用for-each来创建多条输出线,但是我遇到了麻烦,因为有两种不同的情况需要循环。

In the following example, App is the base record, SST_Interval is repeatable (1 or more), and ReplacementPart can have 0 or more PartType s. 在以下示例中, App是基本记录, SST_Interval是可重复的(1个或更多),而ReplacementPart可以具有0个或多个PartType

I would like to extract a CSV that has the following format 我想提取具有以下格式的CSV

app_id|base_vehicle_id|sst_interval_id|sst_interval_month|part_type_id

For the record shown below, the result would look like this 对于下面显示的记录,结果将如下所示

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

Here is the record 这是记录

<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>

Here is the XSLT I have tried. 这是我尝试过的XSLT。 If you can point me in the right direction I would be very grateful. 如果您能指出正确的方向,我将不胜感激。

<?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>

I don't think you are far off. 我认为你离我们并不遥远。 You just need to change the definition of the tmp variable to include more fields, and use mode csv for all of them, and then change the template matching PartType to use csv-nl instead. 您只需要更改tmp变量的定义以包含更多字段,并对所有字段使用模式csv ,然后将匹配PartType的模板PartType为使用csv-nl

Try this XSLT instead 试试这个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>

EDIT: In response to your comment, if you wanted to cope with missing nodes, I would perhaps disregard the templates with mode, and store the separator and new line characters in variables instead. 编辑:作为对您的评论的回应,如果您想应对缺少的节点,我可能会不理会带有模式的模板,而是将分隔符和换行符存储在变量中。 Then you could do this... 那你可以做...

<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:variable name="sep" select="'|'" />
  <xsl:variable name="nl" select="'&#xa;'" />

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

  <xsl:template match="PartType">
    <xsl:value-of select="@id" />
    <xsl:value-of select="$nl" />
  </xsl:template>

  <xsl:template match="SST_Interval">
    <xsl:variable name="tmp">
      <xsl:value-of select="ancestor::App/@id" />
      <xsl:value-of select="$sep" />
      <xsl:value-of select="ancestor::App/BaseVehicle/@id" />
      <xsl:value-of select="$sep" />
      <xsl:value-of select="@id"/>
      <xsl:value-of select="$sep" />
      <xsl:value-of select="SST_IntervalMonth"/>
      <xsl:value-of select="$sep" />
    </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:stylesheet>

EDIT 2: In the case of there being no PartType records, try replacing the current xsl:for-each with this code 编辑2:在没有PartType记录的情况下,请尝试使用此代码替换当前的xsl:for-each

    <xsl:variable name="PartTypes" select="ancestor::App/MOTOR_Operation/ReplacementPart/PartType" />
    <xsl:for-each select="$PartTypes">
      <xsl:copy-of select="$tmp"/>
      <xsl:apply-templates select="." />
    </xsl:for-each>
    <xsl:if test="not($PartTypes)">
      <xsl:copy-of select="$tmp"/>
      <xsl:value-of select="$nl" />
    </xsl:if>

The XSLT you presented in the question works exactly as it should. 您在问题中提出的XSLT完全可以正常工作。 The main problem is simply that you use mode csv-nl in the wrong place. 主要问题很简单,就是您在错误的地方使用了csv-nl模式。 Additionally, there's nothing to provide the base vehicle ID and interval month fields, but it looks pretty straightforward to add those. 此外,没有任何内容可提供基本车辆ID和间隔月份字段,但添加这些字段看起来非常简单。


Update: With respect to optional fields, although you could use XSL's conditional constructs, a more natural approach would be to simply allow the transformation of the relevant field to produce nothing. 更新:关于可选字段,尽管您可以使用XSL的条件构造,一种更自然的方法是简单地允许相关字段的转换不产生任何结果。 The only trick here is that for your application you must always output a delimiter, but that arises from your particular approach to inserting delimiters. 唯一的窍门是,对于您的应用程序,您必须始终输出定界符,但这是由您插入定界符的特定方法引起的。

One fairly clean way to work within that framework would be to unconditionally call a named template that transforms the optional element and adds the delimiter. 在该框架内工作的一种相当干净的方法是无条件调用命名模板,该模板将转换可选元素并添加定界符。 I have updated the stylesheet below to demonstrate this for the base vehicle id. 我已经更新了以下样式表,以针对基本车辆ID进行演示。


Allow me, however, to suggest some simplifications: 但是,请允许我提出一些简化的建议:

  • You don't need to omit-xml-declarations when the output method is text 当输出方法是text时,您不需要omit-xml-declarations
  • you don't need to strip-space from the input elements because you're not actually using any text nodes; 您不需要从输入元素中strip-space ,因为您实际上并没有使用任何文本节点; only attribute nodes are contributing to the output. 只有属性节点才有助于输出。 Note also that whitespace stripping applies exclusively to whitespace-only text nodes 另请注意,空白剥离仅适用于仅空白文本节点
  • You don't need to suppress <Header> and <Footer> elements when there aren't any in the input, OR when the structure of the stylesheet and input result in such nodes never being transformed in the first place. 当输入中没有任何元素时,或者当样式表和输入的结构导致此类节点从不进行转换时,无需取消<Header><Footer>元素。
  • You're a bit verbose in providing a template for PartType when you know at the point where you select it for transformation that you want only its id attribute 当您知道在选择要转换的点时只需要其id属性时,在为PartType提供模板时就有点冗长
  • Leveraging template parameters could make you code cleaner 利用模板参数可以使代码更简洁
  • Prefer to avoid xsl:for-each where you don't need it. 最好避免在不需要的地方避免使用xsl:for-each Often, you can simply xsl:apply-templates to the same nodes, instead 通常,您可以简单地将xsl:apply-templates应用于相同的节点,而不是
  • As a personal style choice, I prefer to avoid the concat() function where I can instead simply provide a sequence of xsl:value-of and / or xsl:text elements 作为个人风格的选择,我宁愿避免使用concat()函数,而可以直接提供xsl:value-of和/或xsl:text元素的序列

Taking all those into account, I'd suggest this variation on your stylesheet: 考虑到所有这些因素,我建议您在样式表中使用以下变体:

<?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>

I would suggest a different approach: 我建议一种不同的方法:

XSLT 1.0 XSLT 1.0

<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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM