XSLT使用for循环类型操作扩展输入XML

时间:2012-06-05 18:38:13

标签: xml xslt xslt-2.0

我是XSLT的新手,请原谅我的无知,如果我输入这样的XML:

<Doc>
 <stuff>
   <for var="i" from="1" to="2">
      <item>$(i)></item>
      <for var="j" from="2" to="4">
        <innerItem>$(j)</innerItem>
      </for>
    </for>    
  </stuff>
</Doc>

我想使用转换来扩展输出XML:

<Doc>
 <stuff>
   <item>1</item>
     <innerItem>2</innerItem>
     <innerItem>3</innerItem>
     <innerItem>4</innerItem>
   <item>2</item>
     <innerItem>2</innerItem>
     <innerItem>3</innerItem>
     <innerItem>4</innerItem>
 </stuff>
</Doc>

我所拥有的就是:接下来要做什么?

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>         
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

  <xsl:template match="for">
    <xsl:variable name="from" select="@from" />
    <xsl:variable name="to" select="@to" />
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>    
  </xsl:template>
</xsl:stylesheet>

2 个答案:

答案 0 :(得分:2)

这里更简单(没有明确的条件指令,没有用户定义的函数,没有模式)和更短的工作转换

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
  <xsl:param name="pVars" as="element()*"/>
  <xsl:copy>
   <xsl:apply-templates select="node()|@*">
     <xsl:with-param name="pVars" select="$pVars"/>
   </xsl:apply-templates>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="for">
  <xsl:param name="pVars" as="element()*"/>

  <xsl:variable name="vCurrentFor" select="."/>

  <xsl:for-each select="@from to @to">
      <xsl:variable name="vnewVars">
        <xsl:sequence select="$pVars"/>
        <var name="{$vCurrentFor/@var}" value="{current()}"/>
      </xsl:variable>

    <xsl:apply-templates select="$vCurrentFor/node()">
      <xsl:with-param name="pVars" select="$vnewVars/*"/>
    </xsl:apply-templates>
  </xsl:for-each>
 </xsl:template>

 <xsl:template match="text()[contains(., '$(')]">
  <xsl:param name="pVars" as="element()*"/>

  <xsl:analyze-string select="."
    regex="\$\((.+?)\)">
     <xsl:non-matching-substring>
       <xsl:value-of select="."/>
     </xsl:non-matching-substring>
     <xsl:matching-substring>
       <xsl:variable name="vName" select="regex-group(1)"/>

       <xsl:variable name="vReplacement" select=
        "$pVars[@name eq $vName][last()]/@value"/>
       <xsl:sequence select="string($vReplacement)"/>
     </xsl:matching-substring>
  </xsl:analyze-string>
 </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此转换时:

<Doc>
    <stuff>
        <for var="i" from="1" to="2">
            <item>$(i)</item>
            <for var="j" from="2" to="4">
                <innerItem>$(j)</innerItem>
            </for>
        </for>
    </stuff>
</Doc>

产生了想要的正确结果:

<Doc>
   <stuff>
      <item>1</item>
      <innerItem>2</innerItem>
      <innerItem>3</innerItem>
      <innerItem>4</innerItem>
      <item>2</item>
      <innerItem>2</innerItem>
      <innerItem>3</innerItem>
      <innerItem>4</innerItem>
   </stuff>
</Doc>

可以执行更复杂的处理

同时使用不同级别的变量

<Doc>
    <stuff>
        <for var="x" from="1" to="2">
            <item>$(x)</item>
            <for var="y" from="2" to="4">
                <innerItem>$(x).$(y)</innerItem>
            </for>
        </for>
    </stuff>
</Doc>

此文档的结果是:

<Doc>
   <stuff>
      <item>1</item>
      <innerItem>1.2</innerItem>
      <innerItem>1.3</innerItem>
      <innerItem>1.4</innerItem>
      <item>2</item>
      <innerItem>2.2</innerItem>
      <innerItem>2.3</innerItem>
      <innerItem>2.4</innerItem>
   </stuff>
</Doc>

或使用此XML文档

<Doc>
    <stuff>
        <for var="x" from="1" to="2">
          <item>
             <value>$(x)</value>
                <for var="y" from="2" to="4">
                    <innerItem>
                      <value>$(x).$(y)</value>
                        <for var="z" from="3" to="5">
                          <inner-most-Item>$(x).$(y).$(z)</inner-most-Item>
                        </for>
                    </innerItem>
                </for>
          </item>
        </for>
    </stuff>
</Doc>

结果是

<Doc>
   <stuff>
      <item>
         <value>1</value>
         <innerItem>
            <value>1.2</value>
            <inner-most-Item>1.2.3</inner-most-Item>
            <inner-most-Item>1.2.4</inner-most-Item>
            <inner-most-Item>1.2.5</inner-most-Item>
         </innerItem>
         <innerItem>
            <value>1.3</value>
            <inner-most-Item>1.3.3</inner-most-Item>
            <inner-most-Item>1.3.4</inner-most-Item>
            <inner-most-Item>1.3.5</inner-most-Item>
         </innerItem>
         <innerItem>
            <value>1.4</value>
            <inner-most-Item>1.4.3</inner-most-Item>
            <inner-most-Item>1.4.4</inner-most-Item>
            <inner-most-Item>1.4.5</inner-most-Item>
         </innerItem>
      </item>
      <item>
         <value>2</value>
         <innerItem>
            <value>2.2</value>
            <inner-most-Item>2.2.3</inner-most-Item>
            <inner-most-Item>2.2.4</inner-most-Item>
            <inner-most-Item>2.2.5</inner-most-Item>
         </innerItem>
         <innerItem>
            <value>2.3</value>
            <inner-most-Item>2.3.3</inner-most-Item>
            <inner-most-Item>2.3.4</inner-most-Item>
            <inner-most-Item>2.3.5</inner-most-Item>
         </innerItem>
         <innerItem>
            <value>2.4</value>
            <inner-most-Item>2.4.3</inner-most-Item>
            <inner-most-Item>2.4.4</inner-most-Item>
            <inner-most-Item>2.4.5</inner-most-Item>
         </innerItem>
      </item>
   </stuff>
</Doc>

我会在这里停下来,但考虑到这种语言的良好设计,可能性是无限的。

更新:OP已询问:

  

“有没有办法让内部属性扩展?for   例如:<inner-most-Item id="$(i)">

是的,这很简单 - 只需添加一个匹配属性和重构代码的新模板

<xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:my="my:my">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:template match="node()|@*">
      <xsl:param name="pVars" as="element()*"/>
      <xsl:copy>
       <xsl:apply-templates select="node()|@*">
         <xsl:with-param name="pVars" select="$pVars"/>
       </xsl:apply-templates>
      </xsl:copy>
     </xsl:template>

     <xsl:template match="for">
      <xsl:param name="pVars" as="element()*"/>

      <xsl:variable name="vCurrentFor" select="."/>

      <xsl:for-each select="@from to @to">
          <xsl:variable name="vnewVars">
            <xsl:sequence select="$pVars"/>
            <var name="{$vCurrentFor/@var}" value="{current()}"/>
          </xsl:variable>

        <xsl:apply-templates select="$vCurrentFor/node()">
          <xsl:with-param name="pVars" select="$vnewVars/*"/>
        </xsl:apply-templates>
      </xsl:for-each>
     </xsl:template>

     <xsl:template match="text()[contains(., '$(')]">
      <xsl:param name="pVars" as="element()*"/>

      <xsl:value-of select="my:evalText($pVars, .)"/>
     </xsl:template>

     <xsl:template match="@*[contains(., '$(')]">
      <xsl:param name="pVars" as="element()*"/>

      <xsl:attribute name="{name()}">
        <xsl:value-of select="my:evalText($pVars, .)"/>
      </xsl:attribute> 
     </xsl:template>

     <xsl:function name="my:evalText">
      <xsl:param name="pVars" as="element()*"/>
       <xsl:param name="pText"/>

          <xsl:analyze-string select="$pText"
            regex="\$\((.+?)\)">
             <xsl:non-matching-substring>
               <xsl:value-of select="."/>
             </xsl:non-matching-substring>
             <xsl:matching-substring>
               <xsl:variable name="vName" select="regex-group(1)"/>

               <xsl:variable name="vReplacement" select=
                "$pVars[@name eq $vName][last()]/@value"/>
               <xsl:value-of select="string($vReplacement)"/>
             </xsl:matching-substring>
          </xsl:analyze-string>
     </xsl:function>
</xsl:stylesheet>

现在,此转换应用于以下XML文档:

<Doc>
    <stuff>
        <for var="i" from="1" to="2">
            <item name="X$(i)">$(i)</item>
            <for var="j" from="2" to="4">
                <innerItem name="X$(i).$(j)">$(j)</innerItem>
            </for>
        </for>
    </stuff>
</Doc>

产生了想要的正确结果:

<Doc>
   <stuff>
      <item name="X1">1</item>
      <innerItem name="X1.2">2</innerItem>
      <innerItem name="X1.3">3</innerItem>
      <innerItem name="X1.4">4</innerItem>
      <item name="X2">2</item>
      <innerItem name="X2.2">2</innerItem>
      <innerItem name="X2.3">3</innerItem>
      <innerItem name="X2.4">4</innerItem>
   </stuff>
</Doc>

答案 1 :(得分:0)

这适用于提供的输入XML。我不得不使用模式。字符串替换功能基于此问题的答案:XSLT string replace

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fun="http://www.nothing.org">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <xsl:apply-templates mode="parse"/>
    </xsl:template>
    <xsl:template name="forImpl">
        <xsl:param name="counter" select="@from"/>
        <xsl:param name="to" select="@to"/>
        <xsl:param name="varName" select="@var"/>
        <xsl:param name="nodes"/>
        <xsl:apply-templates select="$nodes" mode="parse">
            <xsl:with-param name="varName" select="concat('$(', $varName, ')')"/>
            <xsl:with-param name="counter" select="$counter"/>
        </xsl:apply-templates>
        <xsl:if test="$counter != $to">
            <xsl:call-template name="forImpl">
                <xsl:with-param name="counter" select="$counter + 1"/>
                <xsl:with-param name="to" select="$to"/>
                <xsl:with-param name="varName" select="$varName"/>
                <xsl:with-param name="nodes" select="$nodes"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
    <xsl:template match="@* | node()" mode="parse">
        <xsl:param name="varName"/>
        <xsl:param name="counter"/>
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" mode="parse">
                <xsl:with-param name="varName" select="$varName"/>
                <xsl:with-param name="counter" select="$counter"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="for" mode="parse">
        <xsl:param name="varName"/>
        <xsl:param name="counter"/>
        <xsl:call-template name="forImpl">
            <xsl:with-param name="counter" select="@from"/>
            <xsl:with-param name="to" select="@to"/>
            <xsl:with-param name="varName" select="@var"/>
            <xsl:with-param name="nodes">
                <xsl:copy-of select="./*"/>
            </xsl:with-param>
        </xsl:call-template>
    </xsl:template>
    <xsl:template match="text()" mode="parse">
        <xsl:param name="varName"/>
        <xsl:param name="counter"/>
        <xsl:value-of select="if (contains(., $varName)) then fun:string-replace-all(., $varName, $counter) else ."/>
    </xsl:template>
    <xsl:function name="fun:string-replace-all">
        <xsl:param name="text"/>
        <xsl:param name="replace"/>
        <xsl:param name="by"/>
        <xsl:choose>
            <xsl:when test="contains($text, $replace)">
                <xsl:value-of select="substring-before($text,$replace)"/>
                <xsl:value-of select="$by"/>
                <xsl:value-of select="fun:string-replace-all(substring-after($text,$replace), $replace, $by)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>
</xsl:stylesheet>