Reputation: 601
I have a scenario where the XML doc contains some nodes that are repeating. I want to get rid of all such nodes. Please note that this is not "Removing Duplicates". I want to completely remove all entries of those nodes that are occuring more than once.
Ex My XML
<ReadUserOBSResponse>
<UserOBS>
<OBSObjectId>1510</OBSObjectId>
<UserObjectId>443</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>540</OBSObjectId>
<UserObjectId>514</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>1521</OBSObjectId>
<UserObjectId>514</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>547</OBSObjectId>
<UserObjectId>544</UserObjectId>
</UserOBS>
</ReadUserOBSResponse>
Desired Output : I want to remove both entries with UserObjectId 514
<ReadUserOBSResponse>
<UserOBS>
<OBSObjectId>1510</OBSObjectId>
<UserObjectId>443</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>547</OBSObjectId>
<UserObjectId>544</UserObjectId>
</UserOBS>
</ReadUserOBSResponse>
I've done some things, but its not working. My Idea was to count the nodes with UserObjectId as the current value, put this in an xsl:if
and then print the nodes.
But I'm not sure how to write this snippet.
Upvotes: 1
Views: 501
Reputation: 163625
Much simpler in XSLT 2.0:
<xsl:for-each-group select="UserObs" group-by="UserObjectId">
<xsl:copy-of select="current-group()[last()=1]"/>
</xsl:for-each-group>
Upvotes: 1
Reputation: 101758
Here is a very efficient (and concise) approach that uses keys:
<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:strip-space elements="*" />
<xsl:key name="kUOId" match="UserOBS" use="UserObjectId" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserOBS[key('kUOId', UserObjectId)[2]]" />
</xsl:stylesheet>
When run on your sample input, the result is:
<ReadUserOBSResponse>
<UserOBS>
<OBSObjectId>1510</OBSObjectId>
<UserObjectId>443</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>547</OBSObjectId>
<UserObjectId>544</UserObjectId>
</UserOBS>
</ReadUserOBSResponse>
Upvotes: 2
Reputation: 126762
This will do what you need.
It has a template for UserOBS
elements that checks whether there is exactly one UserOBS
child of its parent that has the same value for UserObjectId
. If so the entire node is copied to the output.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/ReadUserOBSResponse">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserOBS">
<xsl:if test="count(../UserOBS[UserObjectId = current()/UserObjectId]) = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<ReadUserOBSResponse>
<UserOBS>
<OBSObjectId>1510</OBSObjectId>
<UserObjectId>443</UserObjectId>
</UserOBS>
<UserOBS>
<OBSObjectId>547</OBSObjectId>
<UserObjectId>544</UserObjectId>
</UserOBS>
</ReadUserOBSResponse>
Upvotes: 2