user1075738
user1075738

Reputation: 125

Rare XSLT transformation

I am trying to make a transformation with XSLT. This is my XML source:

<Memoria>
  <seccion>
    <contenido><p>TEXT</p>
    <ul>
      <li>LIST</li>
    </ul>
    <p>ANOTHER TEXT</p>
    <p>&amp;nbsp;</p></contenido>
  </seccion>      
</Memoria>

I am trying use XSLT like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="xml" indent="yes"/>
<xsl:template match="/Memoria">
<cosa>
  <xsl:for-each select="seccion">
  <Address>
    <xsl:for-each select="contenido">
    <FirstName><xsl:value-of select="p" /></FirstName>
    </xsl:for-each>
    <ul>
      <LastName><xsl:value-of select="ul/li" /></LastName>
   </ul>
  </Address>
  </xsl:for-each>
 </cosa>
</xsl:template>

</xsl:stylesheet>

And get this:

<?xml version="1.0"?>
<cosa>
  <Address>
    <FirstName>TEXT</FirstName>
    <ul>
      <LastName>LIST</LastName>
   </ul>
  </Address>
</cosa>

But I expect:

<?xml version="1.0"?>
<cosa>
  <Address>
    <FirstName>TEXT</FirstName>
    <ul>
      <LastName>LISTr</LastName>
   </ul>
   <FirstName>ANOTHER TEXT</FirstName>
  </Address>
</cosa>

I get this XML from a external application.

Upvotes: 1

Views: 147

Answers (4)

Rookie Programmer Aravind
Rookie Programmer Aravind

Reputation: 12154

rare transformation? provided with a proper Input XML and desired output, it wouldn't have been rare in XSLT ..
Honestly I am not happy with your sample Input and output XML. I have come up with one XML hope this is what really your XML looks like. If not then bring up precise i/p and o/p XMLs.

<?xml version="1.0" encoding="utf-8"?>
<Memoria>
  <seccion>
    <contenido>
      <p>TEXT</p>
      <ul>
        <li>Item1</li>
        <li>Item2</li>
        <li>Item3</li>
      </ul>
      <p>ANOTHER TEXT</p>
      <ul>
        <li>Itema</li>
        <li>Itemb</li>
        <li>Itemc</li>
      </ul>
    </contenido>
  </seccion>
</Memoria>

Corresponding XSLT code:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/">
    <cosa>
      <xsl:apply-templates select="//contenido"/>
    </cosa>
  </xsl:template>

  <xsl:template match="text()"/>

  <xsl:template match="contenido">
    <Address>
      <xsl:apply-templates select="p|ul"/>
    </Address>
  </xsl:template>

  <xsl:template match="p[not(contains(.,'&amp;'))]">
    <FirstName>
      <xsl:value-of select="."/>
    </FirstName>
  </xsl:template>

  <xsl:template match="ul">
    <ul>
      <xsl:for-each select="li">
    <LastName>
      <xsl:value-of select="."/>
    </LastName>
      </xsl:for-each>
    </ul>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 3

Lukasz
Lukasz

Reputation: 7662

I'm not sure what exactly you want to achieve... However, the following XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/Memoria">
        <cosa>
            <xsl:for-each select="seccion">
                <Address>
                    <xsl:apply-templates select="contenido" />
                </Address>
            </xsl:for-each>
        </cosa>
    </xsl:template>

    <xsl:template match="contenido">
        <xsl:apply-templates select="p | ul" />
    </xsl:template>

    <xsl:template match="p">
        <FirstName>
            <xsl:value-of select="text()"/>
        </FirstName>
    </xsl:template>

    <xsl:template match="ul">
        <ul>
            <LastName>
                <xsl:value-of select="li"/>
            </LastName>
        </ul>
    </xsl:template>

</xsl:stylesheet>

will produce the following output:

<?xml version="1.0" encoding="UTF-8"?>
<cosa>
    <Address>
        <FirstName>TEXT</FirstName>
        <ul>
            <LastName>LIST</LastName>
        </ul>
        <FirstName>ANOTHER TEXT</FirstName>
        <FirstName>&amp;nbsp;</FirstName>
    </Address>
</cosa>

You may now think of adding of condition to exclude that &amp;&nbsp; element from the output:

<xsl:template match="p">
    <xsl:if test="not(contains(text(), '&amp;'))">
    <FirstName>
        <xsl:value-of select="text()"/>
    </FirstName>
    </xsl:if>
</xsl:template>

Upvotes: 1

Dmitriy Khaykin
Dmitriy Khaykin

Reputation: 5258

Assuming you know for sure that you'll always have FirstName in first P element, and second first name in the 2nd P element, you can use a common node-set() function to get the specific element inside your for-each loop:

The following modified XSLT generates the desired output you have placed in your question:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:exsl="http://exslt.org/common">

  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/Memoria">
    <cosa>
      <xsl:for-each select="seccion">
        <Address>
          <xsl:for-each select="contenido">
            <FirstName>
              <xsl:value-of select="p" />
            </FirstName>
            <ul>
              <LastName>
                <xsl:value-of select="ul/li" />
              </LastName>
            </ul>
            <FirstName>
              <xsl:value-of select="exsl:node-set(.)/p[2]/."/>
            </FirstName>
          </xsl:for-each>
        </Address>  
      </xsl:for-each>
    </cosa>
  </xsl:template>

</xsl:stylesheet>

Note: I usually use xmlns:msxsl="urn:schemas-microsoft-com:xslt" for the node-set() function but I think exsl is not as vendor specific :)

This is likely to break if your XML structure is not consistent or predictable; otherwise it will work and you don't need another for-each to get the 2nd P element for the second FirstName result element.

I hope this is helpful!

Upvotes: 0

apmasell
apmasell

Reputation: 7153

xsl:value-of is going to get the contents of the first element in a node set. If you want to operate over all the elements in a set, you need xsl:for-each or xsl:apply-templates. However, if you do that, you will get:

<?xml version="1.0"?>
<cosa>
  <Address>
    <FirstName>TEXT</FirstName>
    <ul>
      <LastName>LISTr</LastName>
   </ul>
   <FirstName>ANOTHER TEXT</FirstName>
   <FirstName>&amp;&nbsp;</FirstName>
  </Address>
</cosa>

Upvotes: 0

Related Questions