Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to copy specific attribute using XSLT?

Tags:

xml

xslt

Apologies for the basic nature of these questions - I'm brand new to XSLT (and to Stack Overflow too).

I need to transform the following XML being returned by a Sharepoint Web service:

<GetGroupCollectionFromUser xmlns=
   "http://schemas.microsoft.com/sharepoint/soap/directory/">
   <Groups>
      <Group ID="3" Name="Group1" Description="Description" OwnerID="1" 
         OwnerIsUser="False" />
      <Group ID="15" Name="Group2" Description="Description" 
         OwnerID="12" OwnerIsUser="True" />
      <Group ID="16" Name="Group3" Description="Description" 
         OwnerID="7" OwnerIsUser="False" />
   </Groups>
</GetGroupCollectionFromUser>

into this:

<GetGroupCollectionFromUser xmlns=
   "http://schemas.microsoft.com/sharepoint/soap/directory/">
   <Groups>
      <Group Name="Group1" />
      <Group Name="Group2" />
      <Group Name="Group3" />
   </Groups>
</GetGroupCollectionFromUser>

Basically, I need to drop all the attributes for each Group element except Name. After a lot of research and tinkering, and notably dropping the namespace declaration from the original XML, I've come up with something that gets me almost exactly what I need:

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

<xsl:output method="xml" encoding="utf-8" indent="no"/>

<xsl:template match="/GetGroupCollectionFromUser">
    <xsl:copy>                  
        <xsl:apply-templates select="Groups" />
    </xsl:copy>
</xsl:template>

<xsl:template match="Groups">
    <xsl:copy>
        <xsl:apply-templates select="Group" />
    </xsl:copy>
</xsl:template> 

<xsl:template match="Group">
    <xsl:copy>
        <xsl:apply-templates select="@Name" />
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

However, instead of a Name attribute, the above gives me the value of the Name attribute inserted as text into the Group element, like so:

<GetGroupCollectionFromUser>
    <Groups>
        <Group>Group1</Group>
        <Group>Group2</Group>
        <Group>Group3</Group>
    </Groups>
</GetGroupCollectionFromUser>

This will ultimately be consumed by a third-party application that's expecting attribute-centric XML. I'm sure I'm missing something embarrassingly obvious, but no matter what I do with it I can't seem to pull in just the Name attribute. Two questions:

How do I alter the XSLT to return a Name attribute for each Group element, instead of its value as text?

And, how do I handle the namespace properly? When I've included it in the XSLT, trying several methods based on examples I've found here and elsewhere on the web, I get nothing at all back.

Thanks in advance for any advice.

like image 714
rlg Avatar asked May 09 '11 19:05

rlg


2 Answers

The way to handle these situations is by overriding the Identity Transform:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msft="http://schemas.microsoft.com/sharepoint/soap/directory/">
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="msft:Group">
        <xsl:copy>
            <xsl:apply-templates select="@Name" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Output:

<GetGroupCollectionFromUser
    xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/">
    <Groups>
        <Group Name="Group1" />
        <Group Name="Group2" />
        <Group Name="Group3" />
    </Groups>
</GetGroupCollectionFromUser>

Note that the output is properly namespaced.

This solution is preferred because of its simplicity and flexibility. All nodes and attributes are copied as-is, unless an override exists that specifies an alternate behavior.

like image 66
Wayne Avatar answered Oct 22 '22 11:10

Wayne


This is possibly one of the shortest transformations that correctly produces the wanted result:

<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="@*[not(name()='Name')]"/>
</xsl:stylesheet>

When applied on the provided XML document:

<GetGroupCollectionFromUser xmlns=
"http://schemas.microsoft.com/sharepoint/soap/directory/">
    <Groups>
        <Group ID="3" Name="Group1" Description="Description" OwnerID="1"           OwnerIsUser="False" />
        <Group ID="15" Name="Group2" Description="Description"           OwnerID="12" OwnerIsUser="True" />
        <Group ID="16" Name="Group3" Description="Description"           OwnerID="7" OwnerIsUser="False" />
    </Groups>
</GetGroupCollectionFromUser>

the wanted, correct result is produced:

<GetGroupCollectionFromUser xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/">
   <Groups>
      <Group Name="Group1"/>
      <Group Name="Group2"/>
      <Group Name="Group3"/>
   </Groups>
</GetGroupCollectionFromUser>

Explanation:

  1. The identity rule (template) copies every node "as-is".

  2. There is just one template that overrides the identity rule. It matches any attribute whose name is not "Name". The body of the template is empty and this results in any matched attribute not copied.

Using and overriding the identity rule is the most fundamental and powerful XSLT design pattern. Read about it here.

like image 23
Dimitre Novatchev Avatar answered Oct 22 '22 12:10

Dimitre Novatchev