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