|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSo you've been reading up on XML a little. You understand the basics of XML documents, XSL transformations, and maybe you even have a fuzzy sort of "schema awareness". The only catch is that now you are all read up, you're still not really sure what you can do with it. If I am describing you then you might find this article useful. I have written it as a solution to a problem posed by a friend and I am treating it as a useful exercise to find out how I can better utilize XML in my distinctly average programming solutions. There is nothing particularly cutting-edge here....just an attempt to provide a simple XSL example. I have tried to spell everything out very carefully so that this can be used as a tutorial but I am also aware that in doing so, I am also exposing the shoddy parts of code, so any suggestions for improvement will be greatly welcome. In particular, there is only very blunt error checking present. BackgroundThe problem is a simple one. You would like to produce a number of very similar web-pages. Each page consists of a title and some links to relevant articles for that title. In addition, each page contains a drop-down box which redirects you to the other available pages.
In my friend's problem, he wanted a static page for every book of the Bible, complete with links to relevant articles. Of course, you could just code 66 static web-pages. The problems with this approach are well known. Not only is it dull to produce but you have the problem of ensuring consistent style across each page. If you want to make a change later on, you have to change it on 66 different pages. Let's put our XML knowledge to good use instead.
The XML Links FileWe start off with a single XML file that will contain the relevant links for every topic. This way, we reduce the 66 files to 1 file. Here is a snippet from this file (XMLLinks.xml in the download). <?xml version="1.0" encoding="utf-8" ?>
<links>
<link id="Ruth">
<block>
<![CDATA[
<p style="margin-left: 26">
<font face="Arial">
<strong>
Ruth obeys God and finds Love (Ruth)
</strong>
</font>
...
]]>
</block>
<block>
<![CDATA[
<p style="margin-left: 26">
<font face="Arial">
<strong>
There is a Redeemer (Ruth)
</strong>
</font>
download as ...
</p>
]]>
</block>
</link>
<link id="Mark">
<block>
<![CDATA[
...
]]>
</block>
...
</link>
</links>
Each link element represents a choice in the drop-down box and each block element is a block of HTML that will be copied to the relevant static web-page on processing. Now, I must immediately defend myself here from the charge of ruining the XML concept by introducing HTML, hidden in The XSL FileOK, so now we need a stylesheet which will take the XMLLinks.xml file and process it to spit out the 66 static pages. And here comes the rub. If we were using an XSLT engine which supports the use of The basic idea behind parameters for stylesheets is this: you define the parameters at the top of the stylesheet, and when you run the transformation, you provide their values. These parameters can then be used in the transformation to determine the action of the stylesheet. Here is the start of the stylesheet (XMLLinks.xsl in the download): <?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="book"/>
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
...
Notice the declaration of the How is this parameter used then? Let's have a look at another excerpt from the stylesheet: ...
<div style="text-align:left;">
<p>
<font color="#008000" size="7" face="Arial">
<b><xsl:value-of select="$book"/></b>
<br />
</font>
</p>
<p style="font-family: verdana;">Results:</p>
<xsl:for-each select="/links/link[@id=$book]/block">
<xsl:value-of disable-output-escaping="yes" select="."/>
</xsl:for-each>
</div>
...
Here is the time to apologize for the inconsistent use of both old tag based formatting and CSS style formatting. I was copying blocks of code from the original website in question and then inserting bits of my own without refactoring it all. Never a good idea. So we can see the The drop-down select element at the top of the page is produced using: <select name="selectBook" onChange="openURL();" size="1" style="width:150px;">
<option value="index.html">Please Choose...</option>
<xsl:apply-templates select="/links/link" mode="index"/>
</select>
...
<xsl:template match="link" mode="index">
<option value="{@id}.html"><xsl:value-of select="@id"/></option>
</xsl:template>
Notice here that we need some client-side script to produce the redirection. When a choice is made in the select element, the <SCRIPT LANGUAGE="JavaScript">
<xsl:text disable-output-escaping="yes"><![CDATA[<!--]]></xsl:text>
function openURL()
{
selInd = document.theForm.selectBook.selectedIndex;
goURL = document.theForm.selectBook.options[selInd].value;
top.location.href = goURL;
}
<xsl:text disable-output-escaping="yes"><![CDATA[//-->]]></xsl:text>
</SCRIPT>
Here we see the tweak needed to correctly output the JavaScript hide The C# ApplicationFinally, then we need to write an application which executes a sequence of transformations passing in different parameters for each transformation. We could of course write a quick and dirty app to do it in a few lines, but while we're in programming mode, let's write a slightly more general solution. Requirements for the program:
Perhaps at this stage, it is better to run the application and see it working. Download the first of the links at the top of this page and extract the zip file to c:\temp (stick with this directory initially). Navigate to the folder CodeProjectTransform in c:\temp and inside this folder, execute Transform.exe. From the File menu, choose Load Settings and load the Config.xml file which is in the same directory. This will fill in the settings in the various textboxes. Click on the tab named "Execute" (along the top of the form) and click the Execute button. If you get a successful sort of message, you can close the program down (File->Exit) and navigate to the sub-folder Output. You should now have 3 HTML files there (mark, ruth, luke) which can be opened in your browser.
I shall leave out the basic 'plumbing' behind the user interface (tab control and menu), and concentrate instead on the code that runs the actual transformation itself. I shall also ignore the loading and saving of the transformation settings because although it does use an XML file to store the settings, the load settings code is crude and the save settings has not yet been implemented. I should just point out one thing of interest here before I begin. The .NET Framework while complete with a nice
I found a very simple wrap-around class
OK, so onto the transformation code run when the Execute button is clicked. If there are no parameters provided or the first parameter does not have the "Process for each XPATH Node" checkbox checked, then it is a simple one off transformation, for which we use the code: XmlDocument xmlInput = new XmlDocument();
xmlInput.Load(txtInput.Text);
XslTransform xslFormat = new XslTransform();
xslFormat.Load(txtTransform.Text);
...
// simple one-off transformation
XsltArgumentList args = new XsltArgumentList();
foreach(ParameterList.ParametersRow p in dgParamList.Parameters.Rows)
args.AddParam(p.Name,"",resolveParam(p.Value,p.isXPATH, xmlInput));
string strFilename = txtFolder.Text + @"\" +
txtSequential.Text + "." + txtExtension.Text;
StreamWriter writer = new StreamWriter(strFilename);
xslFormat.Transform(xmlInput,args,writer);
writer.Flush();
writer.Close();
txtResults.Text+= "Completed transformation.\r\n";
Let's step through this code carefully. First, we define and initialize two objects: an instance of Next, we define and initialize We need to fill the private string resolveParam(string pValue, bool isXPATH, XmlDocument doc)
{
string retValue;
if(!isXPATH)
retValue = pValue;
else
{
try
{
XmlNode nde = doc.SelectSingleNode(pValue);
if(nde==null)
retValue = String.Empty;
else
retValue = nde.InnerText;
}
catch
{
txtResults.Text += "Invalid XPath Query Causing Error\r\n";
retValue = String.Empty;
}
}
return retValue;
}
This method checks to see if the parameter value is claiming to be an XPath expression. If it is, then it evaluates the expression against the xml input document and returns the text contents of the first node match. If not, then the parameter is simple text and this text is returned. Finally, returning to the simple transformation code, a That just leaves the more complicated sequence of transformations, where a transformation is executed for every node in a node-set obtained from the XML input. // more complicated sequence of transformations
try
{
XmlNodeList nl = xmlInput.SelectNodes(dgParamList.Parameters[0].Value);
for(int t=0;t<nl.Count;t++)
{
try
{
XsltArgumentList args = new XsltArgumentList();
args.AddParam(dgParamList.Parameters[0].Name,"",nl[t].InnerText);
for(int i=1;i<dgParamList.Parameters.Count;i++)
args.AddParam(dgParamList.Parameters[i].Name,"",
resolveParam(dgParamList.Parameters[i].Value,
dgParamList.Parameters[i].isXPATH, xmlInput));
string strName;
if(rdoSequential.Checked)
strName = txtSequential.Text + t.ToString();
else
strName = nl[t].InnerText;
string strFilename = txtFolder.Text + @"\" +
strName + "." + txtExtension.Text;
StreamWriter writer = new StreamWriter(strFilename);
xslFormat.Transform(xmlInput,args,writer);
writer.Flush();
writer.Close();
txtResults.Text+= "Completed transformation of " +
strFilename + ".\r\n";
}
catch
{
txtResults.Text += "Transformation " +
(t+1).ToString() + " failed\r\n";
}
}
}
catch
{
txtResults.Text += "Invalid XPath Query Causing Error\r\n";
return;
}
This is similar in many ways to the simple transformation except we first execute an XPath query against the XML input document using the first parameter stored. We obtain a node-set of matching nodes and proceed to execute one transformation for each node in this node-set. The other parameters are added as before using the That's about it. Go light on me people; first article and a bit of an amateur here. I hope someone finds it helpful. History16 April 2004: Article written.
|
|||||||||||||||||||||||||||||||||||||||||||||||