tags:

views:

166

answers:

3

I have an xml like this:

<person name="foo" gender = "male" />

I want to transform it to

<person id="foo" gender="male" />

Is there a way to do that using XSLT?

  • I will have a lot of child nodes in person

  • I will have more attributes in the person.

A: 

This should do it, not quite sure of the {name()} but you could replace that with "person"

> <xsl:template match="person">
>       <xsl:element name="{name()}">
>         <xsl:attribute name="id">
>           <xsl:value-of select="@name"/>
>         </xsl:attribute>
>         <xsl:attribute name="gender">
>           <xsl:value-of select="@gender"/>
>         </xsl:attribute>
>       </xsl:element>
>     </xsl:template>
derek
Do I need to create an attribute for every attribute that I have? Can I just copy all the attributes excluding the id?
kunjaan
It's crazy to use `xsl:element`, an attribute value template, and the `name()` function to create an element whose name you already know. Replace `<xsl:element>` with `<person>` and you can also replace the `xsl:attribute` elements with AVTs. Do all that and the answer still won't be right (the answer to kunjaan's question is "no, unless you do it like this"), but it will be less wrong.
Robert Rossney
Just trying to provide a generic solution that would work for any element regardless of name. If it will only ever be for the <person> element, then of course the simpler hard-coded solution would be better.
derek
If you want to avoid hardcoding the name, use `<xsl:copy>` instead of `<xsl:element name="{name()}">`. Also, your solution strips away other attributes and any child nodes, while the question asks for these to be preserved.
markusk
@markusk: The question didn't *originally* say that other attributes and child nodes would be present at all. That bit was added some 5 hours after the original question was posted.
Greg Hewgill
A: 

Here's a template you can use that you won't have to create a new attribute for every existing attribute. What you're doing is creating the "id" attribute and then outputting all the attributes except for "name".

The extra "apply-templates" is for any children that <person> might have. Remove it if you don't want to keep the children. (That sounds horrible, doesn't it? ;-)

I also included the identity transform template.

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

   <xsl:template match="person">
      <person id="{@name}">
         <xsl:apply-templates select="@*[name() != 'name']"/>
         <xsl:apply-templates/>
      </person>
   </xsl:template>
DevNull
This will work, but it's more complicated than it needs to be.
Robert Rossney
Slightly more complicated than your answer, but also more specific. It will all depend on what the OP needs. Yours is good answer and explanation btw.
DevNull
+2  A: 

This is very simple: Use the identity transform and create a template that transforms the name attribute:

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

<xsl:template match="@name">
   <xsl:attribute name="id">
      <xsl:value-of select="."/>
   </xsl:attribute>
</xsl:template>

This will leave everything in the document except for name attributes exactly as it is. If you only want to change the name attribute on person elements, put a more restrictive XPath in the template's match attribute, e.g. person/@name.

Robert Rossney
Short, clean, simple. Excellent answer.
markusk
Worked beautifully.
kunjaan