处理 foreach 中的排除元素

Posted

技术标签:

【中文标题】处理 foreach 中的排除元素【英文标题】:Handle excluded element in a foreach 【发布时间】:2021-08-19 08:15:00 【问题描述】:

在第一个模板中,我有意排除了一个元素(“牛奶”),因为解析的数据映射相对平坦,我想使用 XSLT 对数据进行分类和结构化。目的是处理第二个模板中的排除元素(“牛奶”)。这两个模板一次运行一个。一起运行模板不会显示排除元素('milk')的结果,它应该设置另一个属性名称和属性值。

JSON:

<data>

  "storage": 
    "pencils": 12,
    "milk": 8,
    "rulers": 4
  

</data>

XSL:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:transform
  version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:storage="http://www.exammple.com/1"
  xmlns:office="http://www.exammple.com/2"
  xmlns:item="http://www.exammple.com/3"
  expand-text="yes">

  <xsl:output method="xml" indent="yes"/>

  <xsl:mode on-no-match="shallow-skip"/>

  <!-- Parse JSON to XML -->

  <xsl:template match="data">
    <storage:one>
      <xsl:apply-templates select="json-to-xml(.)"/>
    </storage:one>
  </xsl:template>

  <!-- Print map -->
  <!-- <xsl:template match="*[@key = 'storage']"> <xsl:copy-of select=".."/> </xsl:template> -->

  <xsl:template match="*[@key='storage']">

      <xsl:for-each select="*[not(@key='milk')]">
      <xsl:element name="item:@key">
        <xsl:attribute name="office">plant-1</xsl:attribute>
        <xsl:value-of select="text()"/>
      </xsl:element>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="*[@key='milk']">
      <xsl:for-each select=".">
      <xsl:element name="item:@key">
        <xsl:attribute name="beverage">plant-2</xsl:attribute>
        <xsl:value-of select="text()"/>
      </xsl:element>
    </xsl:for-each>
  </xsl:template>

</xsl:transform>

结果:

<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
             xmlns:office="http://www.exammple.com/2"
             xmlns:storage="http://www.exammple.com/1">
   <item:pencils office="plant-1">12</item:pencils>
   <item:rulers office="plant-1">4</item:rulers>
</storage:one>

想要的结果:

<?xml version="1.0" encoding="UTF-8"?>
<storage:one xmlns:item="http://www.exammple.com/3"
             xmlns:office="http://www.exammple.com/2"
             xmlns:storage="http://www.exammple.com/1">
   <item:pencils office="plant-1">12</item:pencils>
   <item:rulers office="plant-1">4</item:rulers>
   <item:milk beverage="plant-2">8</item:milk>
</storage:one>

【问题讨论】:

在我看来,通过将模板和应用模板与嵌套的 for-each 混合使用,您似乎让您的生活变得困难。为什么不尝试编写模板并仅依赖应用模板。而且如前所述,做for-each select="." 毫无意义。 【参考方案1】:

您的第二个模板永远不会匹配,因为它永远不会到达。所有元素都由 &lt;xsl:template match="*[@key='storage']"&gt; 处理 - 它没有 &lt;xsl:apply-templates ...&gt; 来访问更多模板。

您的第一个模板不会递归到其子级。所以在第一个模板的末尾添加一个&lt;xsl:apply-templates select="*" /&gt;

    <xsl:template match="*[@key='storage']">
      <xsl:for-each select="*[not(@key='milk')]">
        <xsl:element name="item:@key">
          <xsl:attribute name="office">plant-1</xsl:attribute>
          <xsl:value-of select="text()"/>
        </xsl:element>
      </xsl:for-each>
      <xsl:apply-templates select="*" />
    </xsl:template>

这将尝试在“存储”级别应用更多模板,因此匹配第二个模板。

【讨论】:

【参考方案2】:

我会为每种不同的输出类型编写模板,如果输出的顺序与输入的顺序不同,则在 xsl:sort 或 XPath 3.1 sort 调用中更改顺序:

  <xsl:template match="data">
    <storage:one>
      <xsl:apply-templates select="json-to-xml(.)"/>
    </storage:one>
  </xsl:template>
  
  <xsl:template match="*[@key = 'storage']">
    <xsl:apply-templates select="sort(*, (), function($el)  $el/@key = 'milk' )"/>
  </xsl:template>

  <xsl:template match="*[@key='storage']/*[not(@key='milk')]">
      <xsl:element name="item:@key">
        <xsl:attribute name="office">plant-1</xsl:attribute>
        <xsl:value-of select="text()"/>
      </xsl:element>
  </xsl:template>

  <xsl:template match="*[@key='storage']/*[@key='milk']">
      <xsl:element name="item:@key">
        <xsl:attribute name="beverage">plant-2</xsl:attribute>
        <xsl:value-of select="text()"/>
      </xsl:element>
  </xsl:template>

【讨论】:

以上是关于处理 foreach 中的排除元素的主要内容,如果未能解决你的问题,请参考以下文章

在foreach中排除空值

如何形象地解释 JavaScript 中 map,foreach,reduce 间的区别

php学习笔记-foreach循环

for 与forEach的区别

C#中的foreach循环怎么用?

2021年大数据常用语言Scala(二十一):函数式编程 遍历 foreach