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}