【问题标题】:XSL structure for category tree for multiple goods多商品类别树的 XSL 结构
【发布时间】:2013-10-15 13:07:32
【问题描述】:

当我将 XML 结构更改为具有多个这样的良好实体时,如何使工作成为here 所述的解决方案:

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

我尝试为 Work/Good 实体创建一个 for-each 循环,但没有帮助:

<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

模板中不允许此代码工作。
我还应该进行哪些更改才能使其正常工作?

【问题讨论】:

  • 我想到了两件事 - a) 请简化您的代码。这是一堵可怕的文字墙,不是一个简洁的好问题。将其简化为实际问题,不要发布您拥有的所有代码。 b) 请格式化您的代码。使用适当的缩进,删除空白行,避免需要水平滚动的行。你的代码看起来你不太关心它 - 那为什么其他人应该呢?
  • @Tomalak 不客气!=)
  • 非常好,现在看起来更友好了! :)

标签: xml xslt xpath xslt-2.0


【解决方案1】:

你不需要为此使用任何for-each,只需删除两个模板

<xsl:template match="Work/Goods">

<xsl:template match="Work/Goods/list">

完全,把根模板改成简单的说

  <xsl:template match="/">
    <html>
      <body>
      <h2>Lists</h2>
        <xsl:apply-templates select="Work/Good/list" mode="table" />
      </body>
    </html>
  </xsl:template>

不需要显式迭代,因为单个 select 表达式将提取所有 Good 元素中的所有 list 元素(请注意,您的 XML 示例中的元素名为 Good 而不是 Goods ) 在Work 元素内。

【讨论】:

  • 我刚刚尝试了您的解决方案,对我来说它产生了&lt;html&gt;&lt;body&gt;&lt;h2&gt;Lists&lt;/h2&gt;&lt;/body&gt;&lt;/html&gt;。你能告诉我我做错了什么吗?
  • @hielsnoppe 我误读了 XML - 问题中的 XSLT 讨论了名为“Goods”的元素,但示例输入将它们称为“Good”,没有尾随 s。问题中的其余 XSLT 取自 my answer to the OP's previous question
  • 我明白了,看起来不错!我将研究您的解决方案以从中学习,并想知道您能否告诉我与我的方法相比有哪些优势,例如在性能或风格(如 XSLT 中的“做事方式”)有什么?感谢您分享您的经验!
【解决方案2】:

我不确定,这是否是你的意思,但我希望以下内容对你有用。我的建议是两次处理文件。一次是step1.xsl,结果是step2.xsl。在我的答案的最后给出了关于转换做什么的解释。

给定一个您在上面提供的格式的输入文件,但属性out="1" 添加到&lt;part /&gt; 元素之一

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

以下变换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>

产生以下输出

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

作为以下转换的输入 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) &lt; 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>

产生一个看起来像我认为你想要的输出

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

如果不是,请澄清。

解释:第一个转换step1.xsl 根据pos 元素的@pid 属性从平面列表构建嵌套层次结构。这使得在第二遍中通过父链更容易。

第二个转换step2.xsl 然后匹配应产生输出的每个pos 元素,由其@out 属性指示,并写出您在原始问题中作为ASCII 艺术起草的结构表。

尚未完成的是合并相同的表,例如两个pos[@out=1] 元素包含在同一个list 中。

【讨论】:

    【解决方案3】:

    问题是你@pid不足以正确识别&lt;pos&gt;元素,因为同一个@pid可能出现在多个商品中。

    这意味着您需要将每个@pid 与它在&lt;xsl:key&gt; 中所属的&lt;Good&gt; 相关联(然后,在每次使用密钥时)。这可以通过从@pid 和封闭的Good/@id 构建一个唯一的字符串来完成。

    concat(@pid, '|', ancestor::Good/@id)
    

    接下来基本上是来自previous question 的XSLT。我已经突出显示了这些更改。

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

    另见http://www.xmlplayground.com/9iG5oz


    PS:由于您似乎无法理解 &lt;xsl:apply-templates&gt; 的工作原理,因此您可能会发现 an older answer of mine explaining it 有帮助。我也写了an answer that explains &lt;xsl:key&gt;

    【讨论】:

      猜你喜欢
      • 2012-05-08
      • 1970-01-01
      • 1970-01-01
      • 2016-09-09
      • 2014-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-20
      相关资源
      最近更新 更多