[英]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(., '
')" />
</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(., '
')" />
</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="'
'" />
<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: 但是,请允许我提出一些简化的建议:
omit-xml-declarations
when the output method is text omit-xml-declarations
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. <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>
元素。 PartType
when you know at the point where you select it for transformation that you want only its id
attribute id
属性时,在为PartType
提供模板时就有点冗长 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
应用于相同的节点,而不是 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>
</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> </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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.