Zoran Kalinić
Zoran Kalinić

Reputation: 317

XSL adding attribute to parent based on child

I started project in php, using XML, and now I need to apply transformation, so I've discovered XSL for the first time...

I've faced the problem: how to create XSL to do following transformation: - node that starts with "attrib-" to transform to attribute of the parent node

Example:

<a1>
  <b1>
    <attrib-c1>12</attrib-c1>
    <c2>23</c2>
  </b1>
</a1>

should become:

<a1>
  <b1 c1="12">
    <c2>23</c2>
  </b1>
</a1>

I've started solution like this:

<xsl:stylesheet>
  <xsl:output method="xml"/>
  <xsl:template match="@*|*|text()">
    <xsl:copy>
      <xsl:apply-templates select="@*|*|text()"/>
    </xsl:copy>
  </xsl:template>
...
</xsl:stylesheet>

I would need some help to solve this task. Thanks in advance...

Upvotes: 2

Views: 2443

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

This is almost the same solution as that of DevNull, but in case there is a conflict between an existing attribute and a new one defined by a child-element, the latter replaces the former:

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

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

 <xsl:template match="*">
  <xsl:copy>
   <xsl:apply-templates select="@*"/>
   <xsl:apply-templates mode="attr" select="*[starts-with(name(), 'attrib-')]"/>
   <xsl:apply-templates/>
  </xsl:copy>
 </xsl:template>

 <xsl:template mode="attr" match="*[starts-with(name(), 'attrib-')]">
  <xsl:attribute name="{substring-after(name(), 'attrib-')}">
    <xsl:value-of select="."/>
  </xsl:attribute>
 </xsl:template>

 <xsl:template match="*[starts-with(name(), 'attrib-')]"/>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<a1>
  <b1 existing="attr" x="Y">
    <attrib-new>12</attrib-new>
    <c2>23</c2>
    <attrib-new2>ABCD</attrib-new2>
    <attrib-x>Z</attrib-x>
  </b1>
</a1>

the wanted, correct result is produced:

<a1>
   <b1 existing="attr" x="Z" new="12" new2="ABCD">
      <c2>23</c2>
   </b1>
</a1>

Upvotes: 1

Daniel Haley
Daniel Haley

Reputation: 52858

XML Input (Modified to slightly increase complexity.)

<a1>
  <b1 existing="attr">
    <attrib-c1>12</attrib-c1>
    <c2>23</c2>
    <attrib-dh>DevNull</attrib-dh>
  </b1>
</a1>

XSLT 1.0

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

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

  <xsl:template match="*">
    <xsl:copy>
      <xsl:apply-templates select="*[starts-with(name(),'attrib')]" mode="attr"/>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*[starts-with(name(),'attrib')]"/>

  <xsl:template match="*" mode="attr">
    <xsl:attribute name="{substring-after(name(),'-')}">
      <xsl:value-of select="."/>
    </xsl:attribute>
  </xsl:template>  

</xsl:stylesheet>

XML Output

<a1>
   <b1 c1="12" dh="DevNull" existing="attr">
      <c2>23</c2>
   </b1>
</a1>

Upvotes: 3

Emiliano Poggi
Emiliano Poggi

Reputation: 24816

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

    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <!-- match elements with children like attrib- and copy/pass -->
    <xsl:template match="*[*[starts-with(name(.),'attrib')]]">
        <xsl:copy>
         <xsl:apply-templates select="*[starts-with(name(.),'attrib')]"/>
         <xsl:apply-templates select="@*|*[not(starts-with(name(.),'attrib'))]"/>
        </xsl:copy>
    </xsl:template>

    <!-- match elements like attrib- and output them as attribute -->
    <xsl:template match="*[starts-with(name(.),'attrib')]">
        <xsl:attribute name="{substring-after(name(.),'attrib-')}">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions