XSLT3 joining values with separator
Asked Answered
P

2

1

I'm pretty new to XSLT and I've been struggling to replicate the solution mentioned here XSL for-each: how to detect last node? for longer than I'm willing to admit :(

I've setup this fiddle. https://xsltfiddle.liberty-development.net/naZXVFi

I was hoping I could use just the value-of + separator, vs choose / when xslt tools, as it did seem more idiomatic.

  • I can't get the separator to show up;
  • nor can I select just the child of skill, I always get the descendants too. That's to say, I shouldn't see any detail in the output.
  • bonus: not sure why that meta tag is not self closing (warning in the html section)

Desired output: skill1, skill2, skill3, skill4, skill5 (no comma space for the last one)

Any help would be greatly appreciated. Thanks.

EDIT: including the code here too: xml: (need to add ref to xslt):

<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?> <!-- not in fiddle -->

<skills>
    <skill>skill1</skill>
    <skill>skill2</skill>
    <skill>skill3
        <details>
            <detail>detail1</detail>
            <detail>detail2</detail>
        </details>
    </skill>
    <skill>skill4</skill>
    <skill>skill5</skill>
</skills>

And test.xsl:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:array="http://www.w3.org/2005/xpath-functions/array"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="html" indent="yes" html-version="5"/>

  <xsl:template match="/">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
      </head>
      <body>
         <xsl:for-each select="/skills/skill">
            <xsl:value-of select="." separator=", "/>
        </xsl:for-each>
      </body>
    </html>


  </xsl:template>

</xsl:stylesheet>
Patriarchy answered 25/5, 2020 at 15:40 Comment(0)
N
1

In general, with XSLT 2/3 to output a sequence separated by some separator string, you simply use xsl:value-of select="$sequence" with the appropriate separator string in the separator attribute (and no for-each):

  <xsl:template match="skills">
    <xsl:value-of select="skill/text()[normalize-space()]/normalize-space()" separator=", "/>
  </xsl:template>

https://xsltfiddle.liberty-development.net/naZXVFi/1

In most cases you would just need select="skill" separator=", " but given your descendants and the white space you seem to want to eliminate the select expression above is a bit more complicated.

Nealson answered 25/5, 2020 at 18:20 Comment(5)
Thanks. It works in the fiddle, but I can't get it to transpose to my setup for now... Any pointers regarding what text()[normalize-space()]/normalize-space() does please? XSLT feels like black magic to me at this point... After just removing the spaces in the xml and changing the core xsl to <xsl:value-of select="skill/text()" separator=", "/> , I loose the separator again ( results in skill1skill2...)Patriarchy
The path skill selects all skill child elements, the path skill/text() all text child nodes of all skill child elements. In square brackets you put predicates/conditions to filter out nodes text()[normalize-space()] filters out the text nodes where the normalize-space call returns a non-empty string. A last step in XPath 2.0 or later can call functions returning atomic values like strings and not simply select nodes, so the last step /normalize-space() returns the result of calling that function on each text node so that we get a string with leading and trailing whitespace strippedNealson
Consider reading an XPath tutorial like altova.com/training/xpath3 if such simple path expressions are not known to you.Nealson
Great explanation, thanks. Any idea why I'm loosing the separator in the case I menitoned? (I'm surprised that I manage to get all the values but the separator doesn't make it...)Patriarchy
The spec for w3.org/TR/xslt-30/#element-value-of says "If the separator attribute is present, then the effective value of this attribute is used to separate adjacent items in the result sequence, as described in 5.7.2 Constructing Simple Content.". That says in w3.org/TR/xslt-30/#constructing-simple-content that "Adjacent text nodes in the sequence are merged into a single text node.". So that is the reason for the result you see, you could use select="skill/text()/string()" to prevent the merge and have the separator applied.Nealson
S
1

Martin has given you the detailed work-through to get the final result including getting rid of the extra spaces etc, but at a high level, here's how to use xsl:value-of with separator correctly.

You have:

  <body>
     <xsl:for-each select="/skills/skill">
        <xsl:value-of select="." separator=", "/>
    </xsl:for-each>
  </body>

This says that for each skill node, take the content of that node and display it. Notably, the value-of only sees one skill at a time, so there is nothing to join with the comma separator.

The answer which would get you what you want is:

  <body>
        <xsl:value-of select="/skills/skill" separator=", "/>
  </body>

This says to take the set of skill nodes and display them joined by comma separators. You can see the output at https://xsltfiddle.liberty-development.net/naZXVFi/4

Siliculose answered 26/5, 2020 at 4:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.