|
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
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 DefinitionsThere are several methods for supplying a DTD. You can include a DTD in an XML document like this:
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
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
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.
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
You also specify rules to describe the legal attributes of elements. The general syntax is
Table 12-2 shows the legal attribute types, and Table 12-3 shows the syntax for the defaults.
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
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 é or <) 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
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
org.xml.sax.EntityResolver 1.4
org.xml.sax.InputSource 1.4
org.xml.sax.ErrorHandler 1.4
org.xml.sax.SAXParseException 1.4
javax.xml.parsers.DocumentBuilderFactory 1.4
XML SchemaBecause 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
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:
NOTE
A Practical ExampleIn 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
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
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 layoutThe 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.java1. 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.xml1. <?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.java1. 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.dtd1. <!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> |
|