我正在使用一组记录,这些记录具有多个可重复的不同子节点类型。最终目标是创建 CSV 映射,并且由于嵌套,每个 XML 记录都可以映射到许多 CSV 行项。
我了解如何使用for-each
创建多个输出行,但我遇到了麻烦,因为有两种不同的情况需要循环。
在下面的示例中,App
是基本记录,SST_Interval
是可重复的(1 个或更多),ReplacementPart
可以有 0 个或多个PartType
s。
我想提取具有以下格式的 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(., '
')" />
</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(., '
')" />
</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="'
'" />
<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>
您在问题中介绍的 XSLT 完全可以正常工作。 主要问题只是您在错误的位置使用模式csv-nl
。 此外,没有任何东西可以提供基本车辆 ID 和间隔月份字段,但添加它们看起来非常简单。
更新:对于可选字段,尽管您可以使用XSL 的条件构造,但更自然的方法是简单地允许相关字段的转换不产生任何结果。 这里唯一的技巧是,对于您的应用程序,您必须始终输出一个分隔符,但这源于您插入分隔符的特定方法。
在该框架内工作的一种相当干净的方式是无条件调用一个命名模板,该模板转换可选元素并添加分隔符。 我已经更新了下面的样式表,以演示基本车辆 ID 的这一点。
但是,请允许我提出一些简化建议:
- 当输出方法为文本时,您无需
omit-xml-declarations
- 您不需要从输入元素
strip-space
,因为您实际上并没有使用任何文本节点;只有属性节点对输出有贡献。 另请注意,空格剥离仅适用于仅空格文本节点 - 当输入中没有任何元素时,或者当样式表和输入的结构导致此类节点从未首先被转换时,您不需要抑制
<Header>
和<Footer>
元素。
当您 - 在选择模板进行转换时知道您只需要其
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>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
我会建议一种不同的方法:
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> </xsl:text>
</xsl:for-each>
<xsl:if test="not($part-types)">
<xsl:value-of select="$common2"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>