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 NameKey = "name"; 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 attributes() 034 .add(NameKey, name) 035 .add(PublicId, publicId) 036 .add(SystemId, systemId); 037 updatePubSyskey(); 038 } 039 040 public void setPubSysKey(@Nullable String value) { 041 if (value != null) 042 attr(PubSysKey, value); 043 } 044 045 private void updatePubSyskey() { 046 if (has(PublicId)) { 047 attributes().add(PubSysKey, PUBLIC_KEY); 048 } else if (has(SystemId)) 049 attributes().add(PubSysKey, SYSTEM_KEY); 050 } 051 052 /** 053 * Get this doctype's name (when set, or empty string) 054 * @return doctype name 055 */ 056 public String name() { 057 return attr(NameKey); 058 } 059 060 /** 061 * Get this doctype's Public ID (when set, or empty string) 062 * @return doctype Public ID 063 */ 064 public String publicId() { 065 return attr(PublicId); 066 } 067 068 /** 069 * Get this doctype's System ID (when set, or empty string) 070 * @return doctype System ID 071 */ 072 public String systemId() { 073 return attr(SystemId); 074 } 075 076 @Override 077 public String nodeName() { 078 return "#doctype"; 079 } 080 081 @Override 082 void outerHtmlHead(Appendable accum, Document.OutputSettings out) throws IOException { 083 if (out.syntax() == Syntax.html && !has(PublicId) && !has(SystemId)) { 084 // looks like a html5 doctype, go lowercase for aesthetics 085 accum.append("<!doctype"); 086 } else { 087 accum.append("<!DOCTYPE"); 088 } 089 if (has(NameKey)) 090 accum.append(" ").append(attr(NameKey)); 091 if (has(PubSysKey)) 092 accum.append(" ").append(attr(PubSysKey)); 093 if (has(PublicId)) 094 accum.append(" \"").append(attr(PublicId)).append('"'); 095 if (has(SystemId)) 096 accum.append(" \"").append(attr(SystemId)).append('"'); 097 accum.append('>'); 098 } 099 100 101 private boolean has(final String attribute) { 102 return !StringUtil.isBlank(attr(attribute)); 103 } 104}