Reputation: 401
I am completely lost trying to pick up XLST for a one-off project (parsing downloaded Salesforce schema information for inter-table relationships in a proprietary scripting language that basically just gives me XPATH, XSLT, and while loops -- and I've never heard of XSLT until today ... it just looks promising for the 2nd half of my filter needs).
I have input XML that looks like this:
<result>
<custom>True</custom>
<createable>False</createable>
<fields>
<createdBy>Joe</createdBy>
<name>field1</name>
<referenceTo>otherPlaceXYZ</referenceTo>
<type>reference</type>
</fields>
<fields>
<createdBy>Joe</createdBy>
<name>field2</name>
</fields>
<fields>
<createdBy>Joe</createdBy>
<name>field3</name>
<referenceTo>otherPlaceABC</referenceTo>
<type>reference</type>
</fields>
<name>CoolName</name>
<label>A label</label>
<searchable>False</searchable>
</result>
I want to do 2 types of filter:
<fields>...</fields>
nodes that don't have a <type>reference</type>
subnode<result>...</result>
that aren't <fields>...</fields>
, <name>...</name>
, or <label>...</label>
, as well as throwing away any children of <fields>...</fields>
that aren't <name>...</name>
or <referenceTo>...</referenceTo>
(although I wouldn't be opposed to leaving in <type>...</type>
if it makes the code simpler). Note that I've actually got 5 or 6 fields I want to keep within "fields" (out of dozens in the file) but keeping this short & sweet in the example. Likewise, there are dozens of children of "result" that I don't actually care about.I'd like my output data to look like this:
<result>
<fields>
<name>field1</name>
<referenceTo>otherPlaceXYZ</referenceTo>
</fields>
<fields>
<name>field3</name>
<referenceTo>otherPlaceABC</referenceTo>
</fields>
<name>CoolName</name>
<label>A label</label>
</result>
I've been playing around at http://www.utilities-online.info/xsltransformation/ but am getting pretty stuck. Documentation about XSLT seems to mostly be oriented at doing far more complicated data transformations than "same XML file, only smaller," so I'm struggling to find the simplest way to get this job done.
Any pointers?
I got as far as this, thanks to deleting the parent node if child node is not present in xml using xslt , but I'm not sure I'm even barking up the right tree considering my final desire for output.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!--Identity template to copy all content by default-->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="fields[not(type='reference')]"/>
</xsl:stylesheet>
Upvotes: 0
Views: 951
Reputation: 29042
You can do this by deactivating one of the built-in templates and creating a white-list of the nodes to copy:
The built-in template to deactivate is described here at O'Reilly:
Built-in template rule for element and document nodes
This template processes the document node and any of its children. This processing ensures that recursive processing will continue, even if no template is declared for a given element:
<xsl:template match="*|/"> <xsl:apply-templates/> </xsl:template>
So your XSLT-1.0 stylesheet could look like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*" />
<!-- Deactivate built-in template for elements -->
<xsl:template match="*" />
<!-- Apply the white list of nodes to copy -->
<xsl:template match="/ | fields[type/text() = 'reference'] | result | result/name | result/label | fields/name | fields/referenceTo | @* | text()">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For example, result/name
selects only the name
s that are children of result
.
The output is:
<?xml version="1.0"?>
<result>
<fields>
<name>field1</name>
<referenceTo>otherPlaceXYZ</referenceTo>
</fields>
<fields>
<name>field3</name>
<referenceTo>otherPlaceABC</referenceTo>
</fields>
<name>CoolName</name>
<label>A label</label>
</result>
Upvotes: 1
Reputation: 117043
My preference would be to use xsl:apply-templates
to do the filtering:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/result">
<xsl:copy>
<xsl:apply-templates select="fields[type='reference']|name|label" />
</xsl:copy>
</xsl:template>
<xsl:template match="fields">
<xsl:copy>
<xsl:apply-templates select="name|referenceTo" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1