001package org.jsoup.nodes;
002
003import org.jsoup.helper.Validate;
004import org.jsoup.internal.QuietAppendable;
005import org.jspecify.annotations.Nullable;
006
007import java.util.List;
008
009/**
010 A node that does not hold any children. E.g.: {@link TextNode}, {@link DataNode}, {@link Comment}.
011 */
012public abstract class LeafNode extends Node {
013    Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set)
014
015    public LeafNode() {
016        value = "";
017    }
018
019    protected LeafNode(String coreValue) {
020        Validate.notNull(coreValue);
021        value = coreValue;
022    }
023
024    @Override protected final boolean hasAttributes() {
025        return value instanceof Attributes;
026    }
027
028    @Override
029    public final Attributes attributes() {
030        ensureAttributes();
031        return (Attributes) value;
032    }
033
034    private void ensureAttributes() {
035        if (!hasAttributes()) { // then value is String coreValue
036            String coreValue = (String) value;
037            Attributes attributes = new Attributes();
038            value = attributes;
039            attributes.put(nodeName(), coreValue);
040        }
041    }
042
043    String coreValue() {
044        return attr(nodeName());
045    }
046
047    @Override @Nullable
048    public Element parent() {
049        return parentNode;
050    }
051
052    @Override
053    public String nodeValue() {
054        return coreValue();
055    }
056
057    void coreValue(String value) {
058        attr(nodeName(), value);
059    }
060
061    @Override
062    public String attr(String key) {
063        if (!hasAttributes()) {
064            return nodeName().equals(key) ? (String) value : EmptyString;
065        }
066        return super.attr(key);
067    }
068
069    @Override
070    public Node attr(String key, String value) {
071        if (!hasAttributes() && key.equals(nodeName())) {
072            this.value = value;
073        } else {
074            ensureAttributes();
075            super.attr(key, value);
076        }
077        return this;
078    }
079
080    @Override
081    public boolean hasAttr(String key) {
082        ensureAttributes();
083        return super.hasAttr(key);
084    }
085
086    @Override
087    public Node removeAttr(String key) {
088        ensureAttributes();
089        return super.removeAttr(key);
090    }
091
092    @Override
093    public String absUrl(String key) {
094        ensureAttributes();
095        return super.absUrl(key);
096    }
097
098    @Override
099    public String baseUri() {
100        return parentNode != null ? parentNode.baseUri() : "";
101    }
102
103    @Override
104    protected void doSetBaseUri(String baseUri) {
105        // noop
106    }
107
108    @Override
109    public int childNodeSize() {
110        return 0;
111    }
112
113    @Override
114    public Node empty() {
115        return this;
116    }
117
118    @Override
119    protected List<Node> ensureChildNodes() {
120        return EmptyNodes;
121    }
122
123    @Override
124    void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) {}
125
126    @Override
127    protected LeafNode doClone(Node parent) {
128        LeafNode clone = (LeafNode) super.doClone(parent);
129
130        // Object value could be plain string or attributes - need to clone
131        if (hasAttributes())
132            clone.value = ((Attributes) value).clone();
133
134        return clone;
135    }
136}