开发者

Merge and rename fields in xslt

I'm new to xslt, and I fear that this problem hinges on me getting hung up in a procedural mindset.

I have a set of information that I want to rename, and, in some cases, condense. I can handle renaming, but the condensing is hanging me up. My approach has been to rename everything, and then, if duplicates arise from the renaming, to remove the duplicates. Here's some code, for the sake of clarity.

Input:

<variables> 
    <var value="a-value"> a </var>
    <var value="b-value"> b </var>
    <var value="c-value"> c </var>
    <var value="d-value"> d </var>
</variables>

Desired Output:

<variables> 
    <var value="开发者_开发百科new-a-value"> new-a </var>
    <var value="new-b-and-c-value"> new-b-and-c </var>
    <var value="new-d-value"> new-d </var>
</variables>

My xslt:

  <xsl:for-each select="var">
    <xsl:choose>

      <xsl:when test="@value = 'a-value'">
        <var value="new-a-value"> new-a </var>
      </xsl:when>

      <xsl:when test="@value = 'b-value'">
        <var value="new-b-and-c-value"> new-b-and-c </var>
      </xsl:when>

      <xsl:when test="@value = 'c-value'">
        <var value="new-b-and-c-value"> new-b-and-c </var>
      </xsl:when>

      <xsl:when test="@value = 'd-value'">
        <var value="new-d-value"> new-d </var>
      </xsl:when>

    </xsl:choose>
  </xsl:for-each>

I want to remove the duplicate terms after the renaming. I'm not sure where to go from here. My intuition urges me to want to set up some sort for check when a b-value or c-value is found or to run another iteration through the mid-processed xml that would remove duplicates, but I don't think that either approach is viable in xslt.


Refactoring: Making more simple so real problem gets clear: grouping on a mapped key value.

This stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:v="value"
 exclude-result-prefixes="v">
    <xsl:key name="kVarByReplace"
             match="var"
             use="document('')/*/v:v[@o=current()/@value]/@n"/>
    <v:v o="a-value" n="new-a-value"/>
    <v:v o="b-value" n="new-b-and-c-value"/>
    <v:v o="c-value" n="new-b-and-c-value"/>
    <v:v o="d-value" n="new-d-value"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="var">
        <xsl:variable name="vKey"
                      select="document('')/*/v:v[@o=current()/@value]/@n"/>
        <xsl:if test="count(.|key('kVarByReplace',$vKey)[1]) = 1">
            <var value="{$vKey}">
                <xsl:value-of select="$vKey"/>
            </var>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Output:

<variables>
    <var value="new-a-value">new-a-value</var>
    <var value="new-b-and-c-value">new-b-and-c-value</var>
    <var value="new-d-value">new-d-value</var>
</variables>

Note: The use of document() function in xsl:key/@use.

This shows how easy this would be in XSLT 2.0:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="vMap" as="element()*">
        <v o="a-value">new-a-value</v>
        <v o="b-value">new-b-and-c-value</v>
        <v o="c-value">new-b-and-c-value</v>
        <v o="d-value">new-d-value</v>
    </xsl:variable>
    <xsl:template match="variables">
        <variables>
            <xsl:for-each-group select="var"
                                group-by="$vMap[@o=current()/@value]">
                <var value="{current-grouping-key()}">
                    <xsl:value-of select="current-grouping-key()"/>
                </var>
            </xsl:for-each-group>
        </variables>
    </xsl:template>
</xsl:stylesheet>

First solution with a key also for the map.

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:v="value"
 exclude-result-prefixes="v">
    <xsl:key name="kValByOld" match="v:v" use="@o"/>
    <xsl:key name="kVarByReplace"
             match="var"
             use="document('')/*/v:v[@o=current()/@value]/@n"/>
    <xsl:variable name="vStylesheet" select="document('')"/>
    <v:v o="a-value" n="new-a-value"/>
    <v:v o="b-value" n="new-b-and-c-value"/>
    <v:v o="c-value" n="new-b-and-c-value"/>
    <v:v o="d-value" n="new-d-value"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="var">
        <xsl:variable name="vOld" select="@value"/>
        <xsl:for-each select="$vStylesheet">
            <xsl:variable name="vNew"
                          select="key('kValByOld',$vOld)/@n"/>
            <xsl:for-each select="$vOld">
                <xsl:if test="count(..|key('kVarByReplace',$vNew)[1])=1">
                    <var value="{$vNew}">
                        <xsl:value-of select="$vNew"/>
                    </var>
                </xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>


Here is an XSLT 2.0 stylesheet that produces the desired output from your input. Whether it does the right thing with other inputs is another question, but it's designed to be suitably extensible and to demonstrate a variety of techniques that can be used for this kind of problem:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:f="http://local-functions/"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 exclude-result-prefixes="f xs">

<xsl:output indent="yes"/>

<xsl:template match="variables">
  <variable>
     <xsl:for-each-group select="*" group-adjacent="f:is-groupable(.)">
        <xsl:choose>
           <xsl:when test="f:is-groupable(.)">
              <var value="new-{string-join(current-group()/replace(@value, '-value', ''), '-and-')}-value">
                 <xsl:text> new-</xsl:text>
                 <xsl:value-of select="string-join(current-group()/normalize-space(.), '-and-')"/>
                 <xsl:text> </xsl:text>
              </var>
           </xsl:when>
           <xsl:otherwise>
              <xsl:for-each select="current-group()">
                <var value="new-{@value}">
                   <xsl:value-of select="replace(., '\S+', 'new-$0')"/>
                </var>
              </xsl:for-each>      
           </xsl:otherwise>
        </xsl:choose>
     </xsl:for-each-group>
  </variable>
</xsl:template>

<xsl:function name="f:is-groupable" as="xs:boolean">
    <xsl:param name="var" as="element(var)"/>
    <xsl:sequence select="$var/@value = ('b-value', 'c-value')"/>
</xsl:function>     

</xsl:stylesheet>


Regarding "run another iteration through the mid-processed xml that would remove duplicates" - this is possible in XSLT 2.0, or when you have the nodeset extension in XSLT 1.0. But I don't think it's necessary in this case.

Rather than a for-each and a choose-when-test-otherwise, do apply-templates:

<xsl:apply-templates select="var" />

Then you have a template for each type of var you want to process, roughly corresponding to your xsl:when elements:

<xsl:template match="var[@value = 'a-value']">
    <var value="new-a-value"> new-a </var>
</xsl:template>

And for the b/c items, something like:

<xsl:template match="var[@value = 'b-value' or @value = 'c-value'][1]">
    <var value="new-b-and-c-value"> new-b-and-c </var>
</xsl:template>

The [1] makes the template fire only for the first such var among its siblings. Whether this is exactly what you want, depends on how the structure of your input might vary. But given your sample input, it works. You'll also need a template to handle b or c vars that are not the first:

<xsl:template match="var[@value = 'b-value' or @value = 'c-value']"
        priority="-1" />

Note the empty body; and the low priority, so that b/c vars that are first don't get processed by this template.

Summing up, given your sample input, the following XSLT stylesheet gives your requested output:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   version="1.0">
   <xsl:output method="xml" indent="yes"/>

   <xsl:template match="/variables">
      <xsl:copy>
         <xsl:apply-templates select="var"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="var[@value = 'a-value']">
      <var value="new-a-value"> new-a </var>
   </xsl:template>

   <xsl:template match="var[@value = 'b-value' or @value = 'c-value'][1]">
      <var value="new-b-and-c-value"> new-b-and-c </var>
   </xsl:template>

   <xsl:template match="var[@value = 'b-value' or @value = 'c-value']"
        priority="-1" />

   <xsl:template match="var[@value = 'd-value']">
      <var value="new-d-value"> new-d </var>
   </xsl:template>
</xsl:stylesheet>

If that somehow doesn't match what you had in mind, let me know and we can iterate.

If you know there will only be one 'b' in the input, you could get simpler and just have a template to match the 'b' and replace it with the new value; and have an empty template to match the 'c' and throw it away. But it sounded like maybe you can't make that assumption...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜