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