75970

How to traverse a nested XML structure using templates

Question:

I am new to XSL and in the process of picking this up from scratch to solve a problem.

I have a source XML file that contains the following structure:-

<root> <Header> </Header> <DetailRecord> <CustomerNumber>1</CustomerNumber> <DetailSubRecord> <Address>London</Address> </DetailSubRecord> <DetailSubRecord> <Address>Hull</Address> </DetailSubRecord> </DetailRecord> <DetailRecord> <CustomerNumber>2</CustomerNumber> <DetailSubRecord> <Address>Birmingham</Address> </DetailSubRecord> <DetailSubRecord> <Address>Manchester</Address> </DetailSubRecord> </DetailRecord> <Footer> </Footer> </root>

where there are mutiple <DetailRecord>s each with multiple <DetailSubRecord>s.

I have managed to put together an XSL that outputs a single nested set of multiple DetailRecords to a flat file but I haven't been able to puzzle out how to refer to the 2nd nested level of address records in the XSL...

Here is my XSL so far:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <xsl:output method="text"/> <xsl:variable name="spaces" select="' '"/> <xsl:variable name="newline"> <xsl:text>&#10;</xsl:text> </xsl:variable> <xsl:template match="/"> <xsl:value-of select="root/Header/HeaderField"/> <xsl:copy-of select="$newline"/> <xsl:for-each select="root/DetailRecord"> <xsl:value-of select="CustomerNumber"/> <xsl:copy-of select="$newline"/> </xsl:for-each> Trailer - recordCount - <xsl:value-of select="count(root/DetailRecord)"/> </xsl:template> </xsl:stylesheet>

Answer1:

XSLT is a functional language, not a procedural; what most newcomers to XSLT don't realise is that the XSLT processor automatically handles each node in the tree, in the order they appear in the source. Without a template to define what to do with each node however, nothing is output.

In most cases, you don't need to use <xsl:for-each> just to get the child elements processed, this is already done for you, you just need to define a template that describes how you want each element to be output. Like this:

<xsl:template match="root"> <xsl:apply-templates /> <xsl:text>Trailer - recordCount - </xsl:text> <xsl:value-of select="count(DetailRecord)" /> </xsl:template> <xsl:template match="HeaderField | CustomerNumber | Address"> <xsl:value-of select="concat(.,$newline)" /> </xsl:template> <xsl:template match="DetailSubRecord"> <!-- do something with subrecord here --> <xsl:apply-templates /> </xsl:template>

The <xsl:apply-templates /> in the first template just tells the XSLT processor to deal with the child elements, after which it adds in the record count.

The second template handles any element with the three names in it's match atrtibute, and in each case outputs the content (.) concatenated with a new line.

The third template in it's current form is actually superfluous, the processor will do that anyway, but you can replace that comment with something more useful.

You'll notice this doesn't give any information on how to handle a DetailRecord element; because all you want to do is process it's children, you don't need to specify anything, as that's taken as a given.

Answer2:

Here you have a simple example on how to (literally) apply templates to your situation. Because you wasnt so clear about the required output, I've invented one.

<hr />

<strong>XSLT 1.0</strong> tested under <strong>Saxon 6.5.5</strong>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:strip-space elements="*"/> <xsl:output method="text"/> <xsl:variable name="spaces" select="' '"/> <xsl:variable name="nl"> <xsl:text>&#10;</xsl:text> </xsl:variable> <xsl:template match="/root"> <xsl:apply-templates select="DetailRecord"/> <xsl:apply-templates select="Footer"/> </xsl:template> <xsl:template match="DetailRecord"> <xsl:value-of select="concat( 'Customer ', CustomerNumber, $nl)" /> <xsl:apply-templates select="DetailSubRecord"/> <xsl:value-of select="concat( 'Address Count:', count(DetailSubRecord),$nl,$nl )" /> </xsl:template> <xsl:template match="DetailSubRecord"> <xsl:value-of select="concat('-',Address,$nl)"/> </xsl:template> <xsl:template match="Footer"> <xsl:value-of select="concat( 'Customer Count:', count(preceding-sibling::DetailRecord),$nl )" /> </xsl:template> </xsl:stylesheet>

Applied on your input gets:

Customer 1 -London -Hull Address Count:2 Customer 2 -Birmingham -Manchester Address Count:2 Customer Count:2

Recommend

  • Can we solve N Queens without backtracking? and How to calculate and what will be the complexity of
  • How to best apply an MVC architecture in dojo mobile (custom controllers)?
  • Reading file from C drive from Android Emulator
  • How to combine two lists together?
  • NetBeans doesn't see style.css [duplicate]
  • How to add regEx in angular filter
  • Feature detection of foreignObject in SVG
  • XSD with multi occurrences unordered
  • Splash Screen will not display
  • SonarQube: Cannot deactivate rule with missing quality profile
  • How to add a focus style to an editable ComboBox in WPF
  • Diff between two dataframes in pandas
  • JBoss External Properties Files in Classpath
  • Adjust width of select element according to selected option's width
  • Android - Material Design - NavigationView - How to put vertical scroll?
  • Jetty 9 HashLoginService
  • Bad request using file_get_contents for PUT request in PHP
  • Jquery UI tool tip close icon
  • Refering to the class itself from within a class mehod in Objective C
  • Read a local file using javascript
  • ImageMagick, replace semi-transparent white with opaque white
  • Seeking advice on Jetty HttpClient Hang
  • When to use `image` and when to use `Matrix` in Emgu CV?
  • does jqgrid support a multiple checkbox list for editing
  • Cannot connect to cassandra from Spark
  • Pass value from viewmodel to script in zk
  • Uncaught Error: Could not find module `ember-load-initializers`
  • Optimizing database types to compact database (SQLite)
  • Cross-Platform Protobuf Serialization
  • SSO with signing and signature validation doesn't work
  • Deserializing XML into class C#
  • Alternatives to the OPTIONAL fallback SPARQL pattern?
  • Do I've to free mysql result after storing it?
  • bootstrap to use multiple ng-app
  • How to get icons for entities from eclipse?
  • How to include full .NET prerequisite for Wix Burn installer
  • Turn off referential integrity in Derby? is it possible?
  • JaxB to read class hierarchy
  • costura.fody for a dll that references another dll
  • jQuery Masonry / Isotope and fluid images: Momentary overlap on window resize