- Parsing XML Documents with DOM Level 2
- DOM Example: Representing an XML Document as a JTree
- Parsing XML Documents with SAX 2.0
- SAX Example 1: Printing the Outline of an XML Document
- SAX Example 2: Counting Book Orders
- Transforming XML with XSLT
- XSLT Example 1: XSLT Document Editor
- XSLT Example 2: Custom JSP Tag
- Summary
23.7 XSLT Example 1: XSLT Document Editor
Listing 23.13 shows a simple Swing document editor that presents three tabbed panes: one for an XML document, one for an XSL style sheet, and one for a resulting XSLT-transformed document. Both the XML and XSL document panes are editable, so after you load the XML and XSL files from disk you can edit the documents directly. Each tabbed pane contains a scrollable Doc_umentPane that inherits from a JEditorPane (see Listing 23.14). The XML and XSL panes are treated as plain text, and the XSLT pane is treated as HTML. If an XML file and XSL file are loaded, selecting the XSLT tab will invoke an XslTransformer (Listing 23.12) to process the XML file by using the style sheet, and present the results as HTML in the XSLT document pane.
Listing 23.13 XsltExample.java
import javax.xml.transform.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.io.*; import cwp.XslTransformer; /** A document editor to process XML and XSL text using * XSLT and presenting the results as HTML. Three tabbed panes * are presented: an editable text pane for the XML document, * an editable text pane for the XSL style sheet, and a non- * editable HTML pane for the HTML result. If an XML and XSL * file are loaded, then selecting the XSLT tab will perform * the transformation and present the results. If there is * a problem processing the XML or XSL document, then a * message box is popped up describing the problem. */ public class XsltExample extends JFrame implements ChangeListener { private static final int XML = 0; private static final int XSL = 1; private static final int XSLT = 2; private static final String DEFAULT_TITLE = "XSLT Example"; private static final String[] tabTitles = { "XML", "XSL", "XSLT" }; private static final String[] extensions = { "xml", "xsl", "html" }; private Action openAction, saveAction, exitAction; private JTabbedPane tabbedPane; private DocumentPane[] documents; private XslTransformer transformer; public XsltExample() { super(DEFAULT_TITLE); transformer = new XslTransformer(); WindowUtilities.setNativeLookAndFeel(); Container content = getContentPane(); content.setBackground(SystemColor.control); // Set up menus JMenuBar menubar = new JMenuBar(); openAction = new OpenAction(); saveAction = new SaveAction(); exitAction = new ExitAction(); JMenu fileMenu = new JMenu("File"); fileMenu.add(openAction); fileMenu.add(saveAction); fileMenu.add(exitAction); menubar.add(fileMenu); setJMenuBar(menubar); // Set up tabbed panes tabbedPane = new JTabbedPane(); documents = new DocumentPane[3]; for(int i=0; i<3; i++) { documents[i] = new DocumentPane(); JPanel panel = new JPanel(); JScrollPane scrollPane = new JScrollPane(documents[i]); panel.add(scrollPane); tabbedPane.add(tabTitles[i], scrollPane); } documents[XSLT].setContentType(DocumentPane.HTML); // JEditorPane has a bug, whereas the setText method does // not properly recognize an HTML document that has a META // element containing a CONTENT-TYPE, unless the EditorKit // is first created through setPage. Xalan automatically // adds a META CONTENT-TYPE to the document. Thus, // preload a document containing a META CONTENT-TYPE. documents[XSLT].loadFile("XSLT-Instructions.html"); documents[XSLT].setEditable(false); tabbedPane.addChangeListener(this); content.add(tabbedPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(450, 350); setVisible(true); } /** Checks to see which tabbed pane was selected by the * user. If the XML and XSL panes hold a document, then * selecting the XSLT tab will perform the transformation. */ public void stateChanged(ChangeEvent event) { int index = tabbedPane.getSelectedIndex(); switch (index) { case XSLT: if (documents[XML].isLoaded() && documents[XSL].isLoaded()) { doTransform(); } case XML: case XSL: updateMenuAndTitle(index); break; default: } } /** Retrieve the documents in the XML and XSL pages * as text (String), pipe into a StringReader, and * perform the XSLT transformation. If an exception * occurs, present the problem in a message dialog. */ private void doTransform() { StringWriter strWriter = new StringWriter(); try { Reader xmlInput = new StringReader(documents[XML].getText()); Reader xslInput = new StringReader(documents[XSL].getText()); transformer = new XslTransformer(); transformer.process(xmlInput, xslInput, strWriter); } catch(TransformerException te) { JOptionPane.showMessageDialog(this, "Error: " + te.getMessage()); } documents[XSLT].setText(strWriter.toString()); } /** Update the title of the application to present * the name of the file loaded into the selected * tabbed pane. Also, update the menu options (Save, * Load) based on which tab is selected. */ private void updateMenuAndTitle(int index) { if ((index > -1) && (index < documents.length)) { saveAction.setEnabled(documents[index].isLoaded()); openAction.setEnabled(documents[index].isEditable()); String title = DEFAULT_TITLE; String filename = documents[index].getFilename(); if (filename.length() > 0) { title += " - [" + filename + "]"; } setTitle(title); } } /** Open a file dialog to either load a new file to or save * the existing file in the present document pane. */ private void updateDocument(int mode) { int index = tabbedPane.getSelectedIndex(); String description = tabTitles[index] + " Files"; String filename = ExtensionFileFilter.getFileName(".", description, extensions[index], mode); if (filename != null) { if (mode==ExtensionFileFilter.SAVE) { documents[index].saveFile(filename); } else { documents[index].loadFile(filename); } updateMenuAndTitle(index); } } public static void main(String[] args) { new XsltExample(); } // Open menu action to load a new file into a // document when selected. class OpenAction extends AbstractAction { public OpenAction() { super("Open ..."); } public void actionPerformed(ActionEvent event) { updateDocument(ExtensionFileFilter.LOAD); } } // Save menu action to save the document in the // selected pane to a file. class SaveAction extends AbstractAction { public SaveAction() { super("Save"); setEnabled(false); } public void actionPerformed(ActionEvent event) { updateDocument(ExtensionFileFilter.SAVE); } } // Exit menu action to close the application. class ExitAction extends AbstractAction { public ExitAction() { super("Exit"); } public void actionPerformed(ActionEvent event) { System.exit(0); } } }
Listing 23.14 DocumentPane.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; import java.net.*; /** A JEditorPane with support for loading and saving the * document. The document should be one of two * types: "text/plain" (default) or "text/html". */ public class DocumentPane extends JEditorPane { public static final String TEXT = "text/plain"; public static final String HTML = "text/html"; private boolean loaded = false; private String filename = ""; /** Set the current page displayed in the editor pane, * replacing the existing document. */ public void setPage(URL url) { loaded = false; try { super.setPage(url); File file = new File(getPage().toString()); setFilename(file.getName()); loaded = true; } catch (IOException ioe) { System.err.println("Unable to set page: " + url); } } /** Set the text in the document page, replace the exiting * document. */ public void setText(String text) { super.setText(text); setFilename(""); loaded = true; } /** Load a file into the editor pane. * * Note that the setPage method of JEditorPane checks the * URL of the currently loaded page against the URL of the * new page to laod. If the two URLs are the same, then * the page is <b>not</b> reloaded. */ public void loadFile(String filename) { try { File file = new File(filename); setPage(file.toURL()); } catch (IOException mue) { System.err.println("Unable to load file: " + filename); } } public void saveFile(String filename) { try { File file = new File(filename); FileWriter writer = new FileWriter(file); writer.write(getText()); writer.close(); setFilename(file.getName()); } catch (IOException ioe) { System.err.println("Unable to save file: " + filename); } } /** Return the name of the file loaded into the editor pane. */ public String getFilename() { return(filename); } /** Set the filename of the document. */ public void setFilename(String filename) { this.filename = filename; } /** Return true if a document is loaded into the editor * page, either through <code>setPage</code> or * <code>setText</code>. */ public boolean isLoaded() { return(loaded); } }
The results for the XsltExample are shown in Figure 237 through Figure 239. Specifically, the result for the XML document pane with the loaded file, perenni_als.xml (Listing 23.4), is shown in Figure 237. The XSL document pane with the loaded file, perennials.xsl (Listing 23.15), is shown in Figure 238. Finally, the XSLT transformation of the XML document is presented in Figure 238. For this example, all daylilies awarded a Stout Medal are selected from the XML file and listed in an HTML TABLE. For each daylily matching the criteria, the year of hybridization, cultivar name, bloom season, and cost are presented in the table.
Note that for Apache Xalan-J to perform the XSLT transformation, the DTD, perenni_als.dtd, must be accessible on-line from http://archive.corewebprogramming.com/dtds/. If you would like to test this example locally, place the file, perennials.dtd, in a dtds subdirectory below the XML file, and change the DOCTYPE statement from
<!DOCTYPE perennials SYSTEM "http://archive.corewebprogramming.com/dtds/perennials.dtd">
to
<!DOCTYPE perennials SYSTEM "dtds/perennials.dtd">
Note that in the XSL file, if you include a doctype-public attribute for the xsl:output element, then Xalan will include a DOCTYPE statement for the first line of the output document.
Core Approach
Include a doctype-public attribute in the xsl:output element to produce a DOCTYPE statement in the transformed output.
Listing 23.15 perennials.xsl
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" doctype-public ="-//W3C//DTD HTML 4.0 Transitional//EN"/> <xsl:template match="/"> <HTML> <HEAD> <TITLE>Daylilies</TITLE> </HEAD> <BODY> <TABLE CELLPADDING="3"> <CAPTION>Stout Medal Award</CAPTION> <TR> <TH>Year</TH> <TH>Cultivar</TH> <TH>Bloom Season</TH> <TH>Cost</TH> </TR> <!-- Select daylilies awarded a Stout Medal. --> <xsl:apply-templates select="/perennials/daylily[award/name='Stout Medal']"/> <TR> <TD COLSPAN="4" ALIGN="CENTER"> E-early M-midseason L-late</TD> </TR> </TABLE> </BODY> </HTML> </xsl:template> <xsl:template match="daylily"> <TR> <TD><xsl:value-of select="award/year"/></TD> <TD><xsl:value-of select="cultivar"/></TD> <!-- Select the bloom code. --> <TD ALIGN="CENTER"><xsl:value-of select="bloom/@code"/></TD> <TD ALIGN="RIGHT"><xsl:value-of select="cost"/></TD> </TR> </xsl:template> </xsl:stylesheet>
Figure 237 Presentation of XML tabbed pane in XsltExample with perennials.xml (Listing 23.4) loaded.
Figure 238 Presentation of XSL tabbed pane in XsltExample with perennials.xsl (Listing 23.15) loaded.
Figure 239 Result of XSLT transformation of perennials.xml (Listing 23.4) and perennials.xsl (Listing 23.15).