KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > nu > xom > DocType


1 /* Copyright 2002-2004 Elliotte Rusty Harold
2    
3    This library is free software; you can redistribute it and/or modify
4    it under the terms of version 2.1 of the GNU Lesser General Public
5    License as published by the Free Software Foundation.
6    
7    This library is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10    GNU Lesser General Public License for more details.
11    
12    You should have received a copy of the GNU Lesser General Public
13    License along with this library; if not, write to the
14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
15    Boston, MA 02111-1307 USA
16    
17    You can contact Elliotte Rusty Harold by sending e-mail to
18    elharo@metalab.unc.edu. Please include the word "XOM" in the
19    subject line. The XOM home page is located at http://www.xom.nu/
20 */

21
22 package nu.xom;
23
24
25 /**
26  * <p>
27  * Represents an XML document type declaration such as
28  * <code>&lt;!DOCTYPE book SYSTEM "docbookx.dtd"></code>.
29  * Note that this is not the same thing as a document
30  * type <em>definition</em> (DTD). XOM does not currently model
31  * the DTD. The document type declaration contains or points to
32  * the DTD, but it is not the DTD.
33  * </p>
34  *
35  * <p>
36  * A <code>DocType</code> object does not have any child
37  * nodes. It can be a child of a <code>Document</code>.
38  * </p>
39  *
40  * <p>
41  * Each <code>DocType</code> object has four <Code>String</code>
42  * properties, some of which may be null:
43  * </p>
44  *
45  * <ul>
46  * <li>The declared name of the root element (which
47  * does not necessarily match the actual root element name
48  * in an invalid document)</li>
49  * <li>The public identifier (which may be null)</li>
50  * <li>The system identifier (which may be null)</li>
51  * <li>The internal DTD subset (which may be null)</li>
52  *
53  * </ul>
54  *
55  * <p>
56  * The first three properties are read-write.
57  * The internal DTD subset is read-only.
58  * XOM fills it in when a document is read by a parser.
59  * However, it cannot be changed, because XOM cannot
60  * currently check that an internal DTD subset is well-formed.
61  * This restriction may be relaxed in a future version.
62  * </p>
63  *
64  * @author Elliotte Rusty Harold
65  * @version 1.0
66  *
67  */

68 public class DocType extends Node {
69
70     
71     private String JavaDoc rootName;
72     private String JavaDoc systemID;
73     private String JavaDoc publicID;
74     // Internal DTD subset purely for parsing
75
private String JavaDoc internalDTDSubset = "";
76
77     
78     /**
79      * <p>
80      * Creates a new document type declaration with a public ID
81      * and a system ID. It has the general form
82      * <code>&lt;!DOCTYPE rootElementName PUBLIC
83      * "publicID" "systemID"&gt;</code>.
84      * </p>
85      *
86      * @param rootElementName the name specified for the root element
87      * @param publicID the public ID of the external DTD subset
88      * @param systemID the URL of the external DTD subset
89      *
90      * @throws IllegalNameException if <code>rootElementName</code>
91      * is not a legal XML 1.0 name
92      * @throws IllegalDataException if <code>publicID</code> is not a
93      * legal XML 1.0 public identifier
94      * @throws MalformedURIException if the system ID is not a
95      * syntactically correct URI, or if it contains a fragment
96      * identifier
97      */

98     public DocType(
99       String JavaDoc rootElementName, String JavaDoc publicID, String JavaDoc systemID) {
100         _setRootElementName(rootElementName);
101         _setSystemID(systemID);
102         _setPublicID(publicID);
103     }
104
105     
106     /**
107      * <p>
108      * Creates a new document type declaration with a system ID
109      * but no public ID. It has the general form
110      * <code>&lt;!DOCTYPE rootElementName SYSTEM "systemID"&gt;</code>.
111      * </p>
112      *
113      * @param rootElementName the name specified for the root element
114      * @param systemID the URL of the external DTD subset
115      *
116      * @throws IllegalNameException if the rootElementName is not
117      * a legal XML 1.0 name
118      * @throws MalformedURIException if the system ID is not a
119      * syntactically correct URI, or if it contains a fragment
120      * identifier
121      */

122     public DocType(String JavaDoc rootElementName, String JavaDoc systemID) {
123         this(rootElementName, null, systemID);
124     }
125
126     
127     /**
128      * <p>
129      * Creates a new document type declaration with
130      * no public or system ID. It has the general form
131      * <code>&lt;!DOCTYPE rootElementName&gt;</code>.
132      * </p>
133      *
134      * @param rootElementName the name specified for the root element
135      *
136      * @throws IllegalNameException if the rootElementName is not
137      * a legal XML 1.0 name
138      */

139     public DocType(String JavaDoc rootElementName) {
140         this(rootElementName, null, null);
141     }
142
143     
144     /**
145      * <p>
146      * Creates a new <code>DocType</code> that's a copy of its
147      * argument. The copy has the same data but no parent document.
148      * </p>
149      *
150      * @param doctype the <code>DocType</code> to copy
151      */

152     public DocType(DocType doctype) {
153         this.internalDTDSubset = doctype.internalDTDSubset;
154         this.publicID = doctype.publicID;
155         this.systemID = doctype.systemID;
156         this.rootName = doctype.rootName;
157     }
158     
159     
160     private DocType() {}
161     
162     static DocType build(
163       String JavaDoc rootElementName, String JavaDoc publicID, String JavaDoc systemID) {
164         DocType result = new DocType();
165         result.publicID = publicID;
166         result.systemID = systemID;
167         result.rootName = rootElementName;
168         return result;
169     }
170
171
172     /**
173      * <p>
174      * Returns the name the document type declaration specifies
175      * for the root element. In an invalid document, this may
176      * not be the same as the actual root element name.
177      * </p>
178      *
179      * @return the declared name of the root element
180      */

181     public final String JavaDoc getRootElementName() {
182         return rootName;
183     }
184
185     
186     /**
187      * <p>
188      * Sets the name the document type declaration specifies
189      * for the root element. In an invalid document, this may
190      * not be the same as the actual root element name.
191      * </p>
192      *
193      * @param name the root element name given by
194      * the document type declaration
195      *
196      * @throws IllegalNameException if the root element name is not
197      * a legal XML 1.0 name
198      */

199     public void setRootElementName(String JavaDoc name) {
200         _setRootElementName(name);
201     }
202
203     
204     private void _setRootElementName(String JavaDoc name) {
205         Verifier.checkXMLName(name);
206         this.rootName = name;
207     }
208
209
210     /**
211      * <p>
212      * Returns the complete internal DTD subset.
213      * White space may not be preserved completely accurately,
214      * but all declarations should be in place.
215      * </p>
216      *
217      * @return the internal DTD subset
218      */

219     public final String JavaDoc getInternalDTDSubset() {
220         return internalDTDSubset;
221     }
222
223     
224     final void setInternalDTDSubset(String JavaDoc internalSubset) {
225         this.internalDTDSubset = internalSubset;
226     }
227
228     
229     /**
230      * <p>
231      * Returns the public ID of the external DTD subset.
232      * This is null if there is no external DTD subset
233      * or if it does not have a public identifier.
234      * </p>
235      *
236      * @return the public ID of the external DTD subset.
237      */

238     public final String JavaDoc getPublicID() {
239         return publicID;
240     }
241
242     /**
243      * <p>
244      * Sets the public ID for the external DTD subset.
245      * This can only be set after a system ID has been set,
246      * because XML requires that all document type declarations
247      * with public IDs have system IDs. Passing null removes
248      * the public ID.
249      * </p>
250      *
251      * @param id the public identifier of the external DTD subset
252      *
253      * @throws IllegalDataException if the public ID does not satisfy
254      * the rules for public IDs in XML 1.0
255      * @throws WellformednessException if no system ID has been set
256      */

257     public void setPublicID(String JavaDoc id) {
258         _setPublicID(id);
259     }
260
261     
262     private void _setPublicID(String JavaDoc id) {
263         
264         if (systemID == null && id != null) {
265             throw new WellformednessException(
266               "Cannot have a public ID without a system ID"
267             );
268         }
269         
270         if (id != null) {
271             int length = id.length();
272             if (length != 0) {
273                 if (Verifier.isXMLSpaceCharacter(id.charAt(0))) {
274                     throw new IllegalDataException("Initial white space "
275                       + "in public IDs is not round trippable.");
276                 }
277                 if (Verifier.isXMLSpaceCharacter(id.charAt(length - 1))) {
278                     throw new IllegalDataException("Trailing white space "
279                       + "in public IDs is not round trippable.");
280                 }
281                 
282                 for (int i = 0; i < length; i++) {
283                     char c = id.charAt(i);
284                     if (!isXMLPublicIDCharacter(c)) {
285                         throw new IllegalDataException("The character 0x"
286                           + Integer.toHexString(c)
287                           + " is not allowed in public IDs");
288                     }
289                     if (c == ' ' && id.charAt(i-1) == ' ') {
290                         throw new IllegalDataException("Multiple consecutive "
291                           + "spaces in public IDs are not round trippable.");
292                     }
293                 }
294             }
295         }
296         this.publicID = id;
297         
298     }
299
300
301     /**
302      * <p>
303      * Returns the system ID of the external DTD subset.
304      * This is a URL. It is null if there is no external DTD subset.
305      * </p>
306      *
307      * @return the URL for the external DTD subset.
308      */

309     public final String JavaDoc getSystemID() {
310         return systemID;
311     }
312
313     
314     /**
315      * <p>
316      * Sets the system ID for the external DTD subset.
317      * This must be a a relative or absolute URI with no fragment
318      * identifier. Passing null removes the system ID, but only if
319      * the public ID has been removed first. Otherwise,
320      * passing null causes a <code>WellformednessException</code>.
321      * </p>
322      *
323      * @param id the URL of the external DTD subset
324      *
325      * @throws MalformedURIException if the system ID is not a
326      * syntactically correct URI, or if it contains a fragment
327      * identifier
328      * @throws WellformednessException if the public ID is non-null
329      * and you attempt to remove the system ID
330      */

331     public void setSystemID(String JavaDoc id) {
332         _setSystemID(id);
333     }
334
335     
336     private void _setSystemID(String JavaDoc id) {
337         
338         if (id == null && publicID != null) {
339             throw new WellformednessException(
340              "Cannot remove system ID without removing public ID first"
341             );
342         }
343
344         if (id != null) {
345             
346             Verifier.checkURIReference(id);
347             
348             if (id.indexOf('#') != -1) {
349                 MalformedURIException ex = new MalformedURIException(
350                  "System literals cannot contain fragment identifiers"
351                 );
352                 ex.setData(id);
353                 throw ex;
354             }
355         }
356         
357         this.systemID = id;
358         
359     }
360
361
362     /**
363      * <p>
364      * Returns the empty string. XPath 1.0 does not define a value
365      * for document type declarations.
366      * </p>
367      *
368      * @return an empty string
369      */

370     public final String JavaDoc getValue() {
371         return "";
372     }
373
374
375     /**
376      * <p>
377      * Throws <code>IndexOutOfBoundsException</code> because
378      * document type declarations do not have children.
379      * </p>
380      *
381      * @return never returns because document type declarations do not
382      * have children. Always throws an exception.
383      *
384      * @param position the index of the child node to return
385      *
386      * @throws IndexOutOfBoundsException because document type declarations
387      * do not have children
388      */

389     public final Node getChild(int position) {
390         throw new IndexOutOfBoundsException JavaDoc(
391           "LeafNodes do not have children");
392     }
393
394     
395     /**
396      * <p>
397      * Returns 0 because document type declarations do not have
398      * children.
399      * </p>
400      *
401      * @return zero
402      */

403     public final int getChildCount() {
404         return 0;
405     }
406     
407     
408     /**
409      * <p>
410      * Returns a string form of the
411      * <code>DocType</code> suitable for debugging
412      * and diagnosis. It deliberately does not return
413      * an actual XML document type declaration.
414      * </p>
415      *
416      * @return a string representation of this object
417      */

418     public final String JavaDoc toString() {
419         return "[" + getClass().getName() + ": " + rootName + "]";
420     }
421
422     
423     /**
424      * <p>
425      * Returns a copy of this <code>DocType</code>
426      * which has the same system ID, public ID, root element name,
427      * and internal DTD subset, but does not belong to a document.
428      * Thus, it can be inserted into a different document.
429      * </p>
430      *
431      * @return a deep copy of this <code>DocType</code>
432      * that is not part of a document
433      */

434     public Node copy() {
435         return new DocType(this);
436     }
437     
438
439     /**
440      * <p>
441      * Returns a string containing the actual XML
442      * form of the document type declaration represented
443      * by this object. For example,
444      * <code>&lt;!DOCTYPE book SYSTEM "docbookx.dtd"></code>.
445      * </p>
446      *
447      * @return a <code>String</code> containing
448      * an XML document type declaration
449      */

450     public final String JavaDoc toXML() {
451           
452         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
453         result.append("<!DOCTYPE ");
454         result.append(rootName);
455         if (publicID != null) {
456             result.append(" PUBLIC \"");
457             result.append(publicID);
458             result.append("\" \"");
459             result.append(systemID);
460             result.append('"');
461         }
462         else if (systemID != null) {
463             result.append(" SYSTEM \"");
464             result.append(systemID);
465             result.append('"');
466         }
467         
468         if (internalDTDSubset.length() != 0) {
469             result.append(" [\n");
470             result.append(internalDTDSubset);
471             result.append(']');
472         }
473         
474         result.append(">");
475         return result.toString();
476     }
477
478
479     boolean isDocType() {
480         return true;
481     }
482
483     
484     private static boolean isXMLPublicIDCharacter(char c) {
485
486         // PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]
487
// but I'm deliberately not allowing carriage return and linefeed
488
// because the parser normalizes them to a space. They are not
489
// roundtrippable.
490
switch(c) {
491             case ' ': return true;
492             case '!': return true;
493             case '"': return false;
494             case '#': return true;
495             case '$': return true;
496             case '%': return true;
497             case '&': return false;
498             case '\'': return true;
499             case '(': return true;
500             case ')': return true;
501             case '*': return true;
502             case '+': return true;
503             case ',': return true;
504             case '-': return true;
505             case '.': return true;
506             case '/': return true;
507             case '0': return true;
508             case '1': return true;
509             case '2': return true;
510             case '3': return true;
511             case '4': return true;
512             case '5': return true;
513             case '6': return true;
514             case '7': return true;
515             case '8': return true;
516             case '9': return true;
517             case ':': return true;
518             case ';': return true;
519             case '<': return false;
520             case '=': return true;
521             case '>': return false;
522             case '?': return true;
523             case '@': return true;
524             case 'A': return true;
525             case 'B': return true;
526             case 'C': return true;
527             case 'D': return true;
528             case 'E': return true;
529             case 'F': return true;
530             case 'G': return true;
531             case 'H': return true;
532             case 'I': return true;
533             case 'J': return true;
534             case 'K': return true;
535             case 'L': return true;
536             case 'M': return true;
537             case 'N': return true;
538             case 'O': return true;
539             case 'P': return true;
540             case 'Q': return true;
541             case 'R': return true;
542             case 'S': return true;
543             case 'T': return true;
544             case 'U': return true;
545             case 'V': return true;
546             case 'W': return true;
547             case 'X': return true;
548             case 'Y': return true;
549             case 'Z': return true;
550             case '[': return false;
551             case '\\': return false;
552             case ']': return false;
553             case '^': return false;
554             case '_': return true;
555             case '`': return false;
556             case 'a': return true;
557             case 'b': return true;
558             case 'c': return true;
559             case 'd': return true;
560             case 'e': return true;
561             case 'f': return true;
562             case 'g': return true;
563             case 'h': return true;
564             case 'i': return true;
565             case 'j': return true;
566             case 'k': return true;
567             case 'l': return true;
568             case 'm': return true;
569             case 'n': return true;
570             case 'o': return true;
571             case 'p': return true;
572             case 'q': return true;
573             case 'r': return true;
574             case 's': return true;
575             case 't': return true;
576             case 'u': return true;
577             case 'v': return true;
578             case 'w': return true;
579             case 'x': return true;
580             case 'y': return true;
581             case 'z': return true;
582         }
583
584         return false;
585         
586     }
587
588     
589 }
Popular Tags