001package org.jsoup.nodes; 002 003import org.jsoup.internal.QuietAppendable; 004import org.jsoup.internal.StringUtil; 005import org.jsoup.helper.Validate; 006import org.jsoup.nodes.Document.OutputSettings.Syntax; 007import org.jspecify.annotations.Nullable; 008 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 022 /** 023 * Create a new doctype element. 024 * @param name the doctype's name 025 * @param publicId the doctype's public ID 026 * @param systemId the doctype's system ID 027 */ 028 public DocumentType(String name, String publicId, String systemId) { 029 super(name); 030 Validate.notNull(publicId); 031 Validate.notNull(systemId); 032 attributes() 033 .add(NameKey, name) 034 .add(PublicId, publicId) 035 .add(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 attributes().add(PubSysKey, PUBLIC_KEY); 047 } else if (has(SystemId)) 048 attributes().add(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(NameKey); 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 "#doctype"; 078 } 079 080 @Override 081 void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) { 082 if (out.syntax() == Syntax.html && !has(PublicId) && !has(SystemId)) { 083 // looks like a html5 doctype, go lowercase for aesthetics 084 accum.append("<!doctype"); 085 } else { 086 accum.append("<!DOCTYPE"); 087 } 088 if (has(NameKey)) 089 accum.append(" ").append(attr(NameKey)); 090 if (has(PubSysKey)) 091 accum.append(" ").append(attr(PubSysKey)); 092 if (has(PublicId)) 093 accum.append(" \"").append(attr(PublicId)).append('"'); 094 if (has(SystemId)) 095 accum.append(" \"").append(attr(SystemId)).append('"'); 096 accum.append('>'); 097 } 098 099 100 private boolean has(final String attribute) { 101 return !StringUtil.isBlank(attr(attribute)); 102 } 103}