1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 */ 4/* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21package com.sun.org.apache.xerces.internal.dom; 22 23import java.io.IOException; 24import java.io.ObjectInputStream; 25import java.io.ObjectOutputStream; 26import java.io.ObjectStreamField; 27import java.util.HashMap; 28import java.util.Hashtable; 29import java.util.Map; 30import org.w3c.dom.DOMException; 31import org.w3c.dom.DocumentType; 32import org.w3c.dom.NamedNodeMap; 33import org.w3c.dom.Node; 34import org.w3c.dom.UserDataHandler; 35 36/** 37 * This class represents a Document Type <em>declaraction</em> in 38 * the document itself, <em>not</em> a Document Type Definition (DTD). 39 * An XML document may (or may not) have such a reference. 40 * <P> 41 * DocumentType is an Extended DOM feature, used in XML documents but 42 * not in HTML. 43 * <P> 44 * Note that Entities and Notations are no longer children of the 45 * DocumentType, but are parentless nodes hung only in their 46 * appropriate NamedNodeMaps. 47 * <P> 48 * This area is UNDERSPECIFIED IN REC-DOM-Level-1-19981001 49 * Most notably, absolutely no provision was made for storing 50 * and using Element and Attribute information. Nor was the linkage 51 * between Entities and Entity References nailed down solidly. 52 * 53 * @xerces.internal 54 * 55 * @author Arnaud Le Hors, IBM 56 * @author Joe Kesselman, IBM 57 * @author Andy Clark, IBM 58 * @since PR-DOM-Level-1-19980818. 59 */ 60public class DocumentTypeImpl 61 extends ParentNode 62 implements DocumentType { 63 64 // 65 // Constants 66 // 67 68 /** Serialization version. */ 69 static final long serialVersionUID = 7751299192316526485L; 70 71 // 72 // Data 73 // 74 75 /** Document type name. */ 76 protected String name; 77 78 /** Entities. */ 79 protected NamedNodeMapImpl entities; 80 81 /** Notations. */ 82 protected NamedNodeMapImpl notations; 83 84 // NON-DOM 85 86 /** Elements. */ 87 protected NamedNodeMapImpl elements; 88 89 // DOM2: support public ID. 90 protected String publicID; 91 92 // DOM2: support system ID. 93 protected String systemID; 94 95 // DOM2: support internal subset. 96 protected String internalSubset; 97 98 /** The following are required for compareDocumentPosition 99 */ 100 // Doctype number. Doc types which have no owner may be assigned 101 // a number, on demand, for ordering purposes for compareDocumentPosition 102 private int doctypeNumber=0; 103 104 private Map<String, UserDataRecord> userData = null; 105 106 107 /** 108 * @serialField name String document type name 109 * @serialField entities NamedNodeMapImpl entities 110 * @serialField notations NamedNodeMapImpl notations 111 * @serialField elements NamedNodeMapImpl elements 112 * @serialField publicID String support public ID 113 * @serialField systemID String support system ID 114 * @serialField internalSubset String support internal subset 115 * @serialField doctypeNumber int Doctype number 116 * @serialField userData Hashtable user data 117 */ 118 private static final ObjectStreamField[] serialPersistentFields = 119 new ObjectStreamField[] { 120 new ObjectStreamField("name", String.class), 121 new ObjectStreamField("entities", NamedNodeMapImpl.class), 122 new ObjectStreamField("notations", NamedNodeMapImpl.class), 123 new ObjectStreamField("elements", NamedNodeMapImpl.class), 124 new ObjectStreamField("publicID", String.class), 125 new ObjectStreamField("systemID", String.class), 126 new ObjectStreamField("internalSubset", String.class), 127 new ObjectStreamField("doctypeNumber", int.class), 128 new ObjectStreamField("userData", Hashtable.class), 129 }; 130 131 // 132 // Constructors 133 // 134 135 /** Factory method for creating a document type node. */ 136 public DocumentTypeImpl(CoreDocumentImpl ownerDocument, String name) { 137 super(ownerDocument); 138 139 this.name = name; 140 // DOM 141 entities = new NamedNodeMapImpl(this); 142 notations = new NamedNodeMapImpl(this); 143 144 // NON-DOM 145 elements = new NamedNodeMapImpl(this); 146 147 } // <init>(CoreDocumentImpl,String) 148 149 /** Factory method for creating a document type node. */ 150 public DocumentTypeImpl(CoreDocumentImpl ownerDocument, 151 String qualifiedName, 152 String publicID, String systemID) { 153 this(ownerDocument, qualifiedName); 154 this.publicID = publicID; 155 this.systemID = systemID; 156 157 } // <init>(CoreDocumentImpl,String) 158 159 // 160 // DOM2: methods. 161 // 162 163 /** 164 * Introduced in DOM Level 2. <p> 165 * 166 * Return the public identifier of this Document type. 167 * @since WD-DOM-Level-2-19990923 168 */ 169 public String getPublicId() { 170 if (needsSyncData()) { 171 synchronizeData(); 172 } 173 return publicID; 174 } 175 /** 176 * Introduced in DOM Level 2. <p> 177 * 178 * Return the system identifier of this Document type. 179 * @since WD-DOM-Level-2-19990923 180 */ 181 public String getSystemId() { 182 if (needsSyncData()) { 183 synchronizeData(); 184 } 185 return systemID; 186 } 187 188 /** 189 * NON-DOM. <p> 190 * 191 * Set the internalSubset given as a string. 192 */ 193 public void setInternalSubset(String internalSubset) { 194 if (needsSyncData()) { 195 synchronizeData(); 196 } 197 this.internalSubset = internalSubset; 198 } 199 200 /** 201 * Introduced in DOM Level 2. <p> 202 * 203 * Return the internalSubset given as a string. 204 * @since WD-DOM-Level-2-19990923 205 */ 206 public String getInternalSubset() { 207 if (needsSyncData()) { 208 synchronizeData(); 209 } 210 return internalSubset; 211 } 212 213 // 214 // Node methods 215 // 216 217 /** 218 * A short integer indicating what type of node this is. The named 219 * constants for this value are defined in the org.w3c.dom.Node interface. 220 */ 221 public short getNodeType() { 222 return Node.DOCUMENT_TYPE_NODE; 223 } 224 225 /** 226 * Returns the document type name 227 */ 228 public String getNodeName() { 229 if (needsSyncData()) { 230 synchronizeData(); 231 } 232 return name; 233 } 234 235 /** Clones the node. */ 236 public Node cloneNode(boolean deep) { 237 238 DocumentTypeImpl newnode = (DocumentTypeImpl)super.cloneNode(deep); 239 // NamedNodeMaps must be cloned explicitly, to avoid sharing them. 240 newnode.entities = entities.cloneMap(newnode); 241 newnode.notations = notations.cloneMap(newnode); 242 newnode.elements = elements.cloneMap(newnode); 243 244 return newnode; 245 246 } // cloneNode(boolean):Node 247 248 /* 249 * Get Node text content 250 * @since DOM Level 3 251 */ 252 public String getTextContent() throws DOMException { 253 return null; 254 } 255 256 /* 257 * Set Node text content 258 * @since DOM Level 3 259 */ 260 public void setTextContent(String textContent) 261 throws DOMException { 262 // no-op 263 } 264 265 /** 266 * DOM Level 3 WD- Experimental. 267 * Override inherited behavior from ParentNodeImpl to support deep equal. 268 */ 269 public boolean isEqualNode(Node arg) { 270 271 if (!super.isEqualNode(arg)) { 272 return false; 273 } 274 275 if (needsSyncData()) { 276 synchronizeData(); 277 } 278 DocumentTypeImpl argDocType = (DocumentTypeImpl) arg; 279 280 //test if the following string attributes are equal: publicId, 281 //systemId, internalSubset. 282 if ((getPublicId() == null && argDocType.getPublicId() != null) 283 || (getPublicId() != null && argDocType.getPublicId() == null) 284 || (getSystemId() == null && argDocType.getSystemId() != null) 285 || (getSystemId() != null && argDocType.getSystemId() == null) 286 || (getInternalSubset() == null 287 && argDocType.getInternalSubset() != null) 288 || (getInternalSubset() != null 289 && argDocType.getInternalSubset() == null)) { 290 return false; 291 } 292 293 if (getPublicId() != null) { 294 if (!getPublicId().equals(argDocType.getPublicId())) { 295 return false; 296 } 297 } 298 299 if (getSystemId() != null) { 300 if (!getSystemId().equals(argDocType.getSystemId())) { 301 return false; 302 } 303 } 304 305 if (getInternalSubset() != null) { 306 if (!getInternalSubset().equals(argDocType.getInternalSubset())) { 307 return false; 308 } 309 } 310 311 //test if NamedNodeMaps entities and notations are equal 312 NamedNodeMapImpl argEntities = argDocType.entities; 313 314 if ((entities == null && argEntities != null) 315 || (entities != null && argEntities == null)) 316 return false; 317 318 if (entities != null && argEntities != null) { 319 if (entities.getLength() != argEntities.getLength()) 320 return false; 321 322 for (int index = 0; entities.item(index) != null; index++) { 323 Node entNode1 = entities.item(index); 324 Node entNode2 = 325 argEntities.getNamedItem(entNode1.getNodeName()); 326 327 if (!((NodeImpl) entNode1).isEqualNode((NodeImpl) entNode2)) 328 return false; 329 } 330 } 331 332 NamedNodeMapImpl argNotations = argDocType.notations; 333 334 if ((notations == null && argNotations != null) 335 || (notations != null && argNotations == null)) 336 return false; 337 338 if (notations != null && argNotations != null) { 339 if (notations.getLength() != argNotations.getLength()) 340 return false; 341 342 for (int index = 0; notations.item(index) != null; index++) { 343 Node noteNode1 = notations.item(index); 344 Node noteNode2 = 345 argNotations.getNamedItem(noteNode1.getNodeName()); 346 347 if (!((NodeImpl) noteNode1).isEqualNode((NodeImpl) noteNode2)) 348 return false; 349 } 350 } 351 352 return true; 353 } //end isEqualNode 354 355 356 /** 357 * NON-DOM 358 * set the ownerDocument of this node and its children 359 */ 360 void setOwnerDocument(CoreDocumentImpl doc) { 361 super.setOwnerDocument(doc); 362 entities.setOwnerDocument(doc); 363 notations.setOwnerDocument(doc); 364 elements.setOwnerDocument(doc); 365 } 366 367 /** NON-DOM 368 Get the number associated with this doctype. 369 */ 370 protected int getNodeNumber() { 371 // If the doctype has a document owner, get the node number 372 // relative to the owner doc 373 if (getOwnerDocument()!=null) 374 return super.getNodeNumber(); 375 376 // The doctype is disconnected and not associated with any document. 377 // Assign the doctype a number relative to the implementation. 378 if (doctypeNumber==0) { 379 380 CoreDOMImplementationImpl cd = (CoreDOMImplementationImpl)CoreDOMImplementationImpl.getDOMImplementation(); 381 doctypeNumber = cd.assignDocTypeNumber(); 382 } 383 return doctypeNumber; 384 } 385 386 // 387 // DocumentType methods 388 // 389 390 /** 391 * Name of this document type. If we loaded from a DTD, this should 392 * be the name immediately following the DOCTYPE keyword. 393 */ 394 public String getName() { 395 396 if (needsSyncData()) { 397 synchronizeData(); 398 } 399 return name; 400 401 } // getName():String 402 403 /** 404 * Access the collection of general Entities, both external and 405 * internal, defined in the DTD. For example, in: 406 * <p> 407 * <pre> 408 * <!doctype example SYSTEM "ex.dtd" [ 409 * <!ENTITY foo "foo"> 410 * <!ENTITY bar "bar"> 411 * <!ENTITY % baz "baz"> 412 * ]> 413 * </pre> 414 * <p> 415 * The Entities map includes foo and bar, but not baz. It is promised that 416 * only Nodes which are Entities will exist in this NamedNodeMap. 417 * <p> 418 * For HTML, this will always be null. 419 * <p> 420 * Note that "built in" entities such as & and < should be 421 * converted to their actual characters before being placed in the DOM's 422 * contained text, and should be converted back when the DOM is rendered 423 * as XML or HTML, and hence DO NOT appear here. 424 */ 425 public NamedNodeMap getEntities() { 426 if (needsSyncChildren()) { 427 synchronizeChildren(); 428 } 429 return entities; 430 } 431 432 /** 433 * Access the collection of Notations defined in the DTD. A 434 * notation declares, by name, the format of an XML unparsed entity 435 * or is used to formally declare a Processing Instruction target. 436 */ 437 public NamedNodeMap getNotations() { 438 if (needsSyncChildren()) { 439 synchronizeChildren(); 440 } 441 return notations; 442 } 443 444 // 445 // Public methods 446 // 447 448 /** 449 * NON-DOM: Subclassed to flip the entities' and notations' readonly switch 450 * as well. 451 * @see NodeImpl#setReadOnly 452 */ 453 public void setReadOnly(boolean readOnly, boolean deep) { 454 455 if (needsSyncChildren()) { 456 synchronizeChildren(); 457 } 458 super.setReadOnly(readOnly, deep); 459 460 // set read-only property 461 elements.setReadOnly(readOnly, true); 462 entities.setReadOnly(readOnly, true); 463 notations.setReadOnly(readOnly, true); 464 465 } // setReadOnly(boolean,boolean) 466 467 /** 468 * NON-DOM: Access the collection of ElementDefinitions. 469 * @see ElementDefinitionImpl 470 */ 471 public NamedNodeMap getElements() { 472 if (needsSyncChildren()) { 473 synchronizeChildren(); 474 } 475 return elements; 476 } 477 478 public Object setUserData(String key, 479 Object data, UserDataHandler handler) { 480 if(userData == null) 481 userData = new HashMap<>(); 482 if (data == null) { 483 if (userData != null) { 484 UserDataRecord udr = userData.remove(key); 485 if (udr != null) { 486 return udr.fData; 487 } 488 } 489 return null; 490 } 491 else { 492 UserDataRecord udr = userData.put(key, new UserDataRecord(data, handler)); 493 if (udr != null) { 494 return udr.fData; 495 } 496 } 497 return null; 498 } 499 500 public Object getUserData(String key) { 501 if (userData == null) { 502 return null; 503 } 504 UserDataRecord udr = userData.get(key); 505 if (udr != null) { 506 return udr.fData; 507 } 508 return null; 509 } 510 511 @Override 512 protected Map<String, UserDataRecord> getUserDataRecord(){ 513 return userData; 514 } 515 516 /** 517 * @serialData Serialized fields. Convert Map to Hashtable for backward 518 * compatibility. 519 */ 520 private void writeObject(ObjectOutputStream out) throws IOException { 521 // Convert the HashMap to Hashtable 522 Hashtable<String, UserDataRecord> ud = (userData == null)? null : new Hashtable<>(userData); 523 524 // Write serialized fields 525 ObjectOutputStream.PutField pf = out.putFields(); 526 pf.put("name", name); 527 pf.put("entities", entities); 528 pf.put("notations", notations); 529 pf.put("elements", elements); 530 pf.put("publicID", publicID); 531 pf.put("systemID", systemID); 532 pf.put("internalSubset", internalSubset); 533 pf.put("doctypeNumber", doctypeNumber); 534 pf.put("userData", ud); 535 out.writeFields(); 536 } 537 538 @SuppressWarnings("unchecked") 539 private void readObject(ObjectInputStream in) 540 throws IOException, ClassNotFoundException { 541 // We have to read serialized fields first. 542 ObjectInputStream.GetField gf = in.readFields(); 543 name = (String)gf.get("name", null); 544 entities = (NamedNodeMapImpl)gf.get("entities", null); 545 notations = (NamedNodeMapImpl)gf.get("notations", null); 546 elements = (NamedNodeMapImpl)gf.get("elements", null); 547 publicID = (String)gf.get("publicID", null); 548 systemID = (String)gf.get("systemID", null); 549 internalSubset = (String)gf.get("internalSubset", null); 550 doctypeNumber = gf.get("doctypeNumber", 0); 551 552 Hashtable<String, UserDataRecord> ud = 553 (Hashtable<String, UserDataRecord>)gf.get("userData", null); 554 555 //convert the Hashtable back to HashMap 556 if (ud != null) userData = new HashMap<>(ud); 557 } 558} // class DocumentTypeImpl 559