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