001package org.jsoup.nodes;
002
003import org.jsoup.SerializationException;
004import org.jsoup.internal.StringUtil;
005
006import java.io.IOException;
007
008/**
009 * An XML Declaration. Includes support for treating the declaration contents as pseudo attributes.
010 */
011public class XmlDeclaration extends LeafNode {
012
013    /**
014     First char is `!` if isDeclaration, like in {@code  <!ENTITY ...>}.
015     Otherwise, is `?`, a processing instruction, like {@code <?xml .... ?>} (and note trailing `?`).
016     */
017    private final boolean isDeclaration;
018
019    /**
020     * Create a new XML declaration
021     * @param name of declaration
022     * @param isDeclaration {@code true} if a declaration (first char is `!`), otherwise a processing instruction (first char is `?`).
023     */
024    public XmlDeclaration(String name, boolean isDeclaration) {
025        super(name);
026        this.isDeclaration = isDeclaration;
027    }
028
029    @Override public String nodeName() {
030        return "#declaration";
031    }
032
033    /**
034     * Get the name of this declaration.
035     * @return name of this declaration.
036     */
037    public String name() {
038        return coreValue();
039    }
040
041    /**
042     * Get the unencoded XML declaration.
043     * @return XML declaration
044     */
045    public String getWholeDeclaration() {
046        StringBuilder sb = StringUtil.borrowBuilder();
047        try {
048            getWholeDeclaration(sb, new Document.OutputSettings());
049        } catch (IOException e) {
050            throw new SerializationException(e);
051        }
052        return StringUtil.releaseBuilder(sb).trim();
053    }
054
055    private void getWholeDeclaration(Appendable accum, Document.OutputSettings out) throws IOException {
056        for (Attribute attribute : attributes()) {
057            String key = attribute.getKey();
058            String val = attribute.getValue();
059            if (!key.equals(nodeName())) { // skips coreValue (name)
060                accum.append(' ');
061                // basically like Attribute, but skip empty vals in XML
062                accum.append(key);
063                if (!val.isEmpty()) {
064                    accum.append("=\"");
065                    Entities.escape(accum, val, out, Entities.ForAttribute);
066                    accum.append('"');
067                }
068            }
069        }
070    }
071
072    @Override
073    void outerHtmlHead(Appendable accum, int depth, Document.OutputSettings out) throws IOException {
074        accum
075            .append("<")
076            .append(isDeclaration ? "!" : "?")
077            .append(coreValue());
078        getWholeDeclaration(accum, out);
079        accum
080            .append(isDeclaration ? "" : "?")
081            .append(">");
082    }
083
084    @Override
085    void outerHtmlTail(Appendable accum, int depth, Document.OutputSettings out) {
086    }
087
088    @Override
089    public String toString() {
090        return outerHtml();
091    }
092
093    @Override
094    public XmlDeclaration clone() {
095        return (XmlDeclaration) super.clone();
096    }
097}