Clover coverage report - Clover results for XOM 1.2d1
Coverage timestamp: Wed Feb 8 2006 08:31:33 EST
file stats: LOC: 613   Methods: 26
NCLOC: 260   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
DocType.java 100% 100% 100% 100%
coverage
 1    /* Copyright 2002-2005 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    * @author Elliotte Rusty Harold
 56    * @version 1.1d1
 57    *
 58    */
 59    public class DocType extends Node {
 60   
 61   
 62    private String rootName;
 63    private String systemID;
 64    private String publicID;
 65    private String internalDTDSubset = "";
 66   
 67   
 68    /**
 69    * <p>
 70    * Creates a new document type declaration with a public ID
 71    * and a system ID. It has the general form
 72    * <code>&lt;!DOCTYPE rootElementName PUBLIC
 73    * "publicID" "systemID"&gt;</code>.
 74    * </p>
 75    *
 76    * @param rootElementName the name specified for the root element
 77    * @param publicID the public ID of the external DTD subset
 78    * @param systemID the URL of the external DTD subset
 79    *
 80    * @throws IllegalNameException if <code>rootElementName</code>
 81    * is not a legal XML 1.0 name
 82    * @throws IllegalDataException if <code>publicID</code> is not a
 83    * legal XML 1.0 public identifier
 84    * @throws MalformedURIException if the system ID is not a
 85    * syntactically correct URI, or if it contains a fragment
 86    * identifier
 87    */
 88  1273 public DocType(
 89    String rootElementName, String publicID, String systemID) {
 90  1273 _setRootElementName(rootElementName);
 91  1268 _setSystemID(systemID);
 92  1221 _setPublicID(publicID);
 93    }
 94   
 95   
 96    /**
 97    * <p>
 98    * Creates a new document type declaration with a system ID
 99    * but no public ID. It has the general form
 100    * <code>&lt;!DOCTYPE rootElementName SYSTEM "systemID"&gt;</code>.
 101    * </p>
 102    *
 103    * @param rootElementName the name specified for the root element
 104    * @param systemID the URL of the external DTD subset
 105    *
 106    * @throws IllegalNameException if the rootElementName is not
 107    * a legal XML 1.0 name
 108    * @throws MalformedURIException if the system ID is not a
 109    * syntactically correct URI, or if it contains a fragment
 110    * identifier
 111    */
 112  53 public DocType(String rootElementName, String systemID) {
 113  53 this(rootElementName, null, systemID);
 114    }
 115   
 116   
 117    /**
 118    * <p>
 119    * Creates a new document type declaration with
 120    * no public or system ID. It has the general form
 121    * <code>&lt;!DOCTYPE rootElementName&gt;</code>.
 122    * </p>
 123    *
 124    * @param rootElementName the name specified for the root element
 125    *
 126    * @throws IllegalNameException if the rootElementName is not
 127    * a legal XML 1.0 name
 128    */
 129  110 public DocType(String rootElementName) {
 130  110 this(rootElementName, null, null);
 131    }
 132   
 133   
 134    /**
 135    * <p>
 136    * Creates a new <code>DocType</code> that's a copy of its
 137    * argument. The copy has the same data but no parent document.
 138    * </p>
 139    *
 140    * @param doctype the <code>DocType</code> to copy
 141    */
 142  7 public DocType(DocType doctype) {
 143  7 this.internalDTDSubset = doctype.internalDTDSubset;
 144  7 this.publicID = doctype.publicID;
 145  7 this.systemID = doctype.systemID;
 146  7 this.rootName = doctype.rootName;
 147    }
 148   
 149   
 150  2505 private DocType() {}
 151   
 152  2505 static DocType build(
 153    String rootElementName, String publicID, String systemID) {
 154  2505 DocType result = new DocType();
 155  2505 result.publicID = publicID;
 156  2505 result.systemID = systemID;
 157  2505 result.rootName = rootElementName;
 158  2505 return result;
 159    }
 160   
 161   
 162    /**
 163    * <p>
 164    * Returns the name the document type declaration specifies
 165    * for the root element. In an invalid document, this may
 166    * not be the same as the actual root element name.
 167    * </p>
 168    *
 169    * @return the declared name of the root element
 170    */
 171  1832 public final String getRootElementName() {
 172  1832 return rootName;
 173    }
 174   
 175   
 176    /**
 177    * <p>
 178    * Sets the name the document type declaration specifies
 179    * for the root element. In an invalid document, this may
 180    * not be the same as the actual root element name.
 181    * </p>
 182    *
 183    * @param name the root element name given by
 184    * the document type declaration
 185    *
 186    * @throws IllegalNameException if the root element name is not
 187    * a legal XML 1.0 name
 188    */
 189  3 public void setRootElementName(String name) {
 190  3 _setRootElementName(name);
 191    }
 192   
 193   
 194  1276 private void _setRootElementName(String name) {
 195  1276 Verifier.checkXMLName(name);
 196  1270 this.rootName = name;
 197    }
 198   
 199   
 200    /**
 201    * <p>
 202    * Returns the complete internal DTD subset.
 203    * White space may not be preserved completely accurately,
 204    * but all declarations should be in place.
 205    * </p>
 206    *
 207    * @return the internal DTD subset
 208    */
 209  606 public final String getInternalDTDSubset() {
 210  606 return internalDTDSubset;
 211    }
 212   
 213   
 214    /**
 215    * <p>
 216    * Sets the internal DTD subset; that is the part of the DTD
 217    * between <code>[</code> and <code>]</code>. Changing the
 218    * internal DTD subset does not affect the instance document.
 219    * That is, default attribute values and attribute types
 220    * specified in the new internal DTD subset are not applied to the
 221    * corresponding elements in the instance document. Furthermore,
 222    * there's no guarantee that the instance document is or is not
 223    * valid with respect to the declarations in the new internal
 224    * DTD subset.
 225    * </p>
 226    *
 227    * @param subset the internal DTD subset
 228    *
 229    * @throws IllegalDataException if subset is not
 230    * a legal XML 1.0 internal DTD subset
 231    *
 232    * @since 1.1
 233    */
 234  11 public final void setInternalDTDSubset(String subset) {
 235   
 236  11 if (subset != null && subset.length() > 0) {
 237  4 Verifier.checkInternalDTDSubset(subset);
 238  3 fastSetInternalDTDSubset(subset);
 239    }
 240    else {
 241  7 this.internalDTDSubset = "";
 242    }
 243   
 244    }
 245   
 246   
 247  2512 final void fastSetInternalDTDSubset(String internalSubset) {
 248  2512 this.internalDTDSubset = internalSubset;
 249    }
 250   
 251   
 252    /**
 253    * <p>
 254    * Returns the public ID of the external DTD subset.
 255    * This is null if there is no external DTD subset
 256    * or if it does not have a public identifier.
 257    * </p>
 258    *
 259    * @return the public ID of the external DTD subset.
 260    */
 261  1902 public final String getPublicID() {
 262  1902 return publicID;
 263    }
 264   
 265   
 266    /**
 267    * <p>
 268    * Sets the public ID for the external DTD subset.
 269    * This can only be set after a system ID has been set,
 270    * because XML requires that all document type declarations
 271    * with public IDs have system IDs. Passing null removes
 272    * the public ID.
 273    * </p>
 274    *
 275    * @param id the public identifier of the external DTD subset
 276    *
 277    * @throws IllegalDataException if the public ID does not satisfy
 278    * the rules for public IDs in XML 1.0
 279    * @throws WellformednessException if no system ID has been set
 280    */
 281  4 public void setPublicID(String id) {
 282  4 _setPublicID(id);
 283    }
 284   
 285   
 286  1225 private void _setPublicID(String id) {
 287   
 288  1225 if (systemID == null && id != null) {
 289  1 throw new WellformednessException(
 290    "Cannot have a public ID without a system ID"
 291    );
 292    }
 293   
 294  1224 if (id != null) {
 295  1042 int length = id.length();
 296  1042 if (length != 0) {
 297  1041 if (Verifier.isXMLSpaceCharacter(id.charAt(0))) {
 298  2 throw new IllegalDataException("Initial white space "
 299    + "in public IDs is not round trippable.");
 300    }
 301  1039 if (Verifier.isXMLSpaceCharacter(id.charAt(length - 1))) {
 302  1 throw new IllegalDataException("Trailing white space "
 303    + "in public IDs is not round trippable.");
 304    }
 305   
 306  1038 for (int i = 0; i < length; i++) {
 307  2189 char c = id.charAt(i);
 308  2189 if (!isXMLPublicIDCharacter(c)) {
 309  919 throw new IllegalDataException("The character 0x"
 310    + Integer.toHexString(c)
 311    + " is not allowed in public IDs");
 312    }
 313  1270 if (c == ' ' && id.charAt(i-1) == ' ') {
 314  1 throw new IllegalDataException("Multiple consecutive "
 315    + "spaces in public IDs are not round trippable.");
 316    }
 317    }
 318    }
 319    }
 320  301 this.publicID = id;
 321   
 322    }
 323   
 324   
 325    /**
 326    * <p>
 327    * Returns the system ID of the external DTD subset.
 328    * This is a URL. It is null if there is no external DTD subset.
 329    * </p>
 330    *
 331    * @return the URL for the external DTD subset.
 332    */
 333  1976 public final String getSystemID() {
 334  1976 return systemID;
 335    }
 336   
 337   
 338    /**
 339    * <p>
 340    * Sets the system ID for the external DTD subset.
 341    * This must be a a relative or absolute URI with no fragment
 342    * identifier. Passing null removes the system ID, but only if
 343    * the public ID has been removed first. Otherwise,
 344    * passing null causes a <code>WellformednessException</code>.
 345    * </p>
 346    *
 347    * @param id the URL of the external DTD subset
 348    *
 349    * @throws MalformedURIException if the system ID is not a
 350    * syntactically correct URI, or if it contains a fragment
 351    * identifier
 352    * @throws WellformednessException if the public ID is non-null
 353    * and you attempt to remove the system ID
 354    */
 355  2 public void setSystemID(String id) {
 356  2 _setSystemID(id);
 357    }
 358   
 359   
 360  1270 private void _setSystemID(String id) {
 361   
 362  1270 if (id == null && publicID != null) {
 363  1 throw new WellformednessException(
 364    "Cannot remove system ID without removing public ID first"
 365    );
 366    }
 367   
 368  1269 if (id != null) {
 369   
 370  1142 Verifier.checkURIReference(id);
 371   
 372  1097 if (id.indexOf('#') != -1) {
 373  2 MalformedURIException ex = new MalformedURIException(
 374    "System literals cannot contain fragment identifiers"
 375    );
 376  2 ex.setData(id);
 377  2 throw ex;
 378    }
 379    }
 380   
 381  1222 this.systemID = id;
 382   
 383    }
 384   
 385   
 386    /**
 387    * <p>
 388    * Returns the empty string. XPath 1.0 does not define a value
 389    * for document type declarations.
 390    * </p>
 391    *
 392    * @return an empty string
 393    */
 394  1 public final String getValue() {
 395  1 return "";
 396    }
 397   
 398   
 399    /**
 400    * <p>
 401    * Throws <code>IndexOutOfBoundsException</code> because
 402    * document type declarations do not have children.
 403    * </p>
 404    *
 405    * @return never returns because document type declarations do not
 406    * have children. Always throws an exception.
 407    *
 408    * @param position the index of the child node to return
 409    *
 410    * @throws IndexOutOfBoundsException because document type declarations
 411    * do not have children
 412    */
 413  1 public final Node getChild(int position) {
 414  1 throw new IndexOutOfBoundsException(
 415    "Document type declarations do not have children");
 416    }
 417   
 418   
 419    /**
 420    * <p>
 421    * Returns 0 because document type declarations do not have
 422    * children.
 423    * </p>
 424    *
 425    * @return zero
 426    */
 427  3 public final int getChildCount() {
 428  3 return 0;
 429    }
 430   
 431   
 432    /**
 433    * <p>
 434    * Returns a string form of the
 435    * <code>DocType</code> suitable for debugging
 436    * and diagnosis. It deliberately does not return
 437    * an actual XML document type declaration.
 438    * </p>
 439    *
 440    * @return a string representation of this object
 441    */
 442  1 public final String toString() {
 443  1 return "[" + getClass().getName() + ": " + rootName + "]";
 444    }
 445   
 446   
 447    /**
 448    * <p>
 449    * Returns a copy of this <code>DocType</code>
 450    * which has the same system ID, public ID, root element name,
 451    * and internal DTD subset, but does not belong to a document.
 452    * Thus, it can be inserted into a different document.
 453    * </p>
 454    *
 455    * @return a deep copy of this <code>DocType</code>
 456    * that is not part of a document
 457    */
 458  7 public Node copy() {
 459  7 return new DocType(this);
 460    }
 461   
 462   
 463    /**
 464    * <p>
 465    * Returns a string containing the actual XML
 466    * form of the document type declaration represented
 467    * by this object. For example,
 468    * <code>&lt;!DOCTYPE book SYSTEM "docbookx.dtd"></code>.
 469    * </p>
 470    *
 471    * @return a <code>String</code> containing
 472    * an XML document type declaration
 473    */
 474  20 public final String toXML() {
 475   
 476  20 StringBuffer result = new StringBuffer();
 477  20 result.append("<!DOCTYPE ");
 478  20 result.append(rootName);
 479  20 if (publicID != null) {
 480  1 result.append(" PUBLIC \"");
 481  1 result.append(publicID);
 482  1 result.append("\" \"");
 483  1 result.append(systemID);
 484  1 result.append('"');
 485    }
 486  19 else if (systemID != null) {
 487  1 result.append(" SYSTEM \"");
 488  1 result.append(systemID);
 489  1 result.append('"');
 490    }
 491   
 492  20 if (internalDTDSubset.length() != 0) {
 493  16 result.append(" [\n");
 494  16 result.append(internalDTDSubset);
 495  16 result.append(']');
 496    }
 497   
 498  20 result.append(">");
 499  20 return result.toString();
 500    }
 501   
 502   
 503  1812 boolean isDocType() {
 504  1812 return true;
 505    }
 506   
 507   
 508  2189 private static boolean isXMLPublicIDCharacter(char c) {
 509   
 510    // PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]
 511    // but I'm deliberately not allowing carriage return and linefeed
 512    // because the parser normalizes them to a space. They are not
 513    // roundtrippable.
 514  2189 switch(c) {
 515  113 case ' ': return true;
 516  1 case '!': return true;
 517  1 case '"': return false;
 518  1 case '#': return true;
 519  1 case '$': return true;
 520  1 case '%': return true;
 521  1 case '&': return false;
 522  1 case '\'': return true;
 523  1 case '(': return true;
 524  1 case ')': return true;
 525  1 case '*': return true;
 526  1 case '+': return true;
 527  1 case ',': return true;
 528  55 case '-': return true;
 529  3 case '.': return true;
 530  221 case '/': return true;
 531  3 case '0': return true;
 532  3 case '1': return true;
 533  1 case '2': return true;
 534  3 case '3': return true;
 535  1 case '4': return true;
 536  1 case '5': return true;
 537  1 case '6': return true;
 538  1 case '7': return true;
 539  1 case '8': return true;
 540  1 case '9': return true;
 541  1 case ':': return true;
 542  1 case ';': return true;
 543  1 case '<': return false;
 544  1 case '=': return true;
 545  1 case '>': return false;
 546  1 case '?': return true;
 547  1 case '@': return true;
 548  1 case 'A': return true;
 549  1 case 'B': return true;
 550  3 case 'C': return true;
 551  57 case 'D': return true;
 552  3 case 'E': return true;
 553  1 case 'F': return true;
 554  1 case 'G': return true;
 555  3 case 'H': return true;
 556  53 case 'I': return true;
 557  1 case 'J': return true;
 558  1 case 'K': return true;
 559  3 case 'L': return true;
 560  55 case 'M': return true;
 561  3 case 'N': return true;
 562  1 case 'O': return true;
 563  1 case 'P': return true;
 564  1 case 'Q': return true;
 565  1 case 'R': return true;
 566  2 case 'S': return true;
 567  6 case 'T': return true;
 568  1 case 'U': return true;
 569  1 case 'V': return true;
 570  3 case 'W': return true;
 571  3 case 'X': return true;
 572  1 case 'Y': return true;
 573  1 case 'Z': return true;
 574  1 case '[': return false;
 575  1 case '\\': return false;
 576  1 case ']': return false;
 577  1 case '^': return false;
 578  1 case '_': return true;
 579  1 case '`': return false;
 580  3 case 'a': return true;
 581  53 case 'b': return true;
 582  54 case 'c': return true;
 583  1 case 'd': return true;
 584  111 case 'e': return true;
 585  1 case 'f': return true;
 586  1 case 'g': return true;
 587  1 case 'h': return true;
 588  56 case 'i': return true;
 589  1 case 'j': return true;
 590  1 case 'k': return true;
 591  54 case 'l': return true;
 592  53 case 'm': return true;
 593  3 case 'n': return true;
 594  54 case 'o': return true;
 595  53 case 'p': return true;
 596  1 case 'q': return true;
 597  3 case 'r': return true;
 598  60 case 's': return true;
 599  16 case 't': return true;
 600  53 case 'u': return true;
 601  1 case 'v': return true;
 602  1 case 'w': return true;
 603  1 case 'x': return true;
 604  1 case 'y': return true;
 605  1 case 'z': return true;
 606    }
 607   
 608  910 return false;
 609   
 610    }
 611   
 612   
 613    }