Question

My input xml is

  <?xml version="1.0" encoding="UTF-8"?>
  <foobar>
    <foo>
        <a>
            <a>
                <a>atr1</a>
                <v>NO</v>
            </a>
            <a>
                <a>more</a>
                <v>more</v>
            </a>
        </a>
        <v>ONE</v>
    </foo>
    <bar>
        <baz>
            <a>
                <a>
                    <a>attr</a>
                    <v>123</v>
                </a>
                <a>
                    <a>attr222</a>
                    <v>22</v>
                </a>
            </a>
            <v>TWO</v>
        </baz>
        <a>
            <a>
                <a>atr6</a>
                <v>ATR</v>
                </a>
           </a>
 </bar>
  <a>
   <a>
      <a>atr0</a>
      <v>NO</v>
  </a>
  <a>
      <a>atr2</a>
      <v>NO</v>
</a>
</a>
</foobar>

My desired output is

<?xml version="1.0" encoding="UTF-8"?>
<foobar atr0="NO" atr2="NO">
    <foo atr1="NO" more="more">ONE</foo>
    <bar atr6="ATR">
        <baz attr="123" attr222="22">TWO</baz>
    </bar>
</foobar>   

i am trying to convert all the nested elements into attributes. My xslt script is-

   <?xml version="1.0" encoding="utf-8"?>
   <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
   <xsl:strip-space elements="*"/>

<!-- Matches all of nodes -->
<xsl:template match = "node()">
<xsl:copy>
<xsl:for-each select= "*">
  <xsl:attribute name="{//a[text()]}">
    <xsl:value-of select="//v[text()]"/>
  </xsl:attribute>  
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

the script is converting the first foobar element to attribute only. what am i doing wrong?

Was it helpful?

Solution

You don't have an xsl:apply-templates so you never get past that first root element. Also, node() includes text, comments, and processing instructions and you don't want to try to create an attribute if you match one of those.

Try something like this instead...

XSLT 1.0

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

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

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

    <xsl:template match="a/a" priority="1">
        <xsl:attribute name="{a}">
            <xsl:value-of select="v"/>
        </xsl:attribute>
    </xsl:template>

    <xsl:template match="v">
        <xsl:apply-templates/>
    </xsl:template>

</xsl:stylesheet>

Output

<foobar atr0="NO" atr2="NO">
   <foo atr1="NO" more="more">ONE</foo>
   <bar atr6="ATR">
      <baz attr="123" attr222="22">TWO</baz>
   </bar>
</foobar>

OTHER TIPS

This is what your code is doing:

You are selecting one node(): foobar, when you are inside it you have a for-each and there you select any node in current context: *, so you match foo, and from there you get all the //a that contain text (the only one: atr1) and all the //v that contain text: NO. And since you never call <xsl:apply-templates> or use some expression to process the nodes down the tree, no other templates are ever called.

You need to process other nodes recursively. Also, you can't just use //a because in some nodes there are more than one a and you would end up with all of them concatenated.

One way to do it is using multiple recursive templates. This is a possible solution:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <xsl:template match="*[not(self::a)][not(self::v)]">
        <xsl:copy>
            <xsl:if test="a">
                <xsl:apply-templates select="a" />   
            </xsl:if>
            <xsl:apply-templates select="*[not(self::a)]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="a[following-sibling::v][text()]">
        <xsl:attribute name="{normalize-space(.)}">
            <xsl:value-of select="following-sibling::v" />
        </xsl:attribute>  
    </xsl:template>

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

</xsl:stylesheet>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top