2011-12-14 100 views
9

我有一个具有csv值的输入字符串。例如,1,2,3 我需要将每个值分开并分配给for-each循环中的目标节点。逗号分隔的字符串解析XSLT为每个节点

我这下面的模板,分割基于定界符输入字符串。如何将每个分隔值分配给for-each循环中的目标元素。

<xsl:template name="output-tokens"> 
<xsl:param name="list"/> 
<xsl:param name="delimiter"/> 
<xsl:variable name="newlist"> 
    <xsl:choose> 
    <xsl:when test="contains($list, $delimiter)"> 
     <xsl:value-of select="normalize-space($list)"/> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:value-of select="concat(normalize-space($list), $delimiter)"/> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:variable> 
<xsl:variable name="first" select="substring-before($newlist, $delimiter)"/> 
<xsl:variable name="remaining" 
       select="substring-after($newlist, $delimiter)"/> 
<xsl:variable name="count" select="position()"/> 
<num> 
    <xsl:value-of select="$first"/> 
</num> 
<xsl:if test="$remaining"> 
    <xsl:call-template name="output-tokens"> 
    <xsl:with-param name="list" select="$remaining"/> 
    <xsl:with-param name="delimiter"> 
     <xsl:value-of select="$delimiter"/> 
    </xsl:with-param> 
    </xsl:call-template> 
</xsl:if> 
</xsl:template> 

输入XML:

<out1:AvailableDates> 
<out1:AvailableDate>15/12/2011,16/12/2011,19/12/2011,20/12/2011,21/12/2011</out1:AvailableDate> 
</out1:AvailableDates> 

预期输出:

<tns:AvailableDates> 
<tns:AvailableDate>15/12/2011</tns:AvailableDate> 
<tns:AvailableDate>16/12/2011</tns:AvailableDate> 
<tns:AvailableDate>120/12/2011</tns:AvailableDate> 
</tns:AvailableDates> 

回答

14

下面是一个完整和短,真XSLT 1.0溶液

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:out1="undefined" xmlns:tns="tns:tns" 
    exclude-result-prefixes="out1 tns"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:template match="out1:AvailableDate"> 
    <tns:AvailableDates> 
    <xsl:apply-templates/> 
    </tns:AvailableDates> 
</xsl:template> 

<xsl:template match="text()" name="split"> 
    <xsl:param name="pText" select="."/> 
    <xsl:param name="pItemElementName" select="'tns:AvailableDate'"/> 
    <xsl:param name="pItemElementNamespace" select="'tns:tns'"/> 

    <xsl:if test="string-length($pText) > 0"> 
    <xsl:variable name="vNextItem" select= 
     "substring-before(concat($pText, ','), ',')"/> 

     <xsl:element name="{$pItemElementName}" 
        namespace="{$pItemElementNamespace}"> 
     <xsl:value-of select="$vNextItem"/> 
     </xsl:element> 

     <xsl:call-template name="split"> 
     <xsl:with-param name="pText" select= 
         "substring-after($pText, ',')"/> 
     <xsl:with-param name="pItemElementName" select="$pItemElementName"/> 
     <xsl:with-param name="pItemElementNamespace" select="$pItemElementNamespace"/> 
     </xsl:call-template> 
    </xsl:if> 
</xsl:template> 
</xsl:stylesheet> 

当所提供的XML文档施加(校正为进行良好的形成):

<out1:AvailableDates xmlns:out1="undefined"> 
    <out1:AvailableDate>15/12/2011,16/12/2011,19/12/2011,20/12/2011,21/12/2011</out1:AvailableDate> 
</out1:AvailableDates> 

的希望,正确的结果产生

<tns:AvailableDates xmlns:tns="tns:tns"> 
    <tns:AvailableDate>15/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>16/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>19/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>20/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>21/12/2011</tns:AvailableDate> 
</tns:AvailableDates> 
4

使用XSLT 2.0,您可以使用tokenize(string, separator)函数,而不是指定的模板。

这XSL:

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:tns="http://tnsnamespace"> 

    <xsl:template match="AvailableDate"> 
     <tns:AvailableDates> 
      <xsl:for-each select="tokenize(current(), ',')"> 
       <tns:AvailableDate> 
        <xsl:value-of select="."/> 
       </tns:AvailableDate> 
      </xsl:for-each> 
     </tns:AvailableDates> 
    </xsl:template> 
</xsl:stylesheet> 

给出以下结果:

<?xml version="1.0" encoding="UTF-8"?> 
<tns:AvailableDates xmlns:tns="http://tnsnamespace"> 
    <tns:AvailableDate>15/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>16/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>19/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>20/12/2011</tns:AvailableDate> 
    <tns:AvailableDate>21/12/2011</tns:AvailableDate> 
</tns:AvailableDates> 

更新

随着下面的模板下向后兼容模式XSLT 2.0处理器给出相同的结果:

<xsl:template match="AvailableDate"> 
    <tns:AvailableDates> 
     <xsl:variable name="myValue"> 
      <xsl:call-template name="output-tokens"> 
       <xsl:with-param name="list" select="."/> 
       <xsl:with-param name="delimiter" select="','"/> 
      </xsl:call-template> 
     </xsl:variable> 

     <xsl:for-each select="$myValue/node()"> 
      <tns:AvailableDate> 
       <xsl:value-of select="."/> 
      </tns:AvailableDate> 
     </xsl:for-each> 
    </tns:AvailableDates> 
</xsl:template> 

对于XSLT 1.0 - 这是不可能简单(与标准功能)获取通过变量节点 - 见@Dimitre Novatchev回答XSLT 1.0 - Create node set and pass as a parameter

为此XSLT 1.0处理器包含扩展函数:node-set(...)

对于撒克逊6.5 node-set()函数在http://icl.com/saxon命名空间

所以限定在XSLT的情况下1.0处理器的解决办法是:

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:exslt="http://exslt.org/common" 
    xmlns:out1="http://out1namespace" 
    xmlns:tns="http://tnsnamespace" 
    exclude-result-prefixes="out1 exslt"> 
    <xsl:output omit-xml-declaration="yes" indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="out1:AvailableDate"> 
     <tns:AvailableDates> 
      <xsl:variable name="myValue"> 
       <xsl:call-template name="output-tokens"> 
        <xsl:with-param name="list" select="."/> 
        <xsl:with-param name="delimiter" select="','"/> 
       </xsl:call-template> 
      </xsl:variable> 
      <xsl:for-each select="exslt:node-set($myValue)/node()"> 
       <tns:AvailableDate> 
        <xsl:value-of select="."/> 
       </tns:AvailableDate> 
      </xsl:for-each> 
     </tns:AvailableDates> 
    </xsl:template> 

    <xsl:template name="output-tokens"> 
     <xsl:param name="list"/> 
     <xsl:param name="delimiter"/> 
     <xsl:variable name="newlist"> 
      <xsl:choose> 
       <xsl:when test="contains($list, $delimiter)"> 
        <xsl:value-of select="normalize-space($list)"/> 
       </xsl:when> 
       <xsl:otherwise> 
        <xsl:value-of select="concat(normalize-space($list), $delimiter)"/> 
       </xsl:otherwise> 
      </xsl:choose> 
     </xsl:variable> 
     <xsl:variable name="first" select="substring-before($newlist, $delimiter)"/> 
     <xsl:variable name="remaining" 
      select="substring-after($newlist, $delimiter)"/> 
     <xsl:variable name="count" select="position()"/> 
     <num> 
      <xsl:value-of select="$first"/> 
     </num> 
     <xsl:if test="$remaining"> 
      <xsl:call-template name="output-tokens"> 
       <xsl:with-param name="list" select="$remaining"/> 
       <xsl:with-param name="delimiter"> 
        <xsl:value-of select="$delimiter"/> 
       </xsl:with-param> 
      </xsl:call-template> 
     </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

感谢@Dimitre Novatchev纠正我和他约从变量访问节点集的答案。

+0

非常感谢。我正在使用BPEL 10g。它有xslt版本1.0。它有什么可能? – Arun 2011-12-14 09:50:05

+0

我已经更新了我对Xslt-1.0的回答 - 它使用*输出令牌*命名模板 – Vitaliy 2011-12-14 10:54:06

+0

@Vitaliy:请使用任何XSLT 1.0(兼容)处理器运行您的XSLT 1.0解决方案,并确认这会产生错误。在XSLT 1.0中,在RTF(结果树片段)上允许有一组非常有限的操作。请改正。 – 2011-12-14 14:08:34

0

就个人而言,我更喜欢基于自定义扩展功能,这个变体。该方法紧凑且干净,并且在XSLT 1.0中工作正常(至少在任何最新的JVM中嵌入了XALAN 2.7)。

1)声明一个类具有静态方法返回org.w3c.dom.Node中

package com.reverseXSL.util; 

import org.w3c.dom.*; 
import java.util.regex.*; 
import javax.xml.parsers.DocumentBuilderFactory; 

public class XslTools { 

    public static Node splitToNodes(String input, String regex) throws Exception { 
    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 
    Element item, list = doc.createElement("List"); 
    Pattern p = Pattern.compile(regex); 
    Matcher m = p.matcher(input); 
    while (m.find()) { 
     item = doc.createElement("Item"); 
     StringBuffer sb = new StringBuffer(); 
     for (int i=1; i<=m.groupCount(); ++i) if (m.start(i)>=0) sb.append(m.group(i)); 
     Text txt = doc.createTextNode(sb.toString()); 
     item.appendChild(txt); 
     list.appendChild(item); 
    } 
    return list; 
    } 

} 

该功能分割的是正则表达式模式的输入字符串,并创建那种<列表的文档片段>项目> </Item> <项目> B </Item> <项目> C </Item> </List>。 正则表达式按顺序匹配,每个匹配产生一个Item元素,其值由每个正则表达式匹配中的捕获组(某些可能为空)组成。这允许摆脱分隔符和其他语法字符。

举例来说,拆分逗号分隔的列表一样" A, B ,, C",跳过空值,和修剪多余的空格(因此得到上面的节点列表),使用像'\s*([^,]+?)\s*(?:,|$)'正则表达式 - 一记扭一个!相反,如果您希望将输入文本以固定大小(此处为10个字符)与最后一个项目分开,请使用'(.{10}|.+)'之类的正则表达式 - 爱它!

然后,您可以使用该函数在XSLT 1.0如下(相当紧凑!):

<xsl:stylesheet version="1.0" xmlns:var="com.reverseXSL.util.XslTools" extension-element-prefixes="var" ... 
... 
<xsl:template ... 
    ... 
    <xsl:for-each select="var:splitToNodes(Detail/CsvText,'\s*([^,]+?)\s*(?:,|$)')/Item"> 
    <Loop><xsl:value-of select="."/></Loop> 
    </xsl:for-each> 
... 

上执行模板匹配产生的输入片段<Detail><CsvText>a, b ,c </CsvText></Detail>你会产生<Loop>a</Loop><Loop>b</Loop><Loop>c</Loop>

的诀窍是不要忘记按照XPath“/ Item”(或“/ *”)生成Node/Item的函数调用,因为您会注意到,所以Node序列会返回到for-each循环。