Introduction
This article describes two solutions that I have employed in applications to generate custom reports. My solutions convert XML data to HTML using XSL Transformations (XSLT). XML is an eXtensible Markup Language that is used as a data-exchange format between applications. XSL is an eXtensible Stylesheet Language that is used to present and lay out XML data in a browser. XSLT is a programming language that facilitates the transformation of XML data to its HTML equivalent before it is displayed in a browser.
The first solution uses Sun's Java API for XML Processing (JAXP) to convert XML to HTML. The latest production version of the JAXP is version 1.1. The second solution uses Microsoft's MSXML 3.0 API to convert XML to HTML. (Microsoft XML [MSXML) is an XSLT processor that works with the Internet Explorer browser and includes an XML parser and XPath processor.)
Some of the benefits of these implementations follow. XML data is derived from the existing application. As a result, the same XML data may be used by other applications, making the application and the data more useful. The presentation layer of the reports is separated from the business objects layer. Consequently, the report-generation utility may be used by other applications, including a Web server application. If the layout of the report is modified, the application does not have to be recompiled and linked. Only the XSL templates have to be modified.
After I describe each solution, I present some of my observations and suggestions.
Note: This article assumes that the reader already has some knowledge of XML and XSL. See the links at the bottom of this article for references to technical reports about XML and XSL.
Using Sun's Java API for XML Processing to Transform XML to HTMLJAXP 1.1 consists of three class libraries. These libraries exist in Java Archive file format (JAR) files. Their file names are jaxp.jar, crimson.jar, and xalan.jar. The final release supports the latest XML standards, including version 2.0 of the Simple API for XML (SAX 2.0), and Level 2 of the Document Object Model (DOM Level 2). Also, it includes integrated support for XSL Transformations (XSLT).
I recommend that you install version 1.3 of the Java Development Kit (JDK) when using JAXP 1.1. When I use version 1.2 of the JDK, my application displays a minor, non-fatal error message when it shuts down. This behavior is caused by the JDK, not the application, and version 1.3 resolves this problem.
JAXP Installation
Decompress the zip or tar file and copy the three JAR files to the following subdirectory in your system's JDK directory. On my Windows machine the subdirectory is as follows.
C:\jdk1.3\lib You must add these JAR files to your CLASSPATH in order to use them. Remember to observe case in the filenames. Alternatively, you may copy the three class libraries to the Java extensions directory -- C:\jdk1.3\jre\lib\ext. I prefer the former installation location, but you are free to choose.
Report Generator Class - RepGen.java and RepGen.class
The report generator is a very simple class that wraps the functionality of the JAXP Transformer object. Applications create an instance of the report generator object and request reports with the buildReport method. The buildReport method takes an XML data string and the name of the XSL template file, which will be used to convert the XML data. It builds the requested report using the JAXP Transformer object, and displays it in the default browser on the target machine. The user prints the HTML page with the standard browser print function. The user saves the HTML page to another file from the browser window, using the "Save As" menu option in the browser's File menu.
The RepGen class is a Java Bean. It has properties that allow the user to get and set the XML data string and the XSL template filename. It has two methods that allow the user to build the HTML page. Both methods have the name buildReport. The first method allows the user to specify the XML data string and XSL template filename as parameters. The second method uses the XML data string and XSL template filename that are set by the properties. The source code for the RepGen class follows.
package com.fleet.utilities.repgen; // Imported JAXP Transformer (TraX) classes import javax.xml.transform.TransformerFactory; import javax.xml.transform.Transformer; import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerConfigurationException; // Imported java classes import java.io.StringReader; import java.io.FileReader; import java.io.BufferedReader; import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; // Import the BrowserLauncher class import com.fleet.utilities.repgen.BrowserLauncher; /* * Use the TraX interface to perform a transformation in the simplest manner possible * (3 statements). */ public class RepGen implements Serializable { String sXML; String sXSLTemplate; public String getXML() { return sXML; } synchronized public void setXML(String sXMLData) { sXML = sXMLData; } public String getXSLTemplate() { return sXSLTemplate; } synchronized public void setXSLTemplate(String sXSLFile) { sXSLTemplate = sXSLFile; } // Make sure the buildReport method is thread-safe. synchronized public void buildReport(String sXMLData, String sXSLFile) throws TransformerException, TransformerConfigurationException, FileNotFoundException, IOException { // Create an output file with the same name as the XSL template file, // but with an extension of ".html". String sOutFile; int iDot = sXSLFile.lastIndexOf("."); if ( iDot != -1 ) { sOutFile = sXSLFile.substring(0, iDot) + ".html"; } else { sOutFile = sXSLFile + ".html"; } // Initialize the Bean properties. sXML = sXMLData; sXSLTemplate = sXSLFile; // Use the static TransformerFactory.newInstance() method to instantiate // a TransformerFactory. The javax.xml.transform.TransformerFactory // system property setting determines the actual class to instantiate -- // org.apache.xalan.transformer.TransformerImpl. TransformerFactory tFactory = TransformerFactory.newInstance(); // Use the TransformerFactory to instantiate a transformer that will work with // the style sheet you specify. This method call also processes the style sheet // into a compiled Templates object. Transformer transformer = tFactory.newTransformer(new StreamSource(sXSLTemplate)); // Use the transformer to apply the associated templates object to an XML document // and write the output to a file with the same name as the XSL template file that // was passed in sXSLFile. transformer.transform(new StreamSource(new StringReader(sXML)), new StreamResult(new FileOutputStream(sOutFile))); // Now, launch the default browser with the HTML result. BrowserLauncher.openURL(sOutFile); } // Make sure the buildReport method is thread-safe. synchronized public void buildReport() throws TransformerException, TransformerConfigurationException, FileNotFoundException, IOException { // Create an output file with the same name as the XSL template file, // but with an extension of ".html". String sOutFile; int iDot = sXSLTemplate.lastIndexOf("."); if ( iDot != -1 ) { sOutFile = sXSLTemplate.substring(0, iDot) + ".html"; } else { sOutFile = sXSLTemplate + ".html"; } // Use the same three lines to transform the XML string to HTML. TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(new StreamSource(sXSLTemplate)); transformer.transform(new StreamSource(new StringReader(sXML)), new StreamResult(new FileOutputStream(sOutFile))); // Now, launch the default browser. BrowserLauncher.openURL(sOutFile); } // constructor public RepGen() {} // Include a static main function for testing. public static void main(String[] args) throws TransformerException, TransformerConfigurationException, FileNotFoundException, IOException { if (args.length != 2) { System.err.println("usage: java com.fleet.utilities.repgen.RepGen source stylesheet"); System.exit(1); } RepGen rgObject = new RepGen(); BufferedReader br = new BufferedReader(new FileReader(args[0])); String sLine; StringBuffer sBuffer = new StringBuffer(); while ( (sLine = br.readLine()) != null ) { sBuffer.append(sLine+"\n"); } rgObject.buildReport(sBuffer.toString(), args[1]); } } The buildReport method performs the transformation from XML to HTML. First, the method creates an instance of the TransformerFactory class. Then, it requests a Transformer object from the class factory. The Transformer object has a "transform" method that uses a StreamSource object for input and a StreamResult object for output. The JAXP Transformer classes define and implement these stream classes. See the import statements in the code above for a list of the classes that are imported by the RepGen class.
The StreamSource class has many constructors. One of these constructors takes a StringReader object and converts it to a StreamSource object. We pass our XML string through the StringReader object's constructor when we create the StreamSource object.
The StreamResult class has many constructors. One of these constructors outputs text to a FileOutputStream object. The buildReport method produces an HTML filename from the XSL template filename, but changes the extension to ".html." Then, it passes this HTML filename to the FileOutputStream object's constructor, which is passed to the transform method via a new StreamResult object. The transform method converts the XML string to HTML and writes the results to the output file.
See the JAXP documentation that comes with the distribution for more details on these classes. Or click on the following link to view various references to JAXP online documentation.
http://java.sun.com/xml/xml_jaxp.html
Our example displays the results in a Web browser using the BrowserLauncher class. The BrowserLauncher class is a simple utility that encapsulates the details about launching the default browser. A programmer named Eric Albert was kind enough to publish this class on his Stanford University Web site. I have included this class in the RepGen JAR file. See the links below to download the source for the BrowserLauncher class. On Windows machines, the BrowserLauncher class uses the default browser. On Unix machines, the BrowserLauncher class assumes that Netscape is the default browser.
The RepGen class is distributed as a JAR file, which is called RepGen.jar. Add this jar file to your CLASSPATH. Test the RepGen class with the following command.
java com.fleet.utilities.repgen.RepGen Report.xml Report.xsl
You will see results displayed as shown below in your browser.
I have included the sample XML and XSL files with the source code download. As you can see in the command above, they are called Report.xml and Report.xsl. Make sure these files are in the current directory when you execute the command. Otherwise, you will have to specify the complete system path and filename for each file.
The following code fragment illustrates an example of how to use the RepGen class.
import com.fleet.utilities.repgen.*; static public void main(String args[]) { RepGen rgObject; StringBuffer sXML = new StringBuffer(); StringBuffer sXSLFile = new StringBuffer(); // Add some code here to build the XML string. // This code will be different in your application. // For now, hard-code some data. sXML.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); sXML.append("..."); // Code removed for brevity. sXSLFile = "SalesReport.xsl"; try { rgObject = new RepGen(); // This method transforms the XML to HTML and pops up the default browser. rgObject.buildReport(sXML, sXSLFile); } catch (FileNotFoundException e) { } catch (TransformerConfigurationException e) { } catch (TransformerException e) { } catch (Exception e) { } } The RepGen class is very simple to use. First, create your XSL template file and your XML data string. Then create an instance of the RepGen class. Finally, call the buildReport method to display the HTML results in a browser.
Exceptions
The transform method fires a TransformerException when an error is encountered in the XML data or the XSL template file. The following code fragment shows how to use the TransformerException class.
try { rgObject = new RepGen(); // This method transforms the XML to HTML and pops up the default browser. rgObject.buildReport(sXML, sXSLFile); } catch (TransformerException e) { // See if a SourceLocator object is available. If it is, display the location // of the error in the XML data stream. Otherwise, just display the error message. if ( e.getLocator() != null ) { System.out.println("Error in XML document at line " + e.getLocator().getLineNumber() + " and column " + e.getLocator().getColumnNumber() + "\n"); } else { System.out.println(e.getMessage()); } } catch (Exception e) { System.out.println(e.getMessage()); } If the error occurs in the XML data stream, the getLocator method in the TransformerException class allows an exception handler to determine the source of the error. It returns a SourceLocator object that has two useful methods. The first method -- getLineNumber -- returns the line number of the offending node. The second method -- getColumnNumber -- returns the column number where the error occurred. These methods get you close to the location of the error, but I have found that it depends on the error. For example, if you forget a closing tag for one of the subnodes in the XML file, the line number overshoots to the next node's closing tag, and the column number is set to -1.
Generally, the SourceLocator object returns useful information. However, most of my errors occur in the XSL template file, not the XML data stream. The SourceLocator does not locate syntax errors in XSL directives. But the TransformerException object does provide information about the location of errors in the XSL template file. Sometimes the error message is not very informative. You may get an error message, such as "identifier must be quoted" if you forget to put double quotes around a tag's value. But you have to find the location of the missing double quote yourself.
While the SourceLocator object is not very useful for finding XSL errors, I have found that the TransformerException generates enough information about the source of errors in XSL templates, including line numbers, to be useful in most cases. So all is not lost. In spite of these minor shortcomings, the TransformerException is quite useful for debugging XML and XSL files.
Observations and Suggestions
The Java solution is platform independent, which is a good thing. Furthermore, the RepGen object can be instantiated from a Java server page. You may want to add additional methods that return the results as a string, rather than launch a web browser to view the results. This way you can display the HTML dynamically. Also, you may want to compare the XML data string and XSL template file with the values that were passed to the buildReport method previously in order to skip a transformation that has already been done. I'll leave these suggestions as an exercise for the reader.
I've noticed that the first transformation that executes takes significantly longer than the next. I attribute these results to the fact that the RepGen class and the JAXP classes are loading and compiling to native code during the first pass. If I convert the code to an Enterprise Java Bean, I suspect that this will no longer be a problem. On one real-world implementation, I compiled the code to Win32 native mode, and it ran quite well.
When the transform method encounters an error in the XML data stream, the SourceLocator object can be used to return the line number and column number where the error occurred. It would have been nice if the SourceLocator object returned the source of errors in XSL template files too. When the transform method encounters an error in the XSL template file, the TransformerException error message returns information about the source of the error in most cases, but not all. Sometimes it tells you what the error is, but you have to find it yourself.
The RepGen class can only be used in Java environments. If the client's platform of choice is Windows, then why not take advantage of the Microsoft MSXML 3.0 API? The next sections cover this solution in more detail.
Using Microsoft's MSXML 3.0 API to Transform XML to HTMLMicrosoft makes the MSXML 3.0 API available for free from its download site. Go to the following URL and search with the criteria "XML."
http://www.microsoft.com/downloads/search.asp
You have to specify your target operating system and "All Downloads." Then download the "MS XML Parser 3.0 Service Pack 1 release." This selection downloads a file called msxml3sp1.exe. Execute this file and follow the installation wizard to complete the installation.
Microsoft has released a preview version of the MSXML 4.0 API, but I have not had the chance to evaluate it. The example in this article uses version 3.0 and has been tested with the original release of the MSXML 3.0 API and with service pack 1.
If you like, you may download my XMLReports Browser utility, which handles the complete installation for you. If you do this, you get the MSXML3.0 API, which has been packaged with a simple browser utility that displays HTML in the MS Web Browser control.
XMLReports Browser Utility
The XMLReports Browser utility is a convenience tool that was developed to handle custom reporting in Visual Basic and ASP applications. The XMLReports Browser utility is a wrapper object that exposes the transformation feature of the Microsoft MSXML 3.0 API, and optionally displays the results in a simple browser. The user supplies the XML document and the XHTML/XSL template to the XMLReports Browser utility. The utility converts the documents to HTML.
Using this tool, the developer can modify the layout of reports by changing the text file that contains the EXtensible HTML (XHTML) and XSLT directives. The application does not need to be recompiled every time a report changes. Your reports are built using XHTML, so you can insert images, build tables, and populate them with the data in the XML document.
Many of the display characteristics of the browser are user-configurable. For example, the user may change the browser's caption, the browser's background colors, button images, button tool tips, and menu captions. Also, the browser object supports a WYSIWYG print function that prints all of the pages that have been inserted into the object by the client application. And you can submit multiple HTML pages to the browser object and page through them using the Next and Previous buttons.
Installing the XMLReports Browser Utility
The XMLReports Browser utility is packaged in a zip file that contains a self-extracting executable. Download the file, XMLReports.zip, at the following URL.
http://www.ftisystems.com/XMLReports.zip
Extract the files into a temporary directory. The XMLReports.zip file contains the following files. setup.exe - the setup program setup.lst - the packing list that is used by the setup program XMLReports.CAB - a cabinet file that contains the XMLReports object and its dependencies. XMLReports.doc - this document. Report.xml - a sample XML file. Report.xsl - a sample XHMTL file with XSLT directives. Execute the setup.exe program and follow all of the directions to complete the installation. If the wizard asks you if you want to keep a newer version of a file on your system, do so. Do not overwrite newer versions of your files with those in the download.
Hardware and Software Requirements
The XMLReports Browser utility runs on Windows 95/98/NT/2000 machines. These machines must have Microsoft Internet Explorer 5.0 or higher, preferably 5.5.
On machines running Windows 95, OSR2 or higher is required. On machines running Windows NT, SP3 or higher is required.
XMLReports Browser Utility Usage
Create a new project in Visual Basic (VB). My example uses VB 6.0. Go to Project/References and select the "XMLReports" server. If it is not selected, click on the checkbox next to the name. If you are not using the XMLReports Browser utility, select the "Microsoft XML v3.0" server instead. The XMLReports server wraps the MSXML v3.0 Server, so you don't have to select it if you are using the XMLReports Browser utility.
The XMLReports Browser utility has two methods that convert XML to HTML. One method is called InitUsingStrings and the other is called InitUsingFiles. Client applications may call these methods repeatedly in order to generate multiple reports in the same browser object.
The InitUsingFiles function converts a client application's XML document to HTML. The caller specifies an XML document filename, an XSL template filename, and an HTML filename. The following function declaration describes the InitUsingFiles method in more detail.
Public Function InitUsingFiles( strXMLFile As String, strXSLFile As String, strHTMLFile As String) As Long Where strXMLFile a string that contains the full filespec to a file that contains the XML document. strXSLFile a string that contains the full filespec to a file that contains the XHTML and XSL directives. The template must conform to the XSLT namespace URI, http://www.w3.org/1999/XSL/Transform. strHTMLFile a string that contains the full filespec to a file that will contain the results of the transformation. Return Codes: 0 if no error occurs. <0 if an error occurs. The InitUsingStrings method does the same thing as the InitUsingFiles method, except that the client application passes strings as parameters instead of filenames.
Using the XMLReports Browser Utility
The following code fragment illustrates how to use the XMLReports Browser utility in a Visual Basic application.
Dim objXMLReports As XMLReports.XMLUtilities Dim strHTMLFile As String Dim strXMLFile As String Dim strXSLFile As String Dim lngResults As Long ' Make sure the Report.xml and Report.xsl files are in the App.Path directory. strHTMLFile = App.Path & "\Report.html" strXMLFile = App.Path & "\Report.xml" strXSLFile = App.Path & "\Report.xsl" Set objXMLReports = New XMLReports.XMLUtilities lngResults = objXMLReports.InitUsingFiles(strXMLFile, strXSLFile, strHTMLFile) If lngResults = 0 Then ' Display the results in the Web Browser Control. objXMLReports.Show Else ' An error occurred. See the documentation that comes with the ' XMLReports distribution for a list of the valid values that can be returned. End If First, create an instance of the XMLReports object. Then specify the three filenames that have to be passed to the InitUsingFiles method. If the InitUsingFiles method returns zero, no error has occurred. Our example shows the browser object. A non-zero return code means that an error occurred. You will receive an error code when a file is not found by the method, when the XSL template has invalid directives in it, and when the XML data is not well-formed.
If the XML data is well-formed, but the XSL template has an invalid directive, the XMLReports object creates an HTML result that shows the exact location where the error occurred in the XSL template. This handy little feature makes it easier to debug your XSL templates during the design phase. A following section will show you how to locate the source of the error in an XSL template.
The XMLReports Browser Utility has a large number of properties and methods, most of which configure the look and feel of the browser object. See the documentation that comes with the XMLReports Browser utility for more details about these properties and methods, and for a list of error codes.
Client applications do not have to display the browser object. Instead, they can execute the InitUsingStrings method to return the results of the transformation in a string. These results can be rendered with dynamic HTML in a client browser, or with Active Server Pages (ASP) scripting logic at the server. If you are going to transform XML in an Active Server Page, you probably want to use the MSXML 3.0 API directly. The next section illustrates its use.
Using the MSXML 3.0 API
To use the MSXML 3.0 API, you have to build a Document Object Model (DOM) for the XML data and the XSL template source. Then you transform the XML data to HTML using the XSL template. The following code fragments show how this is done.
' ' LoadXMLDoc - Returns the XML Document object to the caller. It's used to load ' XML and XSL strings, files, or URLs. ' ' On Entry: ' objDOMXML - The MSXML2.DOMDocument object to load. ' varXML - If it's an XML string, load it into the default DOM document. ' If it's a URI string, load the file or URL. ' If it's an object, just return it. ' Private Sub LoadXMLDoc(objDOMXML As MSXML2.DOMDocument, varXML As Variant) Dim strURI As String If InStr(varXML, "<") > 0 Then 'This is a string because < is not valid in a filename objDOMXML.loadXML varXML Else 'Else load from URI objDOMXML.Load varXML End If End Sub ' ' TransformXML: Takes the XML string or URI and transforms it with the XSL string or URI. ' ' On Entry: ' varXML - an XML string, or a URI to an XML file. ' varXSL - an XSL string, or a URI to an XSL file. ' ' On Exit: A string that contains the HTML. ' Private Function TransformXML(varXML As Variant, varXSL As Variant) As String Dim objXML As New MSXML2.DOMDocument Dim objXSL As New MSXML2.DOMDocument On Error GoTo TransformXMLErrorHandler TransformXML = "" LoadXMLDoc objXML, varXML If objXML.parseError <> 0 Then TransformXML = MakeParseError(objXML) Exit Function End If LoadXMLDoc objXSL, varXSL If objXSL.parseError <> 0 Then TransformXML = MakeParseError(objXSL) Exit Function End If TransformXML = objXML.transformNode(objXSL) Exit Function TransformXMLErrorHandler: TransformXML = "Error " & Err.Number & " in TransformXML. Error is " & _ Err.Description & "." End Function ' ' MakeParseError: - Makes a nice HTML error message for display in a browser. ' ' On Entry: ' objXML - the MSXML2.DOMDocument that caused the error. ' Private Function MakeParseError(objXML As MSXML2.DOMDocument) As String Dim nIdx As Integer Dim strSpaces As String strSpaces = String(objXML.parseError.linepos, " ") MakeParseError = "<html><title>XML Parse Error</title><body>" MakeParseError = MakeParseError & "<font size=4>XML Error loading '" & _ objXML.parseError.url & "'</font>" & _ "<p><b><font size=2>" & objXML.parseError.reason & _ "</b></p></font>" If objXML.parseError.Line > 0 Then MakeParseError = MakeParseError & "<p><font size=3><XMP>" & _ "at line " & objXML.parseError.line & ", character " & _ objXML.parseError.linepos & _ vbCrLf & objXML.parseError.srcText & _ vbCrLf & strSpaces & "^" & _ "</xmp></font></p>" End If MakeParseError = MakeParseError & "</body></html>" End Function The LoadXMLDoc function initializes an object of the type MSXML2.DOMDocument with the contents of the XML or XSL data stream. The data stream may be a string or a URI. Notice that the function uses different methods to initialize the object when the input source is a string, versus when the input source is a URI. The code fragment uses the loadXML method to load a string of XML or XSL directives into the document. The code fragment uses the Load method to load a URI. I am not sure why the developers of the API chose to implement the interface like this, because it requires the programmer to figure out whether the string is data or a URI. In any case, the code fragment assumes that if the string has a "<" character in it, it must be a data stream, not a URI, since this character is not a valid character for a URI.
The TransformXML function calls the LoadXMLDoc function. The TransformXML function has two parameters. The first parameter specifies the XML data stream or URI. The second parameter specifies the XSL data stream or URI. First, the TransformXML function creates a separate instance of the MSXML2.DOMDocument object for the XML data and the XSL data. Then it calls the LoadXMLDoc function to build a DOM for each data stream. After each call, the function checks the parseError object for errors. If an error occurs, an error message is returned from the MakeParseError function. The MakeParseError function is described in the Handling Errors section below. We will skip this for now.
If the function successfully loads both data streams, the function calls the XML document's transformNode method. It passes the XSL document as a parameter, so that the transformNode method can convert the XML data to HTML. The transformNode method returns an HTML string, which can be displayed in a browser.
Handling Errors
The MSXML2.DOMDocument object returns error information in the parseError object. The default property of the parseError object is the errorCode property. It contains the last error that occurred when the document was built. If the errorCode property returns zero, no error has occurred. Otherwise, the parseError object contains more details. The following table shows the other properties and methods of the parseError object.
Property or Method Name Description Filepos The absolute position in the source file where the error occurred. Line The line number in the source file where the error occurred. Linepos The column where the error occurred. Reason The description of the error. SourceText The source text of the line where the error occurred. url The url of the source file.
The following code fragment generates an HTML page that shows the location of the error, including the source text of the line where the error occurred and a pointer to the column. The TransformXML function that is described in the previous section uses this function to return errors that occurred in the LoadXMLDoc function.
' ' MakeParseError - Makes a nice HTML error message for displaying in a browser. ' ' On Entry: ' objXML - The MSXML2.DOMDocument that caused the error. ' Private Function MakeParseError(objXML As MSXML2.DOMDocument) As String Dim nIdx As Integer Dim strSpaces As String strSpaces = String(objXML.parseError.linepos, " ") MakeParseError = "<html><title>XML Parse Error</title><body>" MakeParseError = MakeParseError & "<font size=4>XML Error loading '" & _ objXML.parseError.url & "'</font>" & _ "<p><b><font size=2>" & objXML.parseError.reason & _ "</b></p></font>" If objXML.parseError.Line > 0 Then MakeParseError = MakeParseError & "<p><font size=3><XMP>" & _ "at line " & objXML.parseError.line & ", character " & _ objXML.parseError.linepos & _ vbCrLf & objXML.parseError.srcText & _ vbCrLf & strSpaces & "^" & _ "</xmp></font></p>" End If MakeParseError = MakeParseError & "</body></html>" End Function I changed an XSL directive in my sample XSL file in order to generate an error page so that you can see how expressive it is. The following image illustrates the results after I remove a double quote from one of the tags in the XSL file.
Observations and Suggestions
The Microsoft MSXML 3.0 API only works on machines that run a version of the Windows operating system. If your client's applications are targeted to the Windows operating system, take advantage of this API. It is fast, and its XSL error locator provides decent information.
Packaging the MSXML 3.0 API for deployment with your application has one drawback. The file msxml3.dll has two dependencies, both of which are missed by the Microsoft Packaging and Deployment Wizard that comes with Visual Basic 6.0. They are msxmla.dll and msxmlr.dll. These files are static dynamic link libraries (DLLs) and do not need to be registered like a COM server does. Nonetheless, they are dependencies of the msxml3.dll, so you will have to manually add them to the package.
Also, the Microsoft Packaging and Deployment Wizard thinks that these dependent files have to be registered like self-registering DLLs. This is not true and you will have to remove the "(DLLSelfRegister)" property for each of these files in the setup.lst manifest that gets created by the Packaging and Deployment Wizard. If you do not remove this property, the installation will display non-fatal errors to the user that require the user to click "OK" to continue with the installation.
ConclusionIf you know that your target audience is running the Windows operating system, I recommend the MSXML 3.0 API. If your audience uses machines with various operating systems, then use the Java API for XML Processing. It doesn't make sense to create two solutions for the same client application.
Both of my solutions handled the same set of XSL directives without a problem. The subset of XSL directives that my applications require is not exhaustive. However, it does provide quite a bit of functionality. The following table contains the XSL directives that I have used in both environments. Many other XSL directives, string functions, and math functions exist.
XSL Directive
Description
<xsl:value-of>
Returns the value of the current node in an XPath
<xsl:template name="sub">
Defines a subroutine
<xsl:param name="parm1">
Defines a parameter that can be passed to a subroutine.
<xsl:template match="/">
Shorthand for matching XPath directives.
<xsl:for-each select="...">
For-To-Next loop
<xsl:call-template name="sub">
Calls a previously defined subroutine.
<xsl:with-param name="..." select="...">
Passes a parameter to a subroutine.
<xsl:if test="...">
Conditional Logic - if test.
<xsl:choose><xsl:when
test="..."><xsl:otherwise> Conditional Logic - case statement.
Embedded Functions
sum()
round()
last()
position()
count()
Math Operators
+, -, *, div
Boolean Functions
true()
false()
not()
String Functions
substring()
substring-before()
substring-after()
starts-with()
concat()
contains()
string-length()
translate()
I prefer the error information that the parseError object of the MSXML 3.0 API provides. It was easy to use, and I like that. I was able to implement an error locator that generates HTML pages, showing me exactly where the error occurred in my XML data streams and my XSL templates. This made my debugging efforts easier. Luckily, I wrote this first, so I was able to use it during my Java implementation when I had problems that were hard to diagnose.
I think XML and XSLT are here to stay. The Simple Object Access Protocol (SOAP), which is an XML alternative to CORBA and DCOM, has some promise as well as. And if Microsoft .NET is as good as what I've been reading, in a few years Java will have serious competition from C++, C#, and Visual Basic on operating systems other than Microsoft Windows. I would not be surprised if a Common Language Runtime (CLR) is written for the Linux and Solaris operating systems some day. But this is a subject for another article.
本文地址:http://com.8s8s.com/it/it18631.htm