【问题标题】:XSLT multiple repeatable child nodesXSLT 多个可重复的子节点
【发布时间】:2017-10-25 09:34:44
【问题描述】:

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

我了解如何使用for-each 创建多个输出行,但我遇到了麻烦,因为有两种不同的情况需要循环。

在以下示例中,App 是基本记录,SST_Interval 是可重复的(1 个或多个),ReplacementPart 可以有 0 个或多个 PartTypes。

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

app_id|base_vehicle_id|sst_interval_id|sst_interval_month|part_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>

这是我尝试过的 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>

【问题讨论】:

  • 如果有0个PartTypes,预期的结果是什么?
  • 最后一个字段,part_type_id 会像这样为空:915152|18287|646|12||
  • 但是应该有多少行?每个现有的PartType 都有一行,所以如果不存在,逻辑上应该有 0 行。
  • 你说得很好。完全规范化,应该有两个单独的表,一个用于服务,一个用于服务部分映射。如果是这种情况,如果没有部件,则服务部件表将没有条目。作为第一遍,我将 XML 转换为更加非规范化的形式,所以我想在一行中说这个服务存在并且它没有部分

标签: xml csv xslt xslt-1.0


【解决方案1】:

我不认为你离得很远。您只需更改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>

编辑:针对您的评论,如果您想处理丢失的节点,我可能会忽略带有模式的模板,而是将分隔符和换行符存储在变量中。那么你可以这样做......

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

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

【讨论】:

  • 他,蒂姆,你是完全正确的......但令人尴尬的是,我意识到这不是我遇到的实际问题。有没有办法在我的 csv 模板中添加选择/何时来解决缺失节点的问题?否则,我将获得比其他行具有更少值的 CSV 行
  • 如果有帮助,我已经在我的回答中展示了一个替代解决方案。谢谢!
  • 非常感谢,蒂姆。我想有时更简单,不太优雅的解决方案是最好的解决方案。我认为 PartType 上的 for-each 是有问题的,因为当记录中没有 PartTypes(它可以有 0 个或更多)时,不会打印任何内容。我会尝试摆脱for-each
  • 我对我的问题进行了第二次编辑,以说明如何处理
  • 完美!我欠你一杯啤酒
【解决方案2】:

您在问题中提出的 XSLT 完全可以正常工作。主要问题只是你在错误的地方使用了模式csv-nl。此外,没有提供基本车辆 ID 和间隔月份字段,但添加这些字段看起来非常简单。


更新: 对于可选字段,虽然您可以使用 XSL 的条件构造,但更自然的方法是简单地允许相关字段的转换不产生任何结果。这里唯一的技巧是,对于您的应用程序,您必须始终输出一个分隔符,但这取决于您插入分隔符的特定方法。

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


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

  • 输出方式为文本时不需要omit-xml-declarations
  • 您不需要在输入元素中使用strip-space,因为您实际上并没有使用任何文本节点;只有属性节点对输出有贡献。另请注意,空格剥离仅适用于 whitespace-only 文本节点
  • 当输入中没有任何元素时,或者当样式表和输入的结构导致此类节点从一开始就不会被转换时,您不需要抑制 &lt;Header&gt;&lt;Footer&gt; 元素。
  • 当您知道在选择它进行转换时只需要其id 属性时,您在为PartType 提供模板时有点冗长
  • 利用模板参数可以让您的代码更简洁
  • 宁愿避免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>

【讨论】:

  • 这是一个很好的解决方案!如果BaseVehicle 是可选的,有没有办法编辑csv/csv-nl 模板,以便在BaseVehicle 不存在于App 记录中时输出分隔符?
  • 好的,@emrosenf,我已经更新了解决可选字段的答案。
【解决方案3】:

我会建议一种不同的方法:

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>

【讨论】:

  • for-each的问题是ReplacementPart/PartType是可选的,所以count为0时失败
  • 好吧,正如我在对您问题的评论中所说,这对我来说意义不大。但我确实为每个 SST_Interval 添加了一行,最后有一个空单元格,对于 0 PartTypes 的情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-03
  • 2017-06-15
  • 1970-01-01
  • 2021-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多