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