0

I need to turn the values in a xml attribute through xsl into a svg rect pos x and y

Therefore need to parse from

Examples

<item value="20 10 40" />
<item value="200 0 100" />
<item value="2666 10 40 95" />

parse each item value attribute following some rules (that's the part I'm not sure about, how to extract the numbers into separate variables)

like

 <item value="20 10 40" />
              X  Z  Y

Extract X (20) in var1 and Y (40) in var2

into

<rect x="{$var1}" y="{$var2}" />

(if I want the first and third value in this case)

Basically I need to understand how to parse any TWO of a series of multiple values contained within the attribute VALUE and pass them into the rect vars as var1 and var2 separately.

From my research so far, I have found out 2 methods but not sure how to apply in this case, either substring-before and after or tokenize, please note this needs to work loaded directly in a browser.

Edit1

I have found an ugly solution so far to extract the data for a case of 3 numbers

Xml

<item value="20 10 40" />

Xsl

Value 1    <xsl:value-of select="substring-before (@value, ' ')"/>
Value 2    <xsl:value-of select="substring-before(substring-after (@value, ' '), ' ')"/>
Value 3    <xsl:value-of select="substring-after(substring-after (@value, ' '), ' ')"/>

Result

Value 1    20
Value 2    10
Value 3    40

So looking for something cleaner, perhaps recursive that accept any amount of numbers in the string and parse all.

Onetyper
  • 3
  • 4
  • 1
    Are you saying you want to map from 3 `items` to 1 `rect`? 1 `item` to 1 `rect`? How exactly? You've not explained what you want to do clearly enough for us to help. – kjhughes Nov 21 '15 at 02:47
  • Hi, I'm looking for a flexible solution so I can parse any two numbers of my choice within value attribute separated by a space (like first and third number from left to right) and through xsl put them into 2 separate x and y variables in rect. – Onetyper Nov 21 '15 at 06:17
  • Please indicate if using XSLT 1.0 or 2.0. – michael.hor257k Nov 21 '15 at 08:47
  • Looking for a clean solution that works loading xml in a modern browser like chrome or firefox (and applies xsl stylesheet), so I suppose 1.0, as I have read browsers are not supporting 2.0 – Onetyper Nov 21 '15 at 09:01
  • If you need to do this for lots of input data then my first suggestion would be to change XML format to put any single value in a single element. If you can't do that then look into http://exslt.org/str/functions/tokenize/index.html where the XSLT implementation is provided at http://exslt.org/str/functions/tokenize/str.tokenize.template.xsl. – Martin Honnen Nov 21 '15 at 09:17
  • Thanks Martin, I cannot modify the xml format and I need to use xslt 1.0, tokenize, correct me if I'm wrong is xslt 2.0 and doesn't work when xml and xslt are simply loaded in a browser – Onetyper Nov 21 '15 at 09:31
  • The links I have pointed you to provide an XSLT 1.0 based implementation of a `tokenize` template. – Martin Honnen Nov 21 '15 at 09:53
  • It's not clear how I can use that, could you help with a working example using my xml sample, so I can connect the dots? – Onetyper Nov 21 '15 at 09:58

2 Answers2

1

Extracting values from a delimited list in XSLT 1.0 is awkward, since it has no tokenize() function.

If the number of values in the list is small (like in your example), you can use nested substring-before() and substring-after() calls, as shown (now) in your question.

A more generic solution, which is also more suitable to handle larger lists, would use a recursive named template, for example:

<xsl:template name="get-Nth-value">
    <xsl:param name="list"/>
    <xsl:param name="N"/>
    <xsl:param name="delimiter" select="' '"/>
    <xsl:choose>
        <xsl:when test="$N = 1">
            <xsl:value-of select="substring-before(concat($list, $delimiter), $delimiter)"/>
        </xsl:when>
        <xsl:when test="contains($list, $delimiter) and $N > 1">
            <!-- recursive call -->
            <xsl:call-template name="get-Nth-value">
                <xsl:with-param name="list" select="substring-after($list, $delimiter)"/>
                <xsl:with-param name="N" select="$N - 1"/>
                <xsl:with-param name="delimiter" select="$delimiter"/>
            </xsl:call-template>
        </xsl:when>
    </xsl:choose>
</xsl:template>

Example of call:

<xsl:template match="item">
    <rect>
        <xsl:attribute name="x">
            <xsl:call-template name="get-Nth-value">
                <xsl:with-param name="list" select="@value"/>
                <xsl:with-param name="N" select="1"/>
            </xsl:call-template>                
        </xsl:attribute>
        <xsl:attribute name="y">
            <xsl:call-template name="get-Nth-value">
                <xsl:with-param name="list" select="@value"/>
                <xsl:with-param name="N" select="2"/>
            </xsl:call-template>                
        </xsl:attribute>
    </rect>
</xsl:template>

Demo:http://xsltransform.net/ncdD7mh

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
1

Here is an example using the EXSLT extensions, online at http://xsltransform.net/bnnZW2, the code is

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" exclude-result-prefixes="exsl str">

    <xsl:include href="http://exslt.org/str/functions/tokenize/str.tokenize.template.xsl"/>

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

    <xsl:template match="item">
      <xsl:copy>
        <xsl:variable name="tokens-rtf">
            <xsl:call-template name="str:tokenize">
                <xsl:with-param name="string" select="@value"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:variable name="tokens" select="exsl:node-set($tokens-rtf)/token"/>
        <x>
            <xsl:value-of select="$tokens[1]"/>
        </x> 
        <y>
            <xsl:value-of select="$tokens[2]"/>
        </y>
      </xsl:copy>

    </xsl:template>
</xsl:transform>

the input is

<root>
    <item value="20 10 40" />
    <item value="200 0 100" />
    <item value="2666 10 40 95" />
</root>

the output is

<root>
    <item><x>20</x><y>10</y></item>
    <item><x>200</x><y>0</y></item>
    <item><x>2666</x><y>10</y></item>
</root>

It should be clear how to access other tokens by the position, of course you can also for-each or apply-templates over $tokens.

Note that XSLT processors in browsers might not allow you to import or include a stylesheet module from a different domain, so you would to make sure you put the referenced stylesheet module (str.tokenize.template.xsl) on your own server and reference it from there.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110