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}