001package org.jsoup.nodes; 002 003import org.jsoup.internal.StringUtil; 004import org.jsoup.helper.Validate; 005import org.jsoup.nodes.Document.OutputSettings.Syntax; 006import org.jspecify.annotations.Nullable; 007 008import java.io.IOException; 009 010/** 011 * A {@code <!DOCTYPE>} node. 012 */ 013public class DocumentType extends LeafNode { 014 // todo needs a bit of a chunky cleanup. this level of detail isn't needed 015 public static final String PUBLIC_KEY = "PUBLIC"; 016 public static final String SYSTEM_KEY = "SYSTEM"; 017 private static final String Name = "#doctype"; 018 private static final String PubSysKey = "pubSysKey"; // PUBLIC or SYSTEM 019 private static final String PublicId = "publicId"; 020 private static final String SystemId = "systemId"; 021 // todo: quirk mode from publicId and systemId 022 023 /** 024 * Create a new doctype element. 025 * @param name the doctype's name 026 * @param publicId the doctype's public ID 027 * @param systemId the doctype's system ID 028 */ 029 public DocumentType(String name, String publicId, String systemId) { 030 super(name); 031 Validate.notNull(publicId); 032 Validate.notNull(systemId); 033 attr(Name, name); 034 attr(PublicId, publicId); 035 attr(SystemId, systemId); 036 updatePubSyskey(); 037 } 038 039 public void setPubSysKey(@Nullable String value) { 040 if (value != null) 041 attr(PubSysKey, value); 042 } 043 044 private void updatePubSyskey() { 045 if (has(PublicId)) { 046 attr(PubSysKey, PUBLIC_KEY); 047 } else if (has(SystemId)) 048 attr(PubSysKey, SYSTEM_KEY); 049 } 050 051 /** 052 * Get this doctype's name (when set, or empty string) 053 * @return doctype name 054 */ 055 public String name() { 056 return attr(Name); 057 } 058 059 /** 060 * Get this doctype's Public ID (when set, or empty string) 061 * @return doctype Public ID 062 */ 063 public String publicId() { 064 return attr(PublicId); 065 } 066 067 /** 068 * Get this doctype's System ID (when set, or empty string) 069 * @return doctype System ID 070 */ 071 public String systemId() { 072 return attr(SystemId); 073 } 074 075 @Override 076 public String nodeName() { 077 return Name; 078 } 079 080 @Override 081 void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 082 // add a newline if the doctype has a preceding node (which must be a comment) 083 if (siblingIndex > 0 && out.prettyPrint()) 084 accum.append('\n'); 085 086 if (out.syntax() == Syntax.html && !has(PublicId) && !has(SystemId)) { 087 // looks like a html5 doctype, go lowercase for aesthetics 088 accum.append("<!doctype"); 089 } else { 090 accum.append("<!DOCTYPE"); 091 } 092 if (has(Name)) 093 accum.append(" ").append(attr(Name)); 094 if (has(PubSysKey)) 095 accum.append(" ").append(attr(PubSysKey)); 096 if (has(PublicId)) 097 accum.append(" \"").append(attr(PublicId)).append('"'); 098 if (has(SystemId)) 099 accum.append(" \"").append(attr(SystemId)).append('"'); 100 accum.append('>'); 101 } 102 103 @Override 104 void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) { 105 } 106 107 private boolean has(final String attribute) { 108 return !StringUtil.isBlank(attr(attribute)); 109 } 110}