Ravi
Ravi

Reputation: 601

Get only elements that are not repeating in an XML

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

Answers (3)

Michael Kay
Michael Kay

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

JLRishe
JLRishe

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

Borodin
Borodin

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

Related Questions