001package org.jsoup.parser;
002
003import org.jsoup.helper.Validate;
004import org.jsoup.internal.StringUtil;
005import org.jspecify.annotations.Nullable;
006
007import java.util.Objects;
008
009import static org.jsoup.internal.Normalizer.asciiLowerCase;
010import static org.jsoup.parser.Parser.NamespaceHtml;
011
012/**
013 A Tag represents an Element's name and configured options, common throughout the Document. Options may affect the parse
014 and output.
015 <p>A tag's normalized name uses ASCII lowercase; other characters are unchanged.</p>
016
017 @see TagSet
018 @see Parser#tagSet(TagSet) */
019public class Tag implements Cloneable {
020    /** Tag option: the tag is known (specifically defined). This impacts if options may need to be inferred (when not
021     known) in, e.g., the pretty-printer. Set when a tag is added to a TagSet, or when settings are set(). */
022    public static int Known = 1;
023    /** Tag option: the tag is a void tag (e.g., {@code <img>}), that can contain no children, and in HTML does not require closing. */
024    public static int Void = 1 << 1;
025    /** Tag option: the tag is a block tag (e.g., {@code <div>}, {@code <p>}). Causes the element to be indented when pretty-printing. If not a block, it is inline. */
026    public static int Block = 1 << 2;
027    /** Tag option: pretty-print hint for block tags whose inline children should stay inline. (Must also set Block.) */
028    public static int InlineContainer = 1 << 3;
029    /** Tag option: the tag can self-close (e.g., {@code <foo />}). */
030    public static int SelfClose = 1 << 4;
031    /** Tag option: the tag has been seen self-closing in this parse. */
032    public static int SeenSelfClose = 1 << 5;
033    /** Tag option: the tag preserves whitespace (e.g., {@code <pre>}). */
034    public static int PreserveWhitespace = 1 << 6;
035    /** Tag option: the tag is an RCDATA element that can have text and character references (e.g., {@code <title>}, {@code <textarea>}). */
036    public static int RcData = 1 << 7;
037    /** Tag option: the tag is a Data element that can have text but not character references (e.g., {@code <style>}, {@code <script>}). */
038    public static int Data = 1 << 8;
039    /** Tag option: the tag's value will be included when submitting a form (e.g., {@code <input>}). */
040    public static int FormSubmittable = 1 << 9;
041    /** Tag option: readable text boundary for {@code Element.text()}, used for controls, widgets, and embedded objects. */
042    public static int TextBoundary = 1 << 10;
043
044    String namespace;
045    String tagName;
046    String normalName; // always the lower case version of this tag, regardless of case preservation mode
047    int options = 0;
048    private int parserOptions = 0; // internal tree-builder options; see HtmlTagOptions
049
050    /**
051     Create a new Tag, with the given name and namespace.
052     <p>The tag is not implicitly added to any TagSet.</p>
053     @param tagName the name of the tag. Case-sensitive.
054     @param namespace the namespace for the tag.
055     @see TagSet#valueOf(String, String)
056     @since 1.20.1
057     */
058    public Tag(String tagName, String namespace) {
059        this(tagName, asciiLowerCase(tagName), namespace);
060    }
061
062    /**
063     Create a new Tag, with the given name, in the HTML namespace.
064     <p>The tag is not implicitly added to any TagSet.</p>
065     @param tagName the name of the tag. Case-sensitive.
066     @see TagSet#valueOf(String, String)
067     @since 1.20.1
068     */
069    public Tag(String tagName) {
070        this(tagName, asciiLowerCase(tagName), NamespaceHtml);
071    }
072
073    /** Path for TagSet defaults, no options set; normal name is already LC. */
074    Tag(String tagName, String normalName, String namespace) {
075        this.tagName = tagName;
076        this.normalName = normalName;
077        this.namespace = namespace;
078        setParserOptions();
079    }
080
081    /**
082     * Get this tag's name.
083     *
084     * @return the tag's name
085     */
086    public String getName() {
087        return tagName;
088    }
089
090    /**
091     Get this tag's name.
092     @return the tag's name
093     */
094    public String name() {
095        return tagName;
096    }
097
098    /**
099     Change the tag's name. As Tags are reused throughout a Document, this will change the name for all uses of this tag.
100     @param tagName the new name of the tag. Case-sensitive.
101     @return this tag
102     @throws IllegalArgumentException if this is a Data or RcData tag and the name cannot form a recognizable end tag
103     @since 1.20.1
104     */
105    public Tag name(String tagName) {
106        if (is(RcData) || is(Data)) validateTextTagName(tagName);
107        this.tagName = tagName;
108        this.normalName = asciiLowerCase(tagName);
109        setParserOptions();
110        return this;
111    }
112
113    /**
114     Get this tag's prefix, if it has one; else the empty string.
115     <p>For example, {@code <book:title>} has prefix {@code book}, and tag name {@code book:title}.</p>
116     @return the tag's prefix
117     @since 1.20.1
118     */
119    public String prefix() {
120        int pos = tagName.indexOf(':');
121        if (pos == -1) return "";
122        else return tagName.substring(0, pos);
123    }
124
125    /**
126     Get this tag's local name. The local name is the name without the prefix (if any).
127     <p>For exmaple, {@code <book:title>} has local name {@code title}, and tag name {@code book:title}.</p>
128     @return the tag's local name
129     @since 1.20.1
130     */
131    public String localName() {
132        int pos = tagName.indexOf(':');
133        if (pos == -1) return tagName;
134        else return tagName.substring(pos + 1);
135    }
136
137    /**
138     * Get this tag's normalized name.
139     * @return the tag's normal name.
140     */
141    public String normalName() {
142        return normalName;
143    }
144
145    /**
146     Get this tag's namespace.
147     @return the tag's namespace
148     */
149    public String namespace() {
150        return namespace;
151    }
152
153    /**
154     Set the tag's namespace. As Tags are reused throughout a Document, this will change the namespace for all uses of this tag.
155     @param namespace the new namespace of the tag.
156     @return this tag
157     @since 1.20.1
158     */
159    public Tag namespace(String namespace) {
160        this.namespace = namespace;
161        setParserOptions();
162        return this;
163    }
164
165    /**
166     Set an option on this tag.
167     <p>Once a tag has a setting applied, it will be considered a known tag.</p>
168     @param option the option to set
169     @return this tag
170     @throws IllegalArgumentException if setting Data or RcData on a tag whose name cannot form a recognizable end tag
171     @since 1.20.1
172     */
173    public Tag set(int option) {
174        if ((option & (RcData | Data)) != 0) validateTextTagName(tagName);
175        options |= option;
176        options |= Tag.Known; // considered known if touched
177        return this;
178    }
179
180    /** Ensures a text-mode tag has an unambiguous tokenizer end tag. */
181    private static void validateTextTagName(String name) {
182        boolean valid = !name.isEmpty() && StringUtil.isAsciiLetter(name.charAt(0));
183        for (int i = 0; valid && i < name.length(); i++) {
184            char c = name.charAt(i);
185            valid = c != '<' && c != '>' && c != '/' && c != '\0' && c != '\uFFFD' && !StringUtil.isWhitespace(c);
186        }
187        Validate.isTrue(valid, "Data and RcData tag names must start with an ASCII letter and contain no whitespace, '<', '>', '/', null, or replacement characters");
188    }
189
190    /**
191     Test if an option is set on this tag.
192
193     @param option the option to test
194     @return true if the option is set
195     @since 1.20.1
196     */
197    public boolean is(int option) {
198        return (options & option) != 0;
199    }
200
201    /**
202     Clear (unset) an option from this tag.
203     @param option the option to clear
204     @return this tag
205     @since 1.20.1
206     */
207    public Tag clear(int option) {
208        options &= ~option;
209        // considered known if touched, unless explicitly clearing known
210        if (option != Tag.Known) options |= Tag.Known;
211        return this;
212    }
213
214    /**
215     Set the cached parser options from the current name and namespace.
216     */
217    void setParserOptions() {
218        parserOptions = HtmlTagOptions.optionsFor(normalName, namespace);
219    }
220
221    /**
222     Test if this tag has the given parser option.
223     */
224    boolean hasParserOption(int option) {
225        return (parserOptions & option) != 0;
226    }
227
228    /**
229     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.
230     * <p>
231     * Pre-defined tags (p, div etc) will be ==, but unknown tags are not registered and will only .equals().
232     * </p>
233     * 
234     * @param tagName Name of tag, e.g. "p". Case-insensitive.
235     * @param namespace the namespace for the tag.
236     * @param settings used to control tag name sensitivity
237     * @see TagSet
238     * @return The tag, either defined or new generic.
239     */
240    public static Tag valueOf(String tagName, String namespace, ParseSettings settings) {
241        return TagSet.Html().valueOf(tagName, null, namespace, settings.preserveTagCase());
242    }
243
244    /**
245     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.
246     * <p>
247     * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().
248     * </p>
249     *
250     * @param tagName Name of tag, e.g. "p". <b>Case sensitive</b>.
251     * @return The tag, either defined or new generic.
252     * @see #valueOf(String tagName, String namespace, ParseSettings settings)
253     */
254    public static Tag valueOf(String tagName) {
255        return valueOf(tagName, NamespaceHtml, ParseSettings.preserveCase);
256    }
257
258    /**
259     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.
260     * <p>
261     * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().
262     * </p>
263     *
264     * @param tagName Name of tag, e.g. "p". <b>Case sensitive</b>.
265     * @param settings used to control tag name sensitivity
266     * @return The tag, either defined or new generic.
267     * @see #valueOf(String tagName, String namespace, ParseSettings settings)
268     */
269    public static Tag valueOf(String tagName, ParseSettings settings) {
270        return valueOf(tagName, NamespaceHtml, settings);
271    }
272
273    /**
274     * Gets if this is a block tag.
275     *
276     * @return if block tag
277     */
278    public boolean isBlock() {
279        return (options & Block) != 0;
280    }
281
282    /**
283     * Gets if this tag is an inline tag. Just the opposite of isBlock.
284     *
285     * @return if this tag is an inline tag.
286     */
287    public boolean isInline() {
288        return (options & Block) == 0;
289    }
290
291    /**
292     Get if this is void (aka empty) tag.
293
294     @return true if this is a void tag
295     */
296    public boolean isEmpty() {
297        return (options & Void) != 0;
298    }
299
300    /**
301     * Get if this tag is self-closing.
302     *
303     * @return if this tag should be output as self-closing.
304     */
305    public boolean isSelfClosing() {
306        return (options & SelfClose) != 0 || (options & Void) != 0;
307    }
308
309    /**
310     * Get if this is a pre-defined tag in the TagSet, or was auto created on parsing.
311     *
312     * @return if a known tag
313     */
314    public boolean isKnownTag() {
315        return (options & Known) != 0;
316    }
317
318    /**
319     * Check if this tag name is a known HTML tag.
320     *
321     * @param tagName name of tag
322     * @return if known HTML tag
323     */
324    public static boolean isKnownTag(String tagName) {
325        return TagSet.HtmlTagSet.get(tagName, NamespaceHtml) != null;
326    }
327
328    /**
329     * Get if this tag should preserve whitespace within child text nodes.
330     *
331     * @return if preserve whitespace
332     */
333    public boolean preserveWhitespace() {
334        return (options & PreserveWhitespace) != 0;
335    }
336
337    /**
338     * Get if this tag represents an element that should be submitted with a form. E.g. input, option
339     * @return if submittable with a form
340     */
341    public boolean isFormSubmittable() {
342        return (options & FormSubmittable) != 0;
343    }
344
345    void setSeenSelfClose() {
346        options |= Tag.SeenSelfClose; // does not change known status
347    }
348
349    /**
350     If this Tag uses a specific text TokeniserState for its content, returns that; otherwise null.
351     */
352    @Nullable TokeniserState textState() {
353        if (is(RcData)) return TokeniserState.Rcdata;
354        if (is(Data))   return TokeniserState.Rawtext;
355        else            return null;
356    }
357
358    @Override
359    public boolean equals(Object o) {
360        if (this == o) return true;
361        if (!(o instanceof Tag)) return false;
362        Tag tag = (Tag) o;
363        return Objects.equals(tagName, tag.tagName) &&
364            Objects.equals(namespace, tag.namespace) &&
365            Objects.equals(normalName, tag.normalName) &&
366            options == tag.options;
367    }
368
369    /**
370     Hashcode of this Tag, consisting of the tag name and namespace.
371     */
372    @Override
373    public int hashCode() {
374        return Objects.hash(tagName, namespace); // options not included so that mutations do not prevent use as a key
375    }
376
377    @Override
378    public String toString() {
379        return tagName;
380    }
381
382    @Override
383    protected Tag clone() {
384        try {
385            return (Tag) super.clone();
386        } catch (CloneNotSupportedException e) {
387            throw new RuntimeException(e);
388        }
389    }
390
391
392}