How to copy specific attribute using 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 j开发者_如何学Pythonust 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.
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.
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:
The identity rule (template) copies every node "as-is".
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.
I think that it becomes easier for you if you don't use xsl:copy
here. Try this:
<xsl:template match="Groups/*">
<xsl:element name="Group">
<xsl:attribute name="Name">
<xsl:value-of select="@Name" />
</xsl:attribute>
</xsl:element>
</xsl:template>
Answer to second question is a FAQ. Declare the namespace and use the declared prefix in your xsl. Final solution:
<xsl:stylesheet version="1.0"
xmlns:myns="http://schemas.microsoft.com/sharepoint/soap/director/"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="/myns:GetGroupCollectionFromUser">
<Groups>
<xsl:apply-templates select="myns:Groups" />
</Groups>
</xsl:template>
<xsl:template match="myns:Groups/*">
<xsl:element name="Group">
<xsl:attribute name="Name">
<xsl:value-of select="@Name" />
</xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This seems to work for me:
<xsl:template match="Group">
<xsl:copy>
<xsl:attribute name="Name">
<xsl:apply-templates select="@Name" />
</xsl:attribute>
</xsl:copy>
</xsl:template>
精彩评论