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}