问题
How to make work a solution described here when I change the XML structure to have multiple good entities like that:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Work>
<Good id = "1">
<list num="1050" id = "2531" desc="List 1">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1" />
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2" />
<pos num="1.2.6." isKey="1" id="2591" pid="2554" desc="Position 1.2.6" />
</part>
</list>
<list num="1090" id = "3029" desc="List 2">
<part num="2">
<pos isKey="0" id="3033" pid="3029" desc="Category 2" />
<pos isKey="0" id="3040" pid="3033" desc="Part 9" />
<pos num="9.2." isKey="0" id="3333" pid="3040" desc="Position 9.2" />
<pos num="9.2.1." isKey="0" id="3334" pid="3333" desc="Position 9.2.1" />
<pos num="9.2.1.2" isKey="1" id="3339" pid="3334" desc="Position 9.2.1.2" />
</part>
</list>
</Good>
<Good id = "2">
<list num="1050" id = "2531" desc="List 3">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1" />
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2" />
<pos num="1.2.6." isKey="0" id="2591" pid="2554" desc="Position 1.2.6" />
<pos num="1.2.6.1." isKey="1" id="2592" pid="2591" desc="Position 1.2.6.1" />
</part>
</list>
</Good>
</Work>
I tried to make a for-each loop for Work/Good entity, but it doesn't help:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<!-- key to look up any element with an id attribute based on the value of
that id -->
<xsl:key name="elementsByPid" match="*[@pid]" use="@pid" />
<xsl:template match="/">
<html>
<body>
<h2>Lists</h2>
<xsl:apply-templates select="Work/Goods" />
</body>
</html>
</xsl:template>
<xsl:template match="Work/Goods">
<xsl:for-each select="Work/Goods">
<xsl:value-of select="."/>
</xsl:for-each>
<xsl:apply-templates select="Work/Goods/list" />
</xsl:template>
<xsl:template match="Work/Goods/list">
<xsl:for-each select="Work/Goods/list">
<xsl:value-of select="."/>
</xsl:for-each>
<xsl:apply-templates select="." mode="table"/>
</xsl:template>
<xsl:template match="*" mode="table">
<xsl:variable name="shouldOutput">
<xsl:apply-templates select="." mode="shouldOutput" />
</xsl:variable>
<xsl:if test="string-length($shouldOutput)">
<table>
<xsl:apply-templates select="." />
</table>
</xsl:if>
</xsl:template>
<!-- the main recursive logic - first produce output for this row, then
process any of the children (in the id->pid chain) that need to be
output -->
<xsl:template match="*">
<xsl:apply-templates select="." mode="row" />
<xsl:for-each select="key('elementsByPid', @id)">
<xsl:variable name="shouldOutput">
<xsl:apply-templates select="." mode="shouldOutput" />
</xsl:variable>
<xsl:if test="string-length($shouldOutput)">
<xsl:apply-templates select="." />
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="*" mode="row">
<tr>
<td colspan="2"><xsl:value-of select="@desc" /></td>
</tr>
</xsl:template>
<!-- special case for pos elements with a @num - produce two columns -->
<xsl:template match="pos[@num]" mode="row">
<tr>
<td><xsl:value-of select="@num" /></td>
<td><xsl:value-of select="@desc" /></td>
</tr>
</xsl:template>
<!-- check whether this node should be output by checking whether it, or any
of its descendants in the id->pid tree, has @out=1. The template will
return an empty RTF for nodes that should not be output, and an RTF
containing a text node with one or more "1" characters for nodes that
should. -->
<xsl:template match="*[@out='1']" mode="shouldOutput">1</xsl:template>
<xsl:template match="*" mode="shouldOutput">
<xsl:apply-templates select="key('elementsByPid', @id)"
mode="shouldOutput"/>
</xsl:template>
</xsl:stylesheet>
enter code here
There in the templates that doesn't allow this code to work.
What else should I change to make it work?
回答1:
You don't need to use any for-each
for this, just remove the two templates
<xsl:template match="Work/Goods">
<xsl:template match="Work/Goods/list">
completely, and change the root template to say simply
<xsl:template match="/">
<html>
<body>
<h2>Lists</h2>
<xsl:apply-templates select="Work/Good/list" mode="table" />
</body>
</html>
</xsl:template>
There's no need for explicit iteration, as the single select
expression will pull out all the list
elements inside all the Good
elements (note your XML example has the elements named Good
rather than Goods
) inside the Work
element.
回答2:
I am not sure, whether this is what you mean, but I hope the following is useful to you. My suggestion is to process the document two times. Once with step1.xsl
and the result of it with step2.xsl
. Explanation on what the transformations do is given at the very end of my answer.
Given an input file the form of the one you provided above, but with the attribute out="1"
added to one of the <part />
elements
<?xml version="1.0" encoding="ISO-8859-1"?>
<Work>
<Good id = "1">
<list num="1050" id = "2531" desc="List 1">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1" />
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2" />
<pos num="1.2.6." isKey="1" id="2591" pid="2554" desc="Position 1.2.6" />
</part>
</list>
<list num="1090" id = "3029" desc="List 2">
<part num="2">
<pos isKey="0" id="3033" pid="3029" desc="Category 2" />
<pos isKey="0" id="3040" pid="3033" desc="Part 9" />
<pos num="9.2." isKey="0" id="3333" pid="3040" desc="Position 9.2" />
<pos num="9.2.1." isKey="0" id="3334" pid="3333" desc="Position 9.2.1" out="1" />
<pos num="9.2.1.2" isKey="1" id="3339" pid="3334" desc="Position 9.2.1.2" />
</part>
</list>
</Good>
<Good id = "2">
<list num="1050" id = "2531" desc="List 3">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1" />
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2" />
<pos num="1.2.6." isKey="0" id="2591" pid="2554" desc="Position 1.2.6" />
<pos num="1.2.6.1." isKey="1" id="2592" pid="2591" desc="Position 1.2.6.1" />
</part>
</list>
</Good>
</Work>
the following transformation step1.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="id" match="pos" use="@id" />
<xsl:key name="pid" match="pos" use="@pid" />
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="pos[count(key('id', @pid)) = 0]">
<xsl:variable name="id" select="@id" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="ancestor::Good//pos[@pid=$id]" mode="nested" />
</xsl:copy>
</xsl:template>
<xsl:template match="pos" />
<xsl:template match="pos" mode="nested">
<xsl:variable name="id" select="@id" />
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="ancestor::Good//pos[@pid=$id]" mode="nested" />
</xsl:copy>
</xsl:template>
</xsl:transform>
produces the following output
<?xml version="1.0"?>
<Work>
<Good id="1">
<list num="1050" id="2531" desc="List 1">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1">
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2">
<pos num="1.2.6." isKey="1" id="2591" pid="2554" desc="Position 1.2.6"/>
</pos>
</pos>
</part>
</list>
<list num="1090" id="3029" desc="List 2">
<part num="2">
<pos isKey="0" id="3033" pid="3029" desc="Category 2">
<pos isKey="0" id="3040" pid="3033" desc="Part 9">
<pos num="9.2." isKey="0" id="3333" pid="3040" desc="Position 9.2">
<pos num="9.2.1." isKey="0" id="3334" pid="3333" desc="Position 9.2.1" out="1">
<pos num="9.2.1.2" isKey="1" id="3339" pid="3334" desc="Position 9.2.1.2"/>
</pos>
</pos>
</pos>
</pos>
</part>
</list>
</Good>
<Good id="2">
<list num="1050" id="2531" desc="List 3">
<part num="1">
<pos isKey="0" id="2532" pid="2531" desc="Part 1">
<pos num="1.2." isKey="0" id="2554" pid="2532" desc="Position 1.2">
<pos num="1.2.6." isKey="0" id="2591" pid="2554" desc="Position 1.2.6">
<pos num="1.2.6.1." isKey="1" id="2592" pid="2591" desc="Position 1.2.6.1"/>
</pos>
</pos>
</pos>
</part>
</list>
</Good>
</Work>
that taken as an input for the following transformation step2.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:apply-templates match="pos[@out=1]" />
</xsl:template>
<xsl:template match="pos[@out=1]">
<table>
<tr><td><xsl:value-of select="ancestor::list/@desc" /></td></tr>
<xsl:for-each select="ancestor-or-self::pos[count(ancestor::pos) < 2]">
<xsl:sort select="position()" order="descending" />
<tr><td><xsl:value-of select="@desc" /></td></tr>
</xsl:for-each>
<xsl:for-each select="ancestor-or-self::pos[count(ancestor::pos) >= 2]">
<tr>
<td><xsl:value-of select="@num" /></td>
<td><xsl:value-of select="@desc" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:transform>
produces an output that looks like what I think you want
<?xml version="1.0"?>
<table>
<tr>
<td>List 2</td>
</tr>
<tr>
<td>Part 9</td>
</tr>
<tr>
<td>Category 2</td>
</tr>
<tr>
<td>9.2.</td>
<td>Position 9.2</td>
</tr>
<tr>
<td>9.2.1.</td>
<td>Position 9.2.1</td>
</tr>
</table>
Please clarify if it does not.
Explanation: The first transformation step1.xsl
builds nested hierarchical structures from the flat lists of pos
elements based on their @pid
attributes. These make it easier to go through the chain of parents in the second pass.
The second transformation step2.xsl
then matches on every pos
element that should produce output, indicated by its @out
attribute and writes out a table of the structure you drafted as ASCII art in your original question.
What is not yet done is merging of identical tables in the case that e.g. two pos[@out=1]
elements are contained in the same list
.
回答3:
The problem is that you the @pid
is not enough to correctly identify <pos>
elements, since the same @pid
can occur in multiple goods.
This means that you need to correlate each @pid
with the <Good>
it belongs to in your <xsl:key>
(and then, in each use of the key). This can be done by building a unique string from @pid
and the enclosing Good/@id
.
concat(@pid, '|', ancestor::Good/@id)
What follows is basically is the XSLT from the previous question. I've highlighted the changes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:key name="elementsByPid" match="*[@pid]" use="concat(@pid, '|', ancestor::Good/@id)" />
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ -->
<xsl:template match="/">
<html>
<body>
<h2>Lists</h2>
<xsl:apply-templates select="/Work/Good/list" mode="table" />
</body>
</html>
</xsl:template>
<xsl:template match="*" mode="table">
<xsl:variable name="shouldOutput">
<xsl:apply-templates select="." mode="shouldOutput" />
</xsl:variable>
<xsl:if test="string-length($shouldOutput)">
<table>
<xsl:apply-templates select="." />
</table>
</xsl:if>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates select="." mode="row" />
<xsl:for-each select="key('elementsByPid', concat(@id, '|', ancestor::Good/@id))">
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ -->
<xsl:variable name="shouldOutput">
<xsl:apply-templates select="." mode="shouldOutput" />
</xsl:variable>
<xsl:if test="string-length($shouldOutput)">
<xsl:apply-templates select="." />
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="*" mode="row">
<tr>
<td colspan="2"><xsl:value-of select="@description" /></td>
</tr>
</xsl:template>
<xsl:template match="pos[@num]" mode="row">
<tr>
<td class="num"><xsl:value-of select="@num" /></td>
<td><xsl:value-of select="@description" /></td>
</tr>
</xsl:template>
<xsl:template match="*[@out='1']" mode="shouldOutput">1</xsl:template>
<xsl:template match="*" mode="shouldOutput">
<xsl:apply-templates select="key('elementsByPid', concat(@id, '|', ancestor::Good/@id))" mode="shouldOutput"/>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ -->
</xsl:template>
</xsl:stylesheet>
Also see http://www.xmlplayground.com/9iG5oz.
PS: Since you seem to have problems with understanding how <xsl:apply-templates>
works, maybe you find an older answer of mine explaining it helpful. I've also written an answer that explains <xsl:key>.
来源:https://stackoverflow.com/questions/19382073/xsl-structure-for-category-tree-for-multiple-goods