Reputation: 1550
I have a source XML document which looks somthing like this:
<root>
<users>
<user id="1" name="user 1" />
<user id="2" name="user 2" />
<user id="3" name="user 3" />
</users>
<posts>
<post>
<user>1</user>
<text>First sample post!</text>
<status>DELETED</status>
</post>
<post>
<user>2</user>
<text>Second sample post!</text>
<status>ACTIVE</status>
</post>
<post>
<user>3</user>
<text>Third sample post!</text>
<status>DELETED</status>
</post>
</posts>
</root>
I need to filter the users so that the target document contains only ACTIVE posts and those users refered to in the post element.:
<root>
<users>
<user id="2" name="user 2" />
</users>
<posts>
<post>
<user>2</user>
<text>Second sample post!</text>
</post>
</posts>
</root>
I don't have access to change the source document and I'm required to make this possible using XSLT (which I'm very new to).
I can filter the posts easily but I'm not sure how to build up the list of users.
Before I go too much futher I'd like to check if this is possible.
Cheers
Upvotes: 0
Views: 46
Reputation: 70618
First you should learn about the Identity Template
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
On its own it will copy across all nodes exactly from the source document.
This means, rather than thinking in terms of what you need to copy, think in terms what you don't need to copy. This is achieved by adding templates, with higher priority, that override the identity template.
You don't want post
elements where status
is not "ACTIVE"? Just have an empty template to stop them being copied.
<xsl:template match="post[status!='ACTIVE']" />
Similarly, for removing the status
node itself (for the posts it does copy)
<xsl:template match="status" />
For your user
elements, consider using an xsl:key
to look up post
elements
<xsl:key name="posts" match="post" use="user" />
Then, your template to ignore users would be this....
<xsl:template match="user[key('posts', @id)/status!='ACTIVE']" />
Put this all together gives you this...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="posts" match="post" use="user" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="post[status!='ACTIVE']" />
<xsl:template match="status" />
<xsl:template match="user[key('posts', @id)/status!='ACTIVE']" />
</xsl:stylesheet>
Upvotes: 1
Reputation: 159114
Yes, it is possible, with a stylesheet like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Copy asnything not overridden below -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Copy <user> if active <post> exists -->
<xsl:template match="users/user"><!-- don't match 'post/user' -->
<xsl:variable name="userId" select="@id"/>
<xsl:if test="../../posts/post[user = $userId][status = 'ACTIVE']">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Copy <post> if active -->
<xsl:template match="post">
<xsl:if test="status = 'ACTIVE'">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Don't copy <status> -->
<xsl:template match="status">
</xsl:template>
</xsl:stylesheet>
Test
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(new File("test.xslt")));
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new StreamSource(new File("test.xml")),
new StreamResult(System.out));
Output
<root>
<users>
<user id="2" name="user 2"/>
</users>
<posts>
<post>
<user>2</user>
<text>Second sample post!</text>
</post>
</posts>
</root>
Upvotes: 1