BDF
BDF

Reputation: 99

Rewrite without <xsl:for-each>; select all text separated by spaces; dropping text with attribute

I have the following XSL working but there should be a way to rewrite it without using the xsl:for-each element. I need to take an arbitrary block of XML, drop all elements that have a 'drop' attribute and concatenate the remaining text with spaces without adding extra spaces at the end or end of the concatenated text.

I should say that the only part of the XML I can key off of is the 'drop' attribute. I cannot key off of any of element names like 'testing', 'catalog', 'book', etc.

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

    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*">
        <xsl:for-each select="//*[not(@drop)]/text()">
            <xsl:value-of select="."/>
            <xsl:if test="position() != last()">
                <xsl:text> </xsl:text>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

The XML I've been testing with:

<testing>
    <text drop="meta">Drop this meta</text>
    <catalog>
        <book id="bk101">
            <text drop="stuff">Drop this stuff</text>
            <title ti="Full Title">XML Developer\ts Guide</title>
            <author>Gambardella, Matthew</author>
        </book>
    </catalog>
</testing>

and the valid output:

XML Developer   s Guide Gambardella, Matthew

edit: As pointed out by @michael.hor257k it assumed the XML will not have any mixed content

Upvotes: 2

Views: 665

Answers (3)

hr_117
hr_117

Reputation: 9627

Perhaps this is what you are looking for (works also with XSLT 1.0)

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

  <xsl:template match="*[not(@drop) and normalize-space(text()) != '']" >
    <xsl:value-of select="." />
    <xsl:if test="position() != last()">
      <xsl:text> </xsl:text>
    </xsl:if>
  </xsl:template>
  <xsl:template match="text()" />

</xsl:stylesheet>

Update: As mentioned in comment the use of position() was not right because it checks the position in the current node-list and not in the document. Therefore try this (still XSLT 1.0 solution):

 <xsl:variable name="last" select="generate-id((//*[not(@drop)]/text())[last()])" /> 
 <xsl:template match="*[not(@drop)]/text()[normalize-space() != '']" >
    <xsl:value-of select="." />
    <xsl:if test="generate-id() != $last">
      <xsl:text> </xsl:text>
    </xsl:if>
 </xsl:template>
 <xsl:template match="text()" />

Upvotes: 0

michael.hor257k
michael.hor257k

Reputation: 117165

As Martin Honnen pointed out in his deleted answer, in XSLT 2.0 this could be accomplished by a single xsl:value-of instruction. Here's his answer, with a minor correction by me:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="/">
    <xsl:value-of select="//*[not(@drop) and text()]" separator=" "/>
</xsl:template>

</xsl:stylesheet>

However, this assumes no mixed content in the source XML (as I believe the other answers do too). If this were a requirement, you could use :

<xsl:value-of select="string-join(//*[not(@drop)]/text(), ' ')" />

instead.

Upvotes: 1

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243599

This transformation:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vFirst" select="(//*[not(@drop)]/text())[1]"/>

 <xsl:template match="*[not(@drop)]/text()[not(. is $vFirst)]">
   <xsl:value-of select="concat(' ',  .)"/>
 </xsl:template>
 <xsl:template match="*[@drop]/text()"/>
</xsl:stylesheet>

when applied on the provided source XML document:

<testing>
    <text drop="meta">Drop this meta</text>
    <catalog>
        <book id="bk101">
            <text drop="stuff">Drop this stuff</text>
            <title ti="Full Title">XML Developer     s Guide</title>
            <author>Gambardella, Matthew</author>
        </book>
    </catalog>
</testing>

produces the wanted, correct result:

XML Developer s Guide Gambardella, Matthew

When applied on more irregular XML documents, like this:

<testing>
    <text drop="meta">Drop this meta</text>
    <catalog>
        <book id="bk100"><title ti="Full Title">JSON Developer     s Guide</title></book>
        <book id="bk101">
            <text drop="stuff">Drop this stuff</text>
            <title ti="Full Title">XML Developer     s Guide</title>
            <author>Gambardella, Matthew</author>
        </book>
    </catalog>
</testing>

the transformation again produces the correct, wanted result:

JSON Developer s Guide XML Developer s Guide Gambardella, Matthew

Do note that the solutions in the other two answers don't produce the correct result when applied on the above XML document.

Upvotes: 1

Related Questions