How do I explode an attribute with values separate

2019-04-11 13:29发布

I have a an attribute who's value may be one or more text strings all delimited by a comma. I wish to transform using XSL the attribute value(s) into their own element;

e.g

<post title='Hello World" tags="Test,Hello,World />

In which I would like it transformed to;

<post>
<title>Hello World</title>
<tag>Test</tag>
<tag>Hello</tag>
<tag>World</tag>
</post>

Is this possible? TIA

标签: xml xslt
3条回答
仙女界的扛把子
2楼-- · 2019-04-11 14:12

I really liked Dimitre answer, but had a small bug with his XSLT 1.0 Solution. When I had "Value,Value,Value" , it would only split the first two and skip the last.

Edit This was not a bug, I didn't notice he was calling the template with CONCAT

Here is my modification to his tokenize xslt 1 template.

<xsl:template name="tokenize">
  <xsl:param name="pText"/>
  <xsl:param name="pTag"/>
  <xsl:if test="string-length($pText)">
    <xsl:element name="{$pTag}">
             <xsl:choose>
                  <xsl:when test="string-length(substring-before($pText, ','))">
                      <xsl:value-of select="substring-before($pText, ',')"/>
                  </xsl:when>
                  <xsl:otherwise><xsl:value-of select="$pText" /></xsl:otherwise>
              </xsl:choose>
    </xsl:element>

    <xsl:call-template name="tokenize">
      <xsl:with-param name="pText" select=
       "substring-after($pText, ',')"/>
      <xsl:with-param name="pTag"><xsl:value-of select="$pTag" /></xsl:with-param>
    </xsl:call-template>
  </xsl:if>

Usage Example

<xsl:call-template name="tokenize">
        <xsl:with-param name="pText">123,234,345</xsl:with>
        <xsl:with-param name="pTag">tag</xsl:with-param >
</xsl:call-template>

Outputs

  <tag>123</tag>
  <tag>234</tag>
  <tag>345</tag>
查看更多
等我变得足够好
3楼-- · 2019-04-11 14:16

The first thing you should do is find the person who used an attribute where he should have used elements and make him stop. The reason we accept the verbosity of XML is that it gives us the benefit of not having to figure out how to parse data. If you're going to pack your XML with data that has to be parsed, why are you using XML in the first place?

查看更多
男人必须洒脱
4楼-- · 2019-04-11 14:27

There are several ways to do this.

I. Using a recursively-called named template in XSLT 1.0 This transformation:

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

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
      <xsl:call-template name="tokenize">
        <xsl:with-param name="pText" 
         select="concat(., ',')"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="tokenize">
      <xsl:param name="pText"/>

      <xsl:if test="string-length($pText)">
        <tag>
          <xsl:value-of select=
           "substring-before($pText, ',')"/>
        </tag>

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

when applied on the originally-provided XML document (corrected to be well-formed):

<post title="Hello World" 
      tags="Test,Hello,World" />

produces the required result:

<post>
   <title>Hello World</title>
   <tag>Test</tag>
   <tag>Hello</tag>
   <tag>World</tag>
</post>

II. Using the str-split-to-words template/function from FXSL 1.x

Here FXSL provides the tokenization functionality:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
>

   <xsl:import href="strSplit-to-Words.xsl"/>

   <xsl:output indent="yes" omit-xml-declaration="yes"/>

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
    <xsl:variable name="vwordNodes">
      <xsl:call-template name="str-split-to-words">
        <xsl:with-param name="pStr" select="."/>
        <xsl:with-param name="pDelimiters" 
                  select="','"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
    </xsl:template>

  <xsl:template match="word">
    <tag>
      <xsl:value-of select="."/>
    </tag>
  </xsl:template>

</xsl:stylesheet>

When applied on the same XML document as before, the same correct output is produced.

III. Using the XPath 2.0 standard function tokenize() from an XSLT 2.0 transformation

This is the easiest way -- if one can use an XSLT 2.0 processor.

The following XSLT 2.0 transformation:

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

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

    <xsl:template match="@*[not(name()='tags')]">
      <xsl:element name="{name()}">
        <xsl:value-of select="."/>
      </xsl:element>
    </xsl:template>

    <xsl:template match="@tags">
    <xsl:for-each select="tokenize(.,',')">
      <tag><xsl:value-of select="."/></tag>
    </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

when applied on the same XML document again produces the wanted result.

查看更多
登录 后发表回答