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      *   &lt;!doctype example SYSTEM "ex.dtd" [
 410      *     &lt;!ENTITY foo "foo"&gt;
 411      *     &lt;!ENTITY bar "bar"&gt;
 412      *     &lt;!ENTITY % baz "baz"&gt;
 413      *     ]&gt;
 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 &amp; and &lt; 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