0

I found a similar question here:

How to read attribute of a parent node from a child node in XSLT

But not exactly what I need here. Suppose using the same example here:

<A>
 <b attr1="xx">
   <c>
    Turn this into an Attribute  
   </c>
 </b>
</A>

and I want the resulting xml after xslt looks like:

 <A>
  <b attr1="xx" cAttr="Turn this into an Attribute">
  </b>
 </A>

Using my current knowledge, I could only manage to get rid of the node or change its name to the desired name "cAttr", but I really have no idea of how to turn the whole node into an attribute of the parent node, by only knowing how to refer to attribute field of the parent node won't help me a lot here.

My current code just looks like this:

<xsl:template match="c">
 <xsl:element name="cAttr">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:element>
</xsl:template>

Thanks in advance.

Community
  • 1
  • 1
Kevin
  • 6,711
  • 16
  • 60
  • 107

4 Answers4

2
<!-- match b node -->
<xsl:template match="b">
  <!-- apply templates on all attributes and nodes. see two templates below -->
  <xsl:apply-templates select="@*|node()"/>
</xsl:template>

<!-- copy all existing attrs -->
<xsl:template match="b/@*">
  <xsl:copy-of select="."/>
</xsl:template>

<!-- populate attributes from nodes -->
<xsl:template match="b/node()">
  <xsl:attribute name="{name()}Attr"> <!-- attribute name -->
    <xsl:value-of select="text()"/> <!-- attribute value -->
  </xsl:attribute>
  <!-- match all attributes on child node -->
  <xsl:apply-templates select="@*">
    <xsl:with-param name="prefix" select="name()"/> <!-- pass node name to template -->
  </xsl:apply-templates>
</xsl:template>

<xsl:template match="b/node()/@*">
  <xsl:param name="prefix"/>
  <!-- creates attribute prefixed with child node name -->
  <xsl:attribute name="{$prefix}-{name()}">
    <xsl:value-of select="."/>
  </xsl:attribute>
</xsl:template>
Gleb M Borisov
  • 617
  • 3
  • 10
  • Wouldn't `b/node()` not also try to turn child elements' attributes into attributes of ´b´? Just trying to learn here. – DanMan Feb 22 '11 at 20:51
1

Besides @Flack correct push style, and just for fun, two pull style approaches:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="b/*[1][self::c]" name="attribute" priority="1">
        <xsl:attribute name="cAttr">
            <xsl:value-of select="../c"/>
        </xsl:attribute>
    </xsl:template>
    <xsl:template match="b[c]/*[1]">
        <xsl:call-template name="attribute"/>
        <xsl:call-template name="identity"/>
    </xsl:template>
    <xsl:template match="b/c"/>
</xsl:stylesheet>

Note: Only rules with pattern matching overwriting the identity rule.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="b[c]/*[1]" priority="1">
        <xsl:attribute name="cAttr">
            <xsl:value-of select="../c"/>
        </xsl:attribute>
        <xsl:if test="not(self::c)">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="b/c"/>
</xsl:stylesheet>

Note: One rule but with some xsl:if.

Both output:

<A>
    <b attr1="xx" cAttr="Turn this into an Attribute"></b>
</A>

Edit: Oops! I've missed the stripping rule.

0

Not as nice as Gleb's version, but why not post it if I've already wasted my time= ;)

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

<!-- assemble b element -->
<xsl:template match="b">
    <b>
        <xsl:attribute name="attr1">
            <xsl:value-of select="@attr1" />
        </xsl:attribute>
        <xsl:attribute name="cAttr">
            <xsl:value-of select="c" />
        </xsl:attribute>
    </b>
</xsl:template>

<!-- ignore c element -->
<xsl:template match="c" />
DanMan
  • 11,323
  • 4
  • 40
  • 61
  • Yeah, like I said it's not very good. It works just for this specific case. I'm still a freshman when it comes to XSL. :| – DanMan Feb 22 '11 at 20:41
0

I prefer a more concise (though less generic may be) design:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    <xsl:template match="node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="b">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:attribute name="cAttr">
                <xsl:value-of select="normalize-space(c)"/>
            </xsl:attribute>
            <xsl:apply-templates select="*[not(self::c)]"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Applied to XML like this:

<A>
    <b attr1="xx">
        <d>SomeNode</d>
        <c>
            Turn this into an Attribute
        </c>
        <f>SomeMoreNode</f>
    </b>
</A>

Result will be:

<A>
    <b attr1="xx" cAttr="Turn this into an Attribute">
        <d>SomeNode</d>
        <f>SomeMoreNode</f>
    </b>
</A>
Flack
  • 5,862
  • 2
  • 23
  • 27
  • +1 This is a better and more semantically correct answer, besides that I would apply templates to `c` just in case there is no one. Or use a filter in the pattern for `b` like `b[c]`. –  Feb 22 '11 at 23:18