ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: NetBeans: The Definitive Guide

NetBeans: Working with XML, Part 3

Related Reading

NetBeans: The Definitive Guide
By Tim Boudreau, Jesse Glick, Simeon Greene, Vaughn Spurlin, Jack J. Woehr

by Tim Boudreau, Jack J. Woehr, Vaughn Spurlin, Jesse Glick, Simeon Greene

In this final installment on working with XML, excerpted from NetBeans: The Definitive Guide, learn how to generate Java classes.

Generating Java Classes

Here comes the best part (after all, we are Java programmers): generating Java source code to process XML documents. First, we'll create a SAX document handler to read through our sample XML document and extract its element and attribute values. This gives us serial read-only access, useful in applications that need to get the information in an XML document quickly with minimal memory requirements. Next, we'll create a DOM tree scanner, useful in applications that need to access the information from an XML document in random order, update it, and output a modified XML document.

Generating a SAX Document Handler

Our next example will be a basic Simple API for XML (SAX) parser that simply lists the attributes and text in our Inventory.xml XML document. First, we will use the SAX Document Handler Wizard to generate source for the Java classes that will parse Inventory.xml. We will add a few lines of code to invoke the parser and display the parsed information. Finally, we will configure the execution service parameters and run it.

To launch the Wizard, right-click the DTD file Inventory_Inventory and then select SAX Document Handler Wizard from the context menu. The wizard's frame 1 of 4 specifies the API versions to use in generating code. For our example, select JAXP 1.1, not JAXP 1.0. Select SAX 2.0, not 1.0. Check the checkbox Propogate SAX Events to Generated Handler. Frame 2 allows you to customize the handling of each element. Accept the defaults because no special handling is needed. Frame 3 allows you to specify converter methods in case the extracted data requires a format conversion. Accept the defaults because no conversion is needed. Frame 4 allows you to specify the output File Names. Accept defaults for the file names, but uncheck the checkbox Save Customized Bindings. When the wizard is finished, three Java files will be generated:

In This Series

NetBeans: Working with XML, Part 2
In part two of this three-part series excerpted from NetBeans: The Definitive Guide, go beyond editing XML in your editors, within the open source NetBeans framework.

NetBeans: Working with XML, Part 1
In part one in this series of book excerpts from NetBeans: The Definitive Guide, learn how to work with XML within the NetBeans framework by installing XML support and working with XML editors.

After the files have been generated, a Confirm Changes dialog will pop up recommending changes in the implementation class to implement abstract methods. Check the Perform synchronization without confirmation radio button and click Process All.

Now that we have some source to work with, we need to make a few additions. First, add a main method to the parser classType or paste Example 11-5 into Inventory_InventoryParser.java.

Example 11-5: main method for Inventory_InventoryParser

public static void main(String[ ] args) throws Exception {
   Inventory_InventoryHandler handler =
      new Inventory_InventoryHandlerImpl( );
   EntityResolver resolver = null;
   Inventory_InventoryParser parser =
      new Inventory_InventoryParser(handler, resolver);
   InputSource input = new InputSource(args[0]);
   parser.parse(input);
}

All we need now is some print logic in the handler class. We will enhance a couple of callback methods in Inventory_InventoryHandlerImpl.java to handle the parsed data. Replace the characters method in Inventory_InventoryHandlerImpl.java with Example 11-6.

Example 11-6: characters method for Inventory_InventoryHandlerImpl

public void characters(char[ ] values, int param, int param2)
       throws org.xml.sax.SAXException {
   System.out.println(" Element Data: " + new String(values, param, param2));
}

Replace the startElement method in Inventory_InventoryHandlerImpl.java with Example 11-7. That's all the source changes we need. Right-click the package, and select Build All from the context menu to compile the classes.

Example 11-7: startElement method for Inventory_InventoryHandlerImpl

public void startElement(String str, String str1, String str2,
       org.xml.sax.Attributes attributes) throws org.xml.sax.SAXException {
   System.out.println("Element: " + str2);
   for (int i=0; i<attributes.getLength( ); i++){
      String name = attributes.getQName(i);
      String value = attributes.getValue(i);
      System.out.println("  Attribute: " + name + " = " + value);
   }
}

One more step, configuring execution parameters for the parser, and we'll be ready for a test. Open the Properties sheet for Inventory_InventoryParser.java. Click the Execution tab. Set the Arguments property to Inventory.xml. Invoke the Customizer dialog for the Executor property by clicking the value and then the ellipsis button. Click the Expert tab of the Customizer dialog to expose the Working Directory property. Set Working Directory to the directory that contains the Inventory.xml file, presumably the same as the source directory in which we've been working.

Finally, we're ready to test the completed SAX parser. Right-click Inventory_InventoryParser.java and select Execute from the context menu. If all goes well, the IDE will switch to the Running workspace, open an Output Window, and produce the results shown in Example 11-8.

Example 11-8: Output from Inventory_InventoryHandlerImpl

Element: Inventory
  Attribute: Description = Kit Inventory
Element: Kit
  Attribute: Description = The first kit
Element: Part
  Attribute: Description = Part A
  Element Data: Part A in Kit
Element: Supplier
  Attribute: Description = Our favorite supplier
Element: Part
  Attribute: Description = Part A
  Element Data: Part A in Supplier
Element: Part
  Attribute: Description = Part A
  Attribute: Size = Just right
  Attribute: Color = Purple
  Element Data: Part A alone

Generating a DOM Tree Scanner

And here we have the process for creating a basic Document Object Model (DOM) Tree Scanner. Like the SAX Document Handler above, it will list the attributes and text in our Inventory.xml document. But it will also make a small change to the attribute values to prove that the output is not exactly the same as the input. Our DOM Tree Scanner will change the input by prepending a coded abbreviation for each attribute's qualified name to the attribute's value. Finally, our DOM Tree Scanner will output XML data showing the modified attribute values.

We will start, just as with the SAX Document Handler, by generating the source, adding some code to display the input data, and running a test to verify the results. Then, we will code to output the attribute values and output the modified XML document. The output will be in the same format as the input but with slightly different data.

To generate the source, right-click the DTD file Inventory_Inventory and then select Generate DOM Tree Scanner from the context menu. Accept the default name Inventory_InventoryScanner. Add a main method. Type or paste Example 11-9 into Inventory_InventoryScanner.java. This code was adapted from the example in the comments near the top of the generated source, just after the package statement. Notice that similar comments are sprinkled throughout the source, code examples that could be uncommented to gain access to data parsed out of the XML input.

Example 11-9: main method for Inventory_InventoryScanner

public static void main(String[ ] args) throws Exception {
   javax.xml.parsers.DocumentBuilderFactory builderFactory =
      javax.xml.parsers.DocumentBuilderFactory.newInstance( );
   javax.xml.parsers.DocumentBuilder builder =
      builderFactory.newDocumentBuilder( );
   org.w3c.dom.Document document =
      builder.parse (new org.xml.sax.InputSource (args[0]));
   Inventory_InventoryScanner scanner =
      new Inventory_InventoryScanner (document);
   scanner.visitDocument( );
}

The generated source includes a method to process each element in the XML document, and each method includes logic to process the element's attributes. Each method is different, but they all have a structure similar to visitElement_Part shown in Table 11-1. We will augment the generated source as instructed in the following text and tables.

We need to add logic to print the name of each element. Search the source for occurrences of the following commented line. This line is one of the comments sprinkled throughout the source that shows how to access the data parsed from the XML input.

  // element.getValue( );

Add the print statements shown in Table 11-1 right after the commented lines that were found. Or replace the commented lines with the new statements, if you prefer.

Table 11-1: Printing element names in Inventory_InventoryScanner

Method

Print statement

visitElement_Inventory

System.out.println("Element: Inventory");

visitElement_Kit

System.out.println("Element: Kit");

visitElement_Part

System.out.println("Element: Part");

visitElement_Supplier

System.out.println("Element: Supplier");

We need to add logic to print the attribute names and values. Search the source for occurrences of the following commented line. Again, this is one of the comments sprinkled throughout the source that shows how to access the data parsed from the XML input:

  // attr.getValue( );

Add the print statements shown in Table 11-2 right after the commented lines that were found. Or replace the commented lines with the new statements, if you prefer.

Table 11-2: Printing attribute values in Inventory_InventoryScanner

Method

Attribute

Print statement

visitElement_Inventory

Description

System.out.println(" Attribute: Description = " + attr.getValue( ));

visitElement_Kit

Description

System.out.println(" Attribute: Description = " + attr.getValue( ));

visitElement_Part

Description

System.out.println(" Attribute: Description = " + attr.getValue( ));

visitElement_Part

Color

System.out.println(" Attribute: Color = " + attr.getValue( ));

visitElement_Part

Size

System.out.println(" Attribute: Size = " + attr.getValue( ));

visitElement_Supplier

Description

System.out.println(" Attribute: Description = " + attr.getValue( ));

Finally, we need to add logic to print the data for each element. This is easy in our example because only the Part element has data. Search the source for occurrences of the following commented line:

  // ((org.w3c.dom.Text)node).getData( );

Add the print statements shown in Table 11-3 right after the commented line that was found. Or replace the commented line with the new statement, if you prefer.

Table 11-3:

Printing element data in Inventory_InventoryScanner

Method

Print statement

visitElement_Part

System.out.println(" Element Data: " + ((org.w3c.dom.Text)node).getData( ));

Now, configure execution parameters for the scanner, just as you did in the previous section for the parser. Open the Properties sheet for Inventory_InventoryScanner.java, click the Execution tab, and set the Arguments property to Inventory.xml. Invoke the Customizer dialog for the Executor property by clicking the value and then the ... button, click the Expert tab of the Customizer dialog to expose the Working Directory property, and set Working Directory to the directory that contains the Inventory.xml, the same as the source directory. It's time to test. Compile and execute Inventory_InventoryScanner. The results should match Example 11-8.

Next, add code to make a slight modification to the data values, just enough to prove that they're different when the new XML is output later. Locate the statements that you added to print the attribute values. Before each statement that you located, add a new statement to change the value of the data. We want to prove that the data was changed when it's output again later. Add statements that invoke attr.setValue as shown in Table 11-3. For example, to see what visitElement_Part looks like with all changes in place, see Table 11-4.

Table 11-4: Modifying attribute values in Inventory_InventoryScanner

Method

Attribute

Statement to modify data

visitElement_Inventory

Description

attr.setValue("ID:" + attr.getValue( ));

visitElement_Kit

Description

attr.setValue("KD:" + attr.getValue( ));

visitElement_Part

Description

attr.setValue("PD:" + attr.getValue( ));

visitElement_Part

Color

attr.setValue("PC:" + attr.getValue( ));

visitElement_Part

Size

attr.setValue("PS:" + attr.getValue( ));

visitElement_Supplier

Description

attr.setValue("SD:" + attr.getValue( ));

Near the end of the visitElement_Part method locate the statement that prints the value of data in a Part node. Before the print statement add a statement invoking node.setData to change the text data, as shown in Example 11-10. The entire method is given to show all the added code in context.

Example 11-10: visitElement_Part method with all changes applied


/** Scan through org.w3c.dom.Element named Part. */
void visitElement_Part(org.w3c.dom.Element element) { // <Part>
   // element.getValue( );
   System.out.println("Element: Part");
   org.w3c.dom.NamedNodeMap attrs = element.getAttributes( );
   for (int i = 0; i < attrs.getLength( ); i++) {
      org.w3c.dom.Attr attr = (org.w3c.dom.Attr)attrs.item(i);
      if (attr.getName( ).equals("Description")) { // <Part Description="???">
         // attr.getValue( );
         attr.setValue("PD:" + attr.getValue( ));
         System.out.println(" Attribute: Description = " + attr.getValue( ));
      }
      if (attr.getName( ).equals("Color")) { // <Part Color="???">
         // attr.getValue( );
         attr.setValue("PC:" + attr.getValue( ));
         System.out.println(" Attribute: Color = " + attr.getValue( ));
      }
      if (attr.getName( ).equals("Size")) { // <Part Size="???">
         // attr.getValue( );
         attr.setValue("PS:" + attr.getValue( ));
         System.out.println(" Attribute: Size = " + attr.getValue( ));
      }
   }
   org.w3c.dom.NodeList nodes = element.getChildNodes( );
   for (int i = 0; i < nodes.getLength( ); i++) {
      org.w3c.dom.Node node = nodes.item(i);
      switch (node.getNodeType( )) {
         case org.w3c.dom.Node.CDATA_SECTION_NODE:
            // ((org.w3c.dom.CDATASection)node).getData( );
            break;
         case org.w3c.dom.Node.ELEMENT_NODE:
            org.w3c.dom.Element nodeElement = (org.w3c.dom.Element)node;
            if (nodeElement.getTagName( ).equals("Inventory")) {
               visitElement_Inventory(nodeElement);
            }
            if (nodeElement.getTagName( ).equals("Kit")) {
               visitElement_Kit(nodeElement);
            }
            if (nodeElement.getTagName( ).equals("Part")) {
               visitElement_Part(nodeElement);
            }
            if (nodeElement.getTagName( ).equals("Supplier")) {
               visitElement_Supplier(nodeElement);
            }
            break;
         case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE:
            // ((org.w3c.dom.ProcessingInstruction)node).getTarget( );
            // ((org.w3c.dom.ProcessingInstruction)node).getData( );
            break;
         case org.w3c.dom.Node.TEXT_NODE:
            // ((org.w3c.dom.Text)node).getData( );
            ((org.w3c.dom.Text)node).setData("PT:" +
                     ((org.w3c.dom.Text)node).getData( ));
            System.out.println("  Element Data: " +
                     ((org.w3c.dom.Text)node).getData( ));
            break;
      }
   }
}

We're almost done. Let's take a moment to compile and test. The new output should show our modifications to the input data.

Finally, we'll add more code to output the modified XML data. We will use a Transformer object to copy the document with its modified data to an output stream. Add code to the main method so that it matches Example 11-11.

Example 11-11: main method with all changes applied

public static void main(String[ ] args) throws Exception {
   javax.xml.parsers.DocumentBuilderFactory builderFactory =
      javax.xml.parsers.DocumentBuilderFactory.newInstance( );
   javax.xml.parsers.DocumentBuilder builder =
      builderFactory.newDocumentBuilder( );
   org.w3c.dom.Document document =
      builder.parse (new org.xml.sax.InputSource (args[0]));
   Inventory_InventoryScanner scanner =
      new Inventory_InventoryScanner (document);
   scanner.visitDocument( );
   
   // Output modified Inventory.xml
   TransformerFactory tranFact = TransformerFactory.newInstance( );
   Transformer tran = tranFact.newTransformer( );
   DOMSource DSource = new DOMSource(document);
   StreamResult SResult = new StreamResult(System.out);
   tran.transform(DSource, SResult);
}

Add the import statements in Example 11-12 after the package statement at the top.

Example 11-12: Import statements for Transformer references

import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

Compile and execute. Your output window should match Example 11-13 (except for a couple of line breaks added for readability). At the top is the data extracted and then modified from the input XML document. Next is the new XML document showing the modified data.

Example 11-13: Output from Inventory_InventoryScanner

Element: Inventory
   Attribute: Description = ID:Kit Inventory
Element: Kit
   Attribute: Description = KD:The first kit
Element: Part
   Attribute: Description = PD:Part A
   Element Data: PT:Part A in Kit
Element: Supplier
   Attribute: Description = SD:Our favorite supplier
Element: Part
   Attribute: Description = PD:Part A
   Element Data: PT:Part A in Supplier
Element: Part
   Attribute: Description = PD:Part A
   Attribute: Size = PS:Just right
   Attribute: Color = PC:Purple
   Element Data: PT:Part A alone

<!-- Created by vaughn on May 11, 2002, 9:01 PM -->
<Inventory Description="ID:Kit Inventory">
   <Kit Description="KD:The first kit">
      <Part Description="PD:Part A">PT:Part A in Kit</Part>

   </Kit>
   <Supplier Description="SD:Our favorite supplier">
      <Part Description="PD:Part A">PT:Part A in Supplier</Part></Supplier>
   <Part Description="PD:Part A" Size="PS:Just right"
                 Color="PC:Purple">PT:Part A alone</Part>
</Inventory>

These examples give the basics of reading, modifying, and writing XML documents with Java classes. NetBeans makes it easy to access XML documents wherever you find them or to generate your own whenever you need to share data with other applications. Just remember to use a SAX document handler if serial read access is adequate, especially when speed and memory are critical. Use a DOM tree scanner if you need random access to the data elements in the XML document, if you need to update the data, or if you need to generate a new document. SAX document handlers are widely used in web applications, where resources are tight. Keep that in mind while you're reading the next chapter.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.