Validating XML Documents


In the preceding section, you saw how to traverse the tree structure of a DOM document. However, if you simply follow that approach, you'll find that you will have quite a bit of tedious programming and error checking. Not only do you have to deal with whitespace between elements, but you also need to check whether the document contains the nodes that you expect. For example, suppose you are reading an element:

 <font>    <name>Helvetica</name>    <size>36</size> </font> 

You get the first child. Oops...it is a text node containing whitespace "\n ". You skip text nodes and find the first element node. Then you need to check that its tag name is "name". You need to check that it has one child node of type Text. You move on to the next non-whitespace child and make the same check. What if the author of the document switched the order of the children or added another child element? It is tedious to code all the error checking, and reckless to skip the checks.

Fortunately, one of the major benefits of an XML parser is that it can automatically verify that a document has the correct structure. Then the parsing becomes much simpler. For example, if you know that the font fragment has passed validation, then you can simply get the two grandchildren, cast them as Text nodes and get the text data, without any further checking.

To specify the document structure, you can supply a document type definition (DTD) or an XML Schema definition. A DTD or schema contains rules that explain how a document should be formed, by specifying the legal child elements and attributes for each element. For example, a DTD might contain a rule:

 <!ELEMENT font (name,size)> 

This rule expresses that a font element must always have two children, which are name and size elements. The XML Schema language expresses the same constraint as

 <xsd:element name="font">    <xsd:sequence>       <xsd:element name="name" type="xsd:string"/>       <xsd:element name="size" type="xsd:int"/>    </xsd:sequence> </xsd:element> 

XML Schema can express more sophisticated validation conditions (such as the fact that the size element must contain an integer) than can DTDs. Unlike the DTD syntax, the XML Schema syntax uses XML, which is a benefit if you need to process schema files.

The XML Schema language was designed to replace DTDs. However, as we write this chapter, DTDs are still very much alive. XML Schema is complex, and not all parsers support it. XML Schema support has been added to JDK 5.0, but, as you will see, the implementation is a bit shaky.

NOTE

Some XML users are so annoyed by the complexity of XML Schema that they use alternative validation languages. The most common choice is Relax NG (http://www.relaxng.org).


In the next section, we discuss DTDs in detail. We then briefly cover the basics of XML Schema support. Finally, we show you a complete application that demonstrates how validation simplifies XML programming.

Document Type Definitions

There are several methods for supplying a DTD. You can include a DTD in an XML document like this:


<?xml version="1.0"?>
<!DOCTYPE configuration [
   <!ELEMENT configuration . . .>
   more rules

   . . .
]>
<configuration>
  . . .
</configuration>

As you can see, the rules are included inside a DOCTYPE declaration, in a block delimited by [. . .]. The document type must match the name of the root element, such as configuration in our example.

Supplying a DTD inside an XML document is somewhat uncommon because DTDs can grow lengthy. It makes more sense to store the DTD externally. The SYSTEM declaration can be used for that purpose. You specify a URL that contains the DTD, for example:

 <!DOCTYPE configuration SYSTEM "config.dtd"> 

or

 <!DOCTYPE configuration SYSTEM "http://myserver.com/config.dtd"> 

CAUTION

If you use a relative URL for the DTD (such as "config.dtd"), then give the parser a File or URL object, not an InputStream. If you must parse from an input stream, supply an entity resolversee the following note.


Finally, the mechanism for identifying "well known" DTDs has its origin in SGML. Here is an example:

 <!DOCTYPE web-app   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"   "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> 

If an XML processor knows how to locate the DTD with the public identifier, then it need not go to the URL.

NOTE

If you use a DOM parser and would like to support a PUBLIC identifier, call the setEntityResolver method of the DocumentBuilder class to install an object of a class that implements the EntityResolver interface. That interface has a single method, resolveEntity. Here is the outline of a typical implementation:


class MyEntityResolver implements EntityResolver
{
   public InputSource resolveEntity(String publicID,
      String systemID)
   {
      if (publicID.equals(a known ID))
         return new InputSource(DTD data);
      else
         return null; // use default behavior
   }
}

You can construct the input source from an InputStream, a Reader, or a string.


Now that you have seen how the parser locates the DTD, let us consider the various kinds of rules.

The ELEMENT rule specifies what children an element can have. You specify a regular expression, made up of the components shown in Table 12-1.

Table 12-1. Rules for Element Content

Rule

Meaning

E*

0 or more occurrences of E

E+

1 or more occurrences of E

E?

0 or 1 occurrences of E

E1|E2| . . . |En

One of E1, E2, . . . , En

E1, E2, . . . , En

E1 followed by E2, . . . , En

#PCDATA

Text

(#PCDATA|E1|E2| . . . |En)*

0 or more occurrences of text and E1, E2, . . ., En in any order (mixed content)

ANY

Any children allowed

EMPTY

No children allowed


Here are several simple but typical examples. The following rule states that a menu element contains 0 or more item elements.

 <!ELEMENT menu (item)*> 

This set of rules states that a font is described by a name followed by a size, each of which contain text.

 <!ELEMENT font (name,size)> <!ELEMENT name (#PCDATA)> <!ELEMENT size (#PCDATA)> 

The abbreviation PCDATA denotes parsed character data. The data are called "parsed" because the parser interprets the text string, looking for < characters that denote the start of a new tag, or & characters that denote the start of an entity.

An element specification can contain regular expressions that are nested and complex. For example, here is a rule that describes the makeup of a chapter in this book:

 <!ELEMENT chapter (intro,(heading,(para|image|table|note)+)+) 

Each chapter starts with an introduction, which is followed by one or more sections consisting of a heading and one or more paragraphs, images, tables, or notes.

However, in one common case you can't define the rules to be as flexible as you might like. Whenever an element can contain text, then there are only two valid cases. Either the element contains nothing but text, such as

 <!ELEMENT name (#PCDATA)> 

Or the element contains any combination of text and tags in any order, such as

 <!ELEMENT para (#PCDATA|em|strong|code)*> 

It is not legal to specify other types of rules that contain #PCDATA. For example, the following rule is illegal:

 <!ELEMENT captionedImage (image,#PCDATA)> 

You have to rewrite such a rule, either by introducing another caption element or by allowing any combination of image tags and text.

This restriction simplifies the job of the XML parser when parsing mixed content (a mixture of tags and text). Because you lose some control when allowing mixed content, it is best to design DTDs such that all elements contain either other elements or nothing but text.

NOTE

Actually, it isn't quite true that you can specify arbitrary regular expressions of elements in a DTD rule. An XML parser may reject certain complex rule sets that lead to "nondeterministic" parsing. For example, a regular expression ((x,y)|(x,z)) is nondeterministic. When the parser sees x, it doesn't know which of the two alternatives to take. This expression can be rewritten in a deterministic form, as (x,(y|z)). However, some expressions can't be reformulated, such as ((x,y)*|x?). The Sun parser gives no warnings when presented with an ambiguous DTD. It simply picks the first matching alternative when parsing, which causes it to reject some correct inputs. Of course, the parser is well within its rights to do so because the XML standard allows a parser to assume that the DTD is unambiguous.

In practice, this isn't an issue over which you should lose sleep, since most DTDs are so simple that you never run into ambiguity problems.


You also specify rules to describe the legal attributes of elements. The general syntax is


<!ATTLIST element attribute type default>

Table 12-2 shows the legal attribute types, and Table 12-3 shows the syntax for the defaults.

Table 12-2. Attribute Types

Type

Meaning

CDATA

Any character string

(A1|A2| . . . |An)

One of the string attributes A1 A2 . . . |An

NMTOKEN, NMTOKENS

One or more name tokens

ID

A unique ID

IDREF, IDREFS

One or more references to a unique ID

ENTITY, ENTITIES

One or more unparsed entities


Table 12-3. Attribute Defaults

Default

Meaning

#REQUIRED

Attribute is required.

#IMPLIED

Attribute is optional.

A

Attribute is optional; the parser reports it to be A if it is not specified.

#FIXED A

The attribute must either be unspecified or A; in either case, the parser reports it to be A.


Here are two typical attribute specifications:

 <!ATTLIST font style (plain|bold|italic|bold-italic) "plain"> <!ATTLIST size unit CDATA #IMPLIED> 

The first specification describes the style attribute of a font element. There are four legal attribute values, and the default value is plain. The second specification expresses that the unit attribute of the size element can contain any character data sequence.

NOTE

We generally recommend the use of elements, not attributes, to describe data. Following that recommendation, the font style should be a separate element, such as <font><style>plain</style>...</font>. However, attributes have an undeniable advantage for enumerated types since the parser can verify that the values are legal. For example, if the font style is an attribute, the parser checks that it is one of the four allowed values, and it supplies a default if no value was given.


The handling of a CDATA attribute value is subtly different from the processing of #PCDATA that you have seen before, and quite unrelated to the <![CDATA[...]]> sections. The attribute value is first normalized, that is, the parser processes character and entity references (such as &#233; or &lt;) and replaces whitespace with spaces.

An NMTOKEN (or name token) is similar to CDATA, but most non-alphanumeric characters and internal whitespace are disallowed, and the parser removes leading and trailing whitespace. NMTOKENS is a whitespace-separated list of name tokens.

The ID construct is quite useful. An ID is a name token that must be unique in the documentthe parser checks the uniqueness. You will see an application in the next sample program. An IDREF is a reference to an ID that exists in the same documentwhich the parser also checks. IDREFS is a whitespace-separated list of ID references.

An ENTITY attribute value refers to an "unparsed external entity." That is a holdover from SGML that is rarely used in practice. The annotated XML specification at http://www.xml.com/axml/axml.html has an example.

A DTD can also define entities, or abbreviations that are replaced during parsing. You can find a good example for the use of entities in the user interface descriptions for the Mozilla/Netscape 6 browser. Those descriptions are formatted in XML and contain entity definitions such as

 <!ENTITY back.label "Back"> 

Elsewhere, text can contain an entity reference, for example:

 <menuitem label="&back.label;"/> 

The parser replaces the entity reference with the replacement string. For internationalization of the application, only the string in the entity definition needs to be changed. Other uses of entities are more complex and less commonly used. Look at the XML specification for details.

This concludes the introduction to DTDs. Now that you have seen how to use DTDs, you can configure your parser to take advantage of them. First, tell the document builder factory to turn on validation.

 factory.setValidating(true); 

All builders produced by this factory validate their input against a DTD. The most useful benefit of validation is to ignore whitespace in element content. For example, consider the XML fragment

 <font>    <name>Helvetica</name>    <size>36</size> </font> 

A nonvalidating parser reports the whitespace between the font, name, and size elements because it has no way of knowing if the children of font are

 (name,size) (#PCDATA,name,size)* 

or perhaps

 ANY 

Once the DTD specifies that the children are (name,size), the parser knows that the whitespace between them is not text. Call

 factory.setIgnoringElementContentWhitespace(true); 

and the builder will stop reporting the whitespace in text nodes. That means you can now rely on the fact that a font node has two children. You no longer need to program a tedious loop:

 for (int i = 0; i < children.getLength(); i++) {    Node child = children.item(i);    if (child instanceof Element)    {       Element childElement = (Element) child;       if (childElement.getTagName().equals("name")) . . .       else if (childElement.getTagName().equals("size")) . . .   } } 

Instead, you can simply access the first and second child:

 Element nameElement = (Element) children.item(0); Element sizeElement = (Element) children.item(1); 

That is why DTDs are so useful. You don't overload your program with rule checking codethe parser has already done that work by the time you get the document.

TIP

Many programmers who start using XML are uncomfortable with validation and end up analyzing the DOM tree on the fly. If you need to convince colleagues of the benefit of using validated documents, show them the two coding alternativesit should win them over.


When the parser reports an error, your application will want to do something about itlog it, show it to the user, or throw an exception to abandon the parsing. Therefore, you should install an error handler whenever you use validation. Supply an object that implements the ErrorHandler interface. That interface has three methods:

 void warning(SAXParseException exception) void error(SAXParseException exception) void fatalError(SAXParseException exception) 

You install the error handler with the setErrorHandler method of the DocumentBuilder class:

 builder.setErrorHandler(handler); 


 javax.xml.parsers.DocumentBuilder 1.4 

  • void setEntityResolver(EntityResolver resolver)

    sets the resolver to locate entities that are referenced in the XML documents to be parsed.

  • void setErrorHandler(ErrorHandler handler)

    sets the handler to report errors and warnings that occur during parsing.


 org.xml.sax.EntityResolver 1.4 

  • public InputSource resolveEntity(String publicID, String systemID)

    returns an input source that contains the data referenced by the given ID(s), or null to indicate that this resolver doesn't know how to resolve the particular name. The publicID parameter may be null if no public ID was supplied.


 org.xml.sax.InputSource 1.4 

  • InputSource(InputStream in)

  • InputSource(Reader in)

  • InputSource(String systemID)

    construct an input source from a stream, reader, or system ID (usually a relative or absolute URL).


 org.xml.sax.ErrorHandler 1.4 

  • void fatalError(SAXParseException exception)

  • void error(SAXParseException exception)

  • void warning(SAXParseException exception)

    Override these methods to provide handlers for fatal errors, nonfatal errors, and warnings.


 org.xml.sax.SAXParseException 1.4 

  • int getLineNumber()

  • int getColumnNumber()

    return the line and column number of the end of the processed input that caused the exception.


 javax.xml.parsers.DocumentBuilderFactory 1.4 

  • boolean isValidating()

  • void setValidating(boolean value)

    are the "validating" property of the factory. If set to true, the parsers that this factory generates validate their input.

  • boolean isIgnoringElementContentWhitespace()

  • void setIgnoringElementContentWhitespace(boolean value)

    are the "ignoringElementContentWhitespace" property of the factory. If set to true, the parsers that this factory generates ignore whitespace text between element nodes that don't have mixed content (i.e., a mixture of elements and #PCDATA).

XML Schema

Because XML Schema is quite a bit more complex than the DTD syntax, we cover only the basics. For more information, we recommend the tutorial at http://www.w3.org/TR/xmlschema-0.

To reference a Schema file in a document, you add attributes to the root element, for example:

 <?xml version="1.0"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:noNamespaceSchemaLocation="config.xsd"> . . . </configuration> 

This declaration states that the schema file config.xsd should be used to validate the document. If your document uses namespaces, the syntax is a bit more complexsee the XML Schema tutorial for details. (The prefix xsi is a namespace aliassee page 922 for more information about namespaces.)

A schema defines a type for each element. The type can be a simple typea string with formatting restrictionsor a complex type. Some simple types are built into XML Schema, including

 xsd:string xsd:int xsd:boolean 

NOTE

We use the prefix xsd: to denote the XSL Schema Definition namespace. Some authors use the prefix xs: instead.


You can define your own simple types. For example, here is an enumerated type:

 <xsd:simpleType name="StyleType">    <xsd:restriction base="xsd:string">       <xsd:enumeration value="PLAIN" />       <xsd:enumeration value="BOLD" />       <xsd:enumeration value="ITALIC" />       <xsd:enumeration value="BOLD_ITALIC" />    </xsd:restriction> </xsd:simpleType> 

When you define an element, you specify its type:

 <xsd:element name="name" type="xsd:string"/> <xsd:element name="size" type="xsd:int"/> <xsd:element name="style" type="StyleType"/> 

The type constrains the element content. For example, the elements

 <size>10</size> <style>PLAIN</style> 

will validate correctly, but the elements

 <size>default</size> <style>SLANTED</style> 

will be rejected by the parser.

You can compose types into complex types, for example:

 <xsd:complexType name="FontType">    <xsd:sequence>       <xsd:element ref="name"/>       <xsd:element ref="size"/>       <xsd:element ref="style"/>    </xsd:sequence> </xsd:complexType> 

A FontType is a sequence of name, size, and style elements. In this type definition, we use the ref attribute and refer to definitions that are located elsewhere in the schema. You can also nest definitions, like this:

 <xsd:complexType name="FontType">    <xsd:sequence>       <xsd:element name="name" type="xsd:string"/>       <xsd:element name="size" type="xsd:int"/>       <xsd:element name="style" type="StyleType">          <xsd:simpleType>             <xsd:restriction base="xsd:string">                <xsd:enumeration value="PLAIN" />                <xsd:enumeration value="BOLD" />                <xsd:enumeration value="ITALIC" />                <xsd:enumeration value="BOLD_ITALIC" />             </xsd:restriction>          </xsd:simpleType>       </xsd:element>    </xsd:sequence> </xsd:complexType> 

Note the anonymous type definition of the style element.

The xsd:sequence construct is the equivalent of the concatenation notation in DTDs. The xsd:choice construct is the equivalent of the | operator. For example,

 <xsd:complexType name="contactinfo">    <xsd:choice>       <xsd:element ref="email"/>       <xsd:element ref="phone"/>    </xsd:choice> </xsd:complexType> 

This is the equivalent of the DTD type email|phone.

To allow repeated elements, you use the minoccurs and maxoccurs attributes. For example, the equivalent of the DTD type item* is

 <xsd:element name="item" type=". . ." minoccurs="0" maxoccurs="unbounded"> 

To specify attributes, add xsd:attribute elements to complexType definitions:

 <xsd:element name="size">    <xsd:complexType>       . . .       <xsd:attribute name="unit" type="xsd:string" use="optional" default="cm"/>    </xsd:complexType> </xsd:element> 

This is the equivalent of the DTD statement

 <!ATTLIST size unit CDATA #IMPLIED "cm"> 

You enclose element and type definitions of your schema inside an xsd:schema element:

 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">    . . . </xsd:schema> 

Parsing an XML file with a schema is similar to parsing a file with a DTD, but with three differences:

  1. You need to turn on support for namespaces, even if you don't use them in your XML files.

     factory.setNamespaceAware(true); 

  2. You need to prepare the factory for handling schemas, with the following magic incantation:

     final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); 

  3. At least in JDK 5.0, the parser does not discard element content whitespace. See the code in Example 12-4 for a workaround.

NOTE

This is definitely a bug, even though bug report #4867706 classifies it as a request for enhancement.


A Practical Example

In this section, we work through a practical example that shows the use of XML in a realistic setting. Recall from Volume 1, Chapter 9 that the GridBagLayout is the most useful layout manager for Swing components. However, it is feared not just for its complexity but also for the programming tedium. It would be much more convenient to put the layout instructions into a text file instead of producing large amounts of repetitive code. In this section, you see how to use XML to describe a grid bag layout and how to parse the layout files.

A grid bag is made up of rows and columns, very similar to an HTML table. Similar to an HTML table, we describe it as a sequence of rows, each of which contains cells:

 <gridbag>    <row>       <cell>...</cell>       <cell>...</cell>       . . .     </row>    <row>       <cell>...</cell>       <cell>...</cell>       . . .     </row>    . . . </gridbag> 

The gridbag.dtd specifies these rules:

 <!ELEMENT gridbag (row)*> <!ELEMENT row (cell)*> 

Some cells can span multiple rows and columns. In the grid bag layout, that is achieved by setting the gridwidth and gridheight constraints to values larger than 1. We use attributes of the same name:

 <cell gridwidth="2" gridheight="2"> 

Similarly, we use attributes for the other grid bag constraints fill, anchor, gridx, gridy, weightx, weighty, ipadx, and ipady. (We don't handle the insets constraint because its value is not a simple type, but it would be straightforward to support it.) For example,

 <cell fill="HORIZONTAL" anchor="NORTH"> 

For most of these attributes, we provide the same defaults as the GridBagConstraints default constructor:

 <!ATTLIST cell gridwidth CDATA "1"> <!ATTLIST cell gridheight CDATA "1"> <!ATTLIST cell fill (NONE|BOTH|HORIZONTAL|VERTICAL) "NONE"> <!ATTLIST cell anchor (CENTER|NORTH|NORTHEAST|EAST    |SOUTHEAST|SOUTH|SOUTHWEST|WEST|NORTHWEST) "CENTER"> . . . 

The gridx and gridy values get special treatment because it would be tedious and somewhat error prone to specify them by hand. Supplying them is optional:

 <!ATTLIST cell gridx CDATA #IMPLIED> <!ATTLIST cell gridy CDATA #IMPLIED> 

If they are not supplied, the program determines them according to the following heuristic: In column 0, the default gridx is 0. Otherwise, it is the preceding gridx plus the preceding gridwidth. The default gridy is always the same as the row number. Thus, you don't have to specify gridx and gridy in the most common cases, in which a component spans multiple rows. But if a component spans multiple columns, then you must specify gridx whenever you skip over that component.

NOTE

Grid bag experts may wonder why we don't use the RELATIVE and REMAINDER mechanism to let the grid bag layout automatically determine the gridx and gridy positions. We tried, but no amount of fussing would produce the layout of the font dialog example of Volume 1. Reading through the GridBagLayout source code, it is apparent that the algorithm just won't do the heavy lifting that would be required to recover the absolute positions.


The program parses the attributes and sets the grid bag constraints. For example, to read the grid width, the program contains a single statement:

 constraints.gridwidth = Integer.parseInt(e.getAttribute("gridwidth")); 

The program need not worry about a missing attribute because the parser automatically supplies the default value if no other value was specified in the document.

To test whether a gridx or gridy attribute was specified, we call the getAttribute method and check if it returns the empty string:

 String value = e.getAttribute("gridy"); if (value.length() == 0) // use default    constraints.gridy = r; else    constraints.gridx = Integer.parseInt(value); 

A cell can contain any component, but we found it convenient to allow the user to specify more general beans. That lets us specify noncomponent types such as borders. A bean is defined by a class name and zero or more properties:

 <!ELEMENT bean (class, property*)> <!ELEMENT class (#PCDATA)> 

As always when using beans, we assume that the bean class has a default constructor.

A property contains a name and a value.

 <!ELEMENT property (name, value)> <!ELEMENT name (#PCDATA)> 

The value is an integer, Boolean, string, or another bean:

 <!ELEMENT value (int|string|boolean|bean)> <!ELEMENT int (#PCDATA)> <!ELEMENT string (#PCDATA)> <!ELEMENT boolean (#PCDATA)> 

Here is a typical example, a JLabel whose text property is set to the string "Face: ".

 <bean>   <class>javax.swing.JLabel</class>   <property>     <name>text</name>     <value><string>Face: </string></value>   </property> </bean> 

It seems like a bother to surround a string with the <string> tag. Why not just use #PCDATA for strings and leave the tags for the other types? Because then we would need to use mixed content and weaken the rule for the value element to

 <!ELEMENT value (#PCDATA|int|boolean|bean)*> 

However, that rule would allow an arbitrary mixture of text and tags.

The program sets a property by using the BeanInfo class. BeanInfo enumerates the property descriptors of the bean. We search for the property with the matching name, and then call its setter method with the supplied value.

When our program reads in a user interface description, it has enough information to construct and arrange the user interface components. But, of course, the interface is not aliveno event listeners have been attached. To add event listeners, we have to locate the components. For that reason, we support an optional attribute of type ID for each bean:

 <!ATTLIST bean id ID #IMPLIED> 

For example, here is a combo box with an ID:

 <bean >   <class>javax.swing.JComboBox</class> </bean> 

Recall that the parser checks that IDs are unique.

A programmer can attach event handlers like this:

 gridbag = new GridBagPane("fontdialog.xml"); setContentPane(gridbag); JComboBox face = (JComboBox) gridbag.get("face"); face.addListener(listener); 

NOTE

In this example, we only use XML to describe the component layout and leave it to programmers to attach the event handlers in the Java code. You could go a step further and add the code to the XML description. The most promising approach is to use a scripting language such as JavaScript for the code. If you want to add that enhancement, check out the Rhino interpreter at http://www.mozilla.org/rhino.


The program in Example 12-2 shows how to use the GridBagPane class to do all the boring work of setting up the grid bag layout. The layout is defined in Example 12-3. Figure 12-4 shows the result. The program only initializes the combo boxes (which are too complex for the bean property-setting mechanism that the GridBagPane supports) and attaches event listeners. The GridBagPane class in Example 12-4 parses the XML file, constructs the components, and lays them out. Example 12-5 shows the DTD.

Figure 12-4. A font dialog defined by an XML layout


The program can also process a schema instead of a DTD if you launch it with

 java GridBagTest fontdialog-schema.xml 

Example 12-6 contains the schema.

This example is a typical use of XML. The XML format is robust enough to express complex relationships. The XML parser adds value by taking over the routine job of validity checking and supplying defaults.

Example 12-2. GridBagTest.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import javax.swing.*;  4.  5. /**  6.    This program shows how to use an XML file to describe  7.    a gridbag layout  8. */  9. public class GridBagTest 10. { 11.    public static void main(String[] args) 12.    { 13.       String filename = args.length == 0 ? "fontdialog.xml" : args[0]; 14.       JFrame frame = new FontFrame(filename); 15.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 16.       frame.setVisible(true); 17.    } 18. } 19. 20. /** 21.    This frame contains a font selection dialog that is described by an XML file. 22.    @param filename the file containing the user interface components for the dialog. 23. */ 24. class FontFrame extends JFrame 25. { 26.    public FontFrame(String filename) 27.    { 28.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 29.       setTitle("GridBagTest"); 30. 31.       gridbag = new GridBagPane(filename); 32.       add(gridbag); 33. 34.       face = (JComboBox) gridbag.get("face"); 35.       size = (JComboBox) gridbag.get("size"); 36.       bold = (JCheckBox) gridbag.get("bold"); 37.       italic = (JCheckBox) gridbag.get("italic"); 38. 39.       face.setModel(new DefaultComboBoxModel(new Object[] 40.          { 41.             "Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput" 42.          })); 43. 44.       size.setModel(new DefaultComboBoxModel(new Object[] 45.          { 46.             "8", "10", "12", "15", "18", "24", "36", "48" 47.          })); 48. 49.       ActionListener listener = new 50.          ActionListener() 51.          { 52.             public void actionPerformed(ActionEvent event) { setSample(); } 53.          }; 54. 55.       face.addActionListener(listener); 56.       size.addActionListener(listener); 57.       bold.addActionListener(listener); 58.       italic.addActionListener(listener); 59. 60.       setSample(); 61.    } 62. 63.    /** 64.       This method sets the text sample to the selected font. 65.    */ 66.    public void setSample() 67.    { 68.       String fontFace = (String) face.getSelectedItem(); 69.       int fontSize = Integer.parseInt((String) size.getSelectedItem()); 70.       JTextArea sample = (JTextArea) gridbag.get("sample"); 71.       int fontStyle 72.          = (bold.isSelected() ? Font.BOLD : 0) 73.          + (italic.isSelected() ? Font.ITALIC : 0); 74. 75.       sample.setFont(new Font(fontFace, fontStyle, fontSize)); 76.       sample.repaint(); 77.    } 78. 79.    private GridBagPane gridbag; 80.    private JComboBox face; 81.    private JComboBox size; 82.    private JCheckBox bold; 83.    private JCheckBox italic; 84.    private static final int DEFAULT_WIDTH = 400; 85.    private static final int DEFAULT_HEIGHT = 400; 86. } 

Example 12-3. fontdialog.xml
  1. <?xml version="1.0"?>  2. <!DOCTYPE gridbag SYSTEM "gridbag.dtd">  3. <gridbag>  4.    <row>  5.       <cell anchor="EAST">  6.          <bean>  7.             <class>javax.swing.JLabel</class>  8.             <property>  9.                <name>text</name> 10.                <value><string>Face: </string></value> 11.             </property> 12.          </bean> 13.       </cell> 14.       <cell fill="HORIZONTAL" weightx="100"> 15.          <bean > 16.             <class>javax.swing.JComboBox</class> 17.          </bean> 18.       </cell> 19.       <cell gridheight="4" fill="BOTH" weightx="100" weighty="100"> 20.          <bean > 21.             <class>javax.swing.JTextArea</class> 22.             <property> 23.                <name>text</name> 24.                <value><string>The quick brown fox jumps over the lazy dog</string></value> 25.             </property> 26.             <property> 27.                <name>editable</name> 28.                <value><boolean>false</boolean></value> 29.             </property> 30.             <property> 31.                <name>lineWrap</name> 32.                <value><boolean>true</boolean></value> 33.             </property> 34.             <property> 35.                <name>border</name> 36.                <value> 37.                   <bean> 38.                      <class>javax.swing.border.EtchedBorder</class> 39.                   </bean> 40.                </value> 41.             </property> 42.          </bean> 43.       </cell> 44.    </row> 45.    <row> 46.       <cell anchor="EAST"> 47.          <bean> 48.             <class>javax.swing.JLabel</class> 49.             <property> 50.                <name>text</name> 51.                <value><string>Size: </string></value> 52.             </property> 53.          </bean> 54.       </cell> 55.       <cell fill="HORIZONTAL" weightx="100"> 56.          <bean > 57.             <class>javax.swing.JComboBox</class> 58.          </bean> 59.       </cell> 60.    </row> 61.    <row> 62.       <cell gridwidth="2" weighty="100"> 63.          <bean > 64.             <class>javax.swing.JCheckBox</class> 65.             <property> 66.                <name>text</name> 67.                <value><string>Bold</string></value> 68.             </property> 69.          </bean> 70.       </cell> 71.    </row> 72.    <row> 73.       <cell gridwidth="2" weighty="100"> 74.          <bean > 75.             <class>javax.swing.JCheckBox</class> 76.             <property> 77.                <name>text</name> 78.                <value><string>Italic</string></value> 79.             </property> 80.          </bean> 81.       </cell> 82.    </row> 83. </gridbag> 

Example 12-4. GridBagPane.java
  1. import java.awt.*;  2. import java.beans.*;  3. import java.io.*;  4. import java.lang.reflect.*;  5. import javax.swing.*;  6. import javax.xml.parsers.*;  7. import org.w3c.dom.*;  8. import org.xml.sax.*;  9.  10. import org.w3c.dom.ls.*;  11. import org.w3c.dom.bootstrap.*;  12.  13. /**  14.    This panel uses an XML file to describe its  15.    components and their grid bag layout positions.  16. */  17. public class GridBagPane extends JPanel  18. {  19.    /**  20.       Constructs a grid bag pane.  21.       @param filename the name of the XML file that  22.       describes the pane's components and their positions  23.    */  24.    public GridBagPane(String filename)  25.    {  26.       setLayout(new GridBagLayout());  27.       constraints = new GridBagConstraints();  28.  29.       try  30.       {  31.          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  32.          factory.setValidating(true);  33.  34.          if (filename.contains("-schema"))  35.          {  36.             factory.setNamespaceAware(true);  37.             final String JAXP_SCHEMA_LANGUAGE =  38.                "http://java.sun.com/xml/jaxp/properties/schemaLanguage";  39.             final String W3C_XML_SCHEMA =  40.                "http://www.w3.org/2001/XMLSchema";  41.             factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);  42.          }  43.  44.          factory.setIgnoringElementContentWhitespace(true);  45.  46.          DocumentBuilder builder = factory.newDocumentBuilder();  47.          Document doc = builder.parse(new File(filename));  48.  49.          if (filename.contains("-schema")) // workaround for bug #4867706  50.          {  51.             int count = removeWhitespace(doc.getDocumentElement());  52.             System.out.println(count + " whitespace nodes removed.");  53.          }  54.  55.          parseGridbag(doc.getDocumentElement());  56.       }  57.       catch (Exception e)  58.       {  59.          e.printStackTrace();  60.       }  61.    }  62.  63.    /**  64.       Removes whitespace from element content  65.       @param e the root element  66.       @return the number of whitespace nodes that were removed.  67.    */  68.    private int removeWhitespace(Element e)  69.    {  70.       NodeList children = e.getChildNodes();  71.       if (children.getLength() <= 1) return 0;  72.       int count = 0;  73.       for (int i = children.getLength() - 1; i >= 0; i--)  74.       {  75.          Node child = children.item(i);  76.          if (child instanceof Text && ((Text) child).getData().trim().length() == 0)  77.          {  78.             e.removeChild(child);  79.             count++;  80.          }  81.          else if (child instanceof Element)  82.             count += removeWhitespace((Element) child);  83.       }  84.       return count;  85.    }  86.  87.    /**  88.       Gets a component with a given name  89.       @param name a component name  90.       @return the component with the given name, or null if  91.       no component in this grid bag pane has the given name  92.    */  93.    public Component get(String name)  94.    {  95.       Component[] components = getComponents();  96.       for (int i = 0; i < components.length; i++)  97.       {  98.          if (components[i].getName().equals(name))  99.             return components[i]; 100.       } 101.       return null; 102.    } 103. 104.    /** 105.       Parses a gridbag element. 106.       @param e a gridbag element 107.    */ 108.    private void parseGridbag(Element e) 109.    { 110.       NodeList rows = e.getChildNodes(); 111.       for (int i = 0; i < rows.getLength(); i++) 112.       { 113.          Element row = (Element) rows.item(i); 114.          NodeList cells = row.getChildNodes(); 115.          for (int j = 0; j < cells.getLength(); j++) 116.          { 117.             Element cell = (Element) cells.item(j); 118.             parseCell(cell, i, j); 119.          } 120.       } 121.    } 122. 123.    /** 124.       Parses a cell element. 125.       @param e a cell element 126.       @param r the row of the cell 127.       @param c the column of the cell 128.    */ 129.    private void parseCell(Element e, int r, int c) 130.    { 131.       // get attributes 132. 133.       String value = e.getAttribute("gridx"); 134.       if (value.length() == 0) // use default 135.       { 136.          if (c == 0) constraints.gridx = 0; 137.          else constraints.gridx += constraints.gridwidth; 138.       } 139.       else 140.          constraints.gridx = Integer.parseInt(value); 141. 142.       value = e.getAttribute("gridy"); 143.       if (value.length() == 0) // use default 144.          constraints.gridy = r; 145.       else 146.          constraints.gridy = Integer.parseInt(value); 147. 148.       constraints.gridwidth = Integer.parseInt(e.getAttribute("gridwidth")); 149.       constraints.gridheight = Integer.parseInt(e.getAttribute("gridheight")); 150.       constraints.weightx = Integer.parseInt(e.getAttribute("weightx")); 151.       constraints.weighty = Integer.parseInt(e.getAttribute("weighty")); 152.       constraints.ipadx = Integer.parseInt(e.getAttribute("ipadx")); 153.       constraints.ipady = Integer.parseInt(e.getAttribute("ipady")); 154. 155.       // use reflection to get integer values of static fields 156.       Class cl = GridBagConstraints.class; 157. 158.       try 159.       { 160.          String name = e.getAttribute("fill"); 161.          Field f = cl.getField(name); 162.          constraints.fill = f.getInt(cl); 163. 164.          name = e.getAttribute("anchor"); 165.          f = cl.getField(name); 166.          constraints.anchor = f.getInt(cl); 167.       } 168.       catch (Exception ex) // the reflection methods can throw various exceptions 169.       { 170.          ex.printStackTrace(); 171.       } 172. 173.       Component comp = (Component) parseBean((Element) e.getFirstChild()); 174.       add(comp, constraints); 175.    } 176. 177.    /** 178.       Parses a bean element. 179.       @param e a bean element 180.    */ 181.    private Object parseBean(Element e) 182.    { 183.       try 184.       { 185.          NodeList children = e.getChildNodes(); 186.          Element classElement = (Element) children.item(0); 187.          String className = ((Text) classElement.getFirstChild()).getData(); 188. 189.          Class cl = Class.forName(className); 190. 191.          Object obj = cl.newInstance(); 192. 193.          if (obj instanceof Component) 194.             ((Component) obj).setName(e.getAttribute("id")); 195. 196.          for (int i = 1; i < children.getLength(); i++) 197.          { 198.             Node propertyElement = children.item(i); 199.             Element nameElement = (Element) propertyElement.getFirstChild(); 200.             String propertyName = ((Text) nameElement.getFirstChild()).getData(); 201. 202.             Element valueElement = (Element) propertyElement.getLastChild(); 203.             Object value = parseValue(valueElement); 204.             BeanInfo beanInfo = Introspector.getBeanInfo(cl); 205.             PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); 206.             boolean done = false; 207.             for (int j = 0; !done && j < descriptors.length; j++) 208.             { 209.                if (descriptors[j].getName().equals(propertyName)) 210.                { 211.                   descriptors[j].getWriteMethod().invoke(obj, value); 212.                   done = true; 213.                } 214.             } 215. 216.          } 217.          return obj; 218.       } 219.       catch (Exception ex) // the reflection methods can throw various exceptions 220.       { 221.          ex.printStackTrace(); 222.          return null; 223.       } 224.    } 225. 226.    /** 227.       Parses a value element. 228.       @param e a value element 229.    */ 230.    private Object parseValue(Element e) 231.    { 232.       Element child = (Element) e.getFirstChild(); 233.       if (child.getTagName().equals("bean")) return parseBean(child); 234.       String text = ((Text) child.getFirstChild()).getData(); 235.       if (child.getTagName().equals("int")) return new Integer(text); 236.       else if (child.getTagName().equals("boolean")) return new Boolean(text); 237.       else if (child.getTagName().equals("string")) return text; 238.       else return null; 239.    } 240. 241.    private GridBagConstraints constraints; 242. } 

Example 12-5. gridbag.dtd
  1. <!ELEMENT gridbag (row)*>  2. <!ELEMENT row (cell)*>  3. <!ELEMENT cell (bean)>  4. <!ATTLIST cell gridx CDATA #IMPLIED>  5. <!ATTLIST cell gridy CDATA #IMPLIED>  6. <!ATTLIST cell gridwidth CDATA "1">  7. <!ATTLIST cell gridheight CDATA "1">  8. <!ATTLIST cell weightx CDATA "0">  9. <!ATTLIST cell weighty CDATA "0"> 10. <!ATTLIST cell fill (NONE|BOTH|HORIZONTAL|VERTICAL) "NONE"> 11. <!ATTLIST cell anchor 12.    (CENTER|NORTH|NORTHEAST|EAST|SOUTHEAST|SOUTH|SOUTHWEST|WEST|NORTHWEST) "CENTER"> 13. <!ATTLIST cell ipadx CDATA "0"> 14. <!ATTLIST cell ipady CDATA "0"> 15. 16. <!ELEMENT bean (class, property*)> 17. <!ATTLIST bean id ID #IMPLIED> 18. 19. <!ELEMENT class (#PCDATA)> 20. <!ELEMENT property (name, value)> 21. <!ELEMENT name (#PCDATA)> 22. <!ELEMENT value (int|string|boolean|bean)> 23. <!ELEMENT int (#PCDATA)> 24. <!ELEMENT string (#PCDATA)> 25. <!ELEMENT boolean (#PCDATA)> 

Example 12-6. gridbag.xsd

[View full width]

  1. <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">  2.  3.    <xsd:element name="gridbag" type="GridBagType"/>  4.  5.    <xsd:element name="bean" type="BeanType"/>  6.  7.    <xsd:complexType name="GridBagType">  8.       <xsd:sequence>  9.          <xsd:element name="row" type="RowType" minOccurs="0" maxOccurs="unbounded"/> 10.       </xsd:sequence> 11.    </xsd:complexType> 12. 13.    <xsd:complexType name="RowType"> 14.       <xsd:sequence> 15.          <xsd:element name="cell" type="CellType" minOccurs="0" maxOccurs="unbounded"/> 16.       </xsd:sequence> 17.    </xsd:complexType> 18. 19.    <xsd:complexType name="CellType"> 20.       <xsd:sequence> 21.          <xsd:element ref="bean"/> 22.       </xsd:sequence> 23.       <xsd:attribute name="gridx" type="xsd:int" use="optional"/> 24.       <xsd:attribute name="gridy" type="xsd:int" use="optional"/> 25.       <xsd:attribute name="gridwidth" type="xsd:int" use="optional" default="1" /> 26.       <xsd:attribute name="gridheight" type="xsd:int" use="optional" default="1" /> 27.       <xsd:attribute name="weightx" type="xsd:int" use="optional" default="0" /> 28.       <xsd:attribute name="weighty" type="xsd:int" use="optional" default="0" /> 29.       <xsd:attribute name="fill" use="optional" default="NONE"> 30.         <xsd:simpleType> 31.           <xsd:restriction base="xsd:string"> 32.             <xsd:enumeration value="NONE" /> 33.             <xsd:enumeration value="BOTH" /> 34.             <xsd:enumeration value="HORIZONTAL" /> 35.             <xsd:enumeration value="VERTICAL" /> 36.           </xsd:restriction> 37.         </xsd:simpleType> 38.       </xsd:attribute> 39.       <xsd:attribute name="anchor" use="optional" default="CENTER"> 40.         <xsd:simpleType> 41.           <xsd:restriction base="xsd:string"> 42.             <xsd:enumeration value="CENTER" /> 43.             <xsd:enumeration value="NORTH" /> 44.             <xsd:enumeration value="NORTHEAST" /> 45.             <xsd:enumeration value="EAST" /> 46.             <xsd:enumeration value="SOUTHEAST" /> 47.             <xsd:enumeration value="SOUTH" /> 48.             <xsd:enumeration value="SOUTHWEST" /> 49.             <xsd:enumeration value="WEST" /> 50.             <xsd:enumeration value="NORTHWEST" /> 51.           </xsd:restriction> 52.         </xsd:simpleType> 53.       </xsd:attribute> 54.       <xsd:attribute name="ipady" type="xsd:int" use="optional" default="0" /> 55.       <xsd:attribute name="ipadx" type="xsd:int" use="optional" default="0" /> 56.    </xsd:complexType> 57. 58.    <xsd:complexType name="BeanType"> 59.       <xsd:sequence> 60.          <xsd:element name="class" type="xsd:string"/> 61.          <xsd:element name="property" type="PropertyType" minOccurs="0"  maxOccurs="unbounded"/> 62.       </xsd:sequence> 63.       <xsd:attribute name="id" type="xsd:ID" use="optional" /> 64.    </xsd:complexType> 65. 66.    <xsd:complexType name="PropertyType"> 67.       <xsd:sequence> 68.          <xsd:element name="name" type="xsd:string"/> 69.          <xsd:element name="value" type="ValueType"/> 70.       </xsd:sequence> 71.    </xsd:complexType> 72. 73.    <xsd:complexType name="ValueType"> 74.       <xsd:choice> 75.          <xsd:element ref="bean"/> 76.          <xsd:element name="int" type="xsd:int"/> 77.          <xsd:element name="string" type="xsd:string"/> 78.          <xsd:element name="boolean" type="xsd:boolean"/> 79.       </xsd:choice> 80.    </xsd:complexType> 81. </xsd:schema> 



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net