Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XSLT

Applying XSL Transformations

5.00/5 (1 vote)
31 Mar 2010CPOL1 min read 1  
Setup: A data from one module is passed into another so that Module 1 takes a (flat) file as input and feeds an xml structure into Module 2. While Module 2 is internal, Module 1 is designed to handle different customer inputs into the system. However, it is not always possible to generate the...
Setup:
A data from one module is passed into another so that Module 1 takes a (flat) file as input and feeds an xml structure into Module 2. While Module 2 is internal, Module 1 is designed to handle different customer inputs into the system. However, it is not always possible to generate the expected Module 2 xml structure from the flat file in one step.

|Customer| -> file -> |Module 1| -> xml(products-product-detail) -> |Module 2|

There are at least two ways to accomodate the (multi-step) translation process:
  • Add re-parsing logic directly in the code - not smart enough as we do not know what input formats will need to be supported by Module 1 in the future
  • Apply customizable xsl transformations - providing decoupled solution with a reliable techology


For example:

Given a list of details, we can group them into product(s) in order to conform to the expected Module 2 hierarchy.

List of details:
XML
<?xml version='1.0' encoding="iso-8859-1"?>
<PRODUCTS>
	<DETAIL>
		<DESC>D1</DESC>
		<PRODUCT_NAME>APL</PRODUCT_NAME>
	</DETAIL>
	<DETAIL>	
		<DESC>D2</DESC>
		<PRODUCT_NAME>ANL</PRODUCT_NAME>
	</DETAIL>
	<DETAIL>
		<DESC>D3</DESC>
		<PRODUCT_NAME>APL</PRODUCT_NAME>
	</DETAIL>
</PRODUCTS> 


List of details grouped by product:
XML
<?xml version='1.0' encoding="iso-8859-1"?>
<PRODUCTS>
	<PRODUCT>
		<PRODUCT_NAME>ANL</PRODUCT_NAME>
		<DETAIL>	
			<DESC>D2</DESC>
			<PRODUCT_NAME>ANL</PRODUCT_NAME>
		</DETAIL>
	</PRODUCT>
	<PRODUCT>
		<PRODUCT_NAME>APL</PRODUCT_NAME>
		<DETAIL>
			<DESC>D1</DESC>
			<PRODUCT_NAME>APL</PRODUCT_NAME>
		</DETAIL>
		<DETAIL>
			<DESC>D3</DESC>
			<PRODUCT_NAME>APL</PRODUCT_NAME>
		</DETAIL>
	</PRODUCT>
</PRODUCTS> 


:thumbsup: In order to achieve this grouping, we can use the Muenchian method as described here:

http://www.jenitennison.com/xslt/grouping/muenchian.html[^]
XML
<!-- GROUPING USING THE MUENCHIAN METHOD: http://www.jenitennison.com/xslt/grouping/muenchian.html -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="xml" indent="yes" encoding="iso-8859-1"/>

	<xsl:key name="details-by-product" match="DETAIL" use="PRODUCT_NAME" />

	<xsl:template match="PRODUCTS">
		<PRODUCTS>
		<xsl:for-each select="DETAIL[count(. | key('details-by-product', PRODUCT_NAME)[1]) = 1]">
			<xsl:sort select="PRODUCT_NAME" />
			<PRODUCT>
				<PRODUCT_NAME><xsl:value-of select="PRODUCT_NAME"/></PRODUCT_NAME>
				<xsl:for-each select="key('details-by-product', PRODUCT_NAME)">
					<DETAIL>
					<xsl:apply-templates/>
					</DETAIL>
				</xsl:for-each>
			</PRODUCT>
		</xsl:for-each>
		</PRODUCTS>
	</xsl:template>

	<xsl:template match="@*|node()">
	  <xsl:copy>
		<xsl:apply-templates select="@*|node()"/>
	  </xsl:copy>
	</xsl:template>
</xsl:stylesheet>

See also:


    XSL Transformations (XSLT):  http://www.w3.org/TR/xslt[^]
    Grouping With XSLT 2.0:  http://www.xml.com/pub/a/2003/11/05/tr.html[^]
    Dave Pawson's Grouping page:  http://www.dpawson.co.uk/xsl/sect2/N4486.html[^]


:thumbsup: In order to test the transformation, we can initiate the xslt in a script as described here:

http://msdn.microsoft.com/en-us/library/ms762796(VS.85).aspx[^]
//Initiate XSLT in a script: http://msdn.microsoft.com/en-us/library/ms762796(VS.85).aspx
var oArgs = WScript.Arguments;

if (oArgs.length == 0)
{
    WScript.Echo ("Usage : cscript xslt.js xml xsl");
    WScript.Quit();
}
xmlFile = oArgs(0) + ".xml";
xslFile = oArgs(1) + ".xsl";

var xsl = new ActiveXObject("MSXML2.DOMDOCUMENT.6.0");
var xml = new ActiveXObject("MSXML2.DOMDocument.6.0");
xml.validateOnParse = false;
xml.async = false;
xml.load(xmlFile);

if (xml.parseError.errorCode != 0)
    WScript.Echo ("XML Parse Error : " + xml.parseError.reason);

xsl.async = false;
xsl.load(xslFile);

if (xsl.parseError.errorCode != 0)
    WScript.Echo ("XSL Parse Error : " + xsl.parseError.reason);

try
{
    WScript.Echo (xml.transformNode(xsl.documentElement));
}
catch(err)
{
    WScript.Echo ("Transformation Error : " + err.number + "*" + err.description);
}


:thumbsup: In order to apply the transformation at run-time, we can use the XslCompiledTransform as described here:

http://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform_members.aspx[^]
C#
// XslCompiledTransform: http://msdn.microsoft.com/en-us/library/system.xml.xsl.xslcompiledtransform_members.aspx
// Note: Exception handling is outside of the scope of this tip
// Note: Xml results are loaded into memory and do not have to be written into the file system, if there is no need for it.
private string ApplyStylesheet(string xml, string stylesheet)
{
	if (!string.IsNullOrEmpty(stylesheet))
	{
		//Configure the reader to skip all forms of validation
		XmlReaderSettings settingsReader = new XmlReaderSettings();
		settingsReader.ValidationType = ValidationType.None;
		settingsReader.ProhibitDtd = false;
		settingsReader.XmlResolver = null;

		StringReader strReader = new StringReader(xml);
		XmlReader xmlReader = XmlReader.Create(strReader, settingsReader);
		// use XmlNodeReader xmlReader = new XmlNodeReader(xml); if xml is XmlNode (not a string)

		StringWriter stringWriter = new StringWriter();
		XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);

		XslCompiledTransform xslt = new XslCompiledTransform();
		xslt.Load(stylesheet);
		xslt.Transform(xmlReader, xmlWriter);

		xmlWriter.Close();
		xmlReader.Close();

		return stringWriter.ToString();
	}
	return xml;
}


Good luck!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)