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}