Package Torello.HTML

Class TagNode

  • All Implemented Interfaces:
    java.io.Serializable, java.lang.CharSequence, java.lang.Cloneable, java.lang.Comparable<TagNode>

    public final class TagNode
    extends HTMLNode
    implements java.lang.CharSequence, java.io.Serializable, java.lang.Cloneable, java.lang.Comparable<TagNode>
    Represents an HTML Element Tag, and is the flagship class of the Java-HTML Library.

    This class is intended to represent HTMLNode's that are Tags, also known as 'elements.'

    Here, below, are just simple examples of what constitute an instance of this class TagNode. Technically speaking, each of the String's below would be stored in the ancestor / parent class HTMLNode.str field. To retrieve the actual text-String of an HTML Element, just reference TagNode.str to get that String ().

    Example:
     <A HREF="http://some.url.com/index.html">
     <IMG SRC="https://some.image.url.net/image.jpg">
     <DIV ID="MainDivClass">
     <BODY ONLOAD="callInit();">
     <HEAD>, <TITLE>
     <TABLE CLASS="DataTables" ID="Weights" STYLE="width: 80%; color: red; background: black">
     </TABLE>
     </SPAN>
     </A>
     </BODY>
     etc...
    


    The three inherited classes of abstract class HTMLNode are very light-weight, and contain some amount of public methods, but do not have heavy internal-state (either static, or non-static). Below is a list of the internal field's that are added to each of the three instantiations of the ancestor HTMLNode class:

    • class TagNode adds a field public final boolean isClosing - which tells a user if this tag has a forward-slash immediately following the '<' (less-than symbol) at character position 2. This is how one identifies a 'closing-version' of the element, for instance: '</DIV>' and '</SPAN>' would both have their public final boolean isClosing fields set to TRUE. There is also a public final String tok field added to instances of TagNode that identify what html element the TagNode represents. For example an HTML Element such as: <A HREF="http://My.URL.com" TARGET=_blank>, would have it's String 'tok' field set to 'a'

    • class TextNode this inherited class from class HTMLNode does not add any internal state at all. It has the exact same internally-maintained fields as its parent-class. The public final String str field merely states what text this text-node actually represents.

    • class CommentNode for searching-purposes, and ease-of-use, class CommentNode, which is the third and final class to inherit HTMLNode keeps one extra internal-field, which is public final String body. This field is a redundant, duplicate, of the internal string public final String str - which is inherited from the HTML Node class. The subtle difference is that, since comment nodes represent the HTML <!-- and --> symbols, the 'body' of the comment sometimes needs to be searched, quickly. The public final String body leaves off these leading and ending comment delimiter symbols: <!-- and -->

    Below is the inheritance diagram (with fields) of the three concrete-classes that extend the abstract class HTMLNode:

    HTMLNode Inheritance Diagram
    See Also:
    TextNode, CommentNode, HTMLNode, Serialized Form


    • Nested Class Summary

      Nested Classes 
      Modifier and Type Class
      static class  TagNode.AttrRegEx
    • Field Summary

       
      Serializable ID
      Modifier and Type Field
      static long serialVersionUID
       
      Data Fields
      Modifier and Type Field
      boolean isClosing
      String tok
    • Constructor Summary

      Constructors 
      Constructor
      TagNode​(String s)
      TagNode​(String tok, Properties attributes, Iterable<String> keyOnlyAttributes, SD quotes, boolean addEndingForwardSlash)
      TagNode​(String tok, Properties attributes, SD quotes, boolean addEndingForwardSlash)
    • Method Summary

       
      General Purpose
      Modifier and Type Method
      static TagNode getInstance​(String tok, TC openOrClosed)
       
      HTML Element, .tok Field Test
      Modifier and Type Method
      boolean isTag​(String... possibleTags)
      boolean isTag​(TC tagCriteria, String... possibleTags)
      boolean isTagExcept​(String... possibleTags)
      boolean isTagExcept​(TC tagCriteria, String... possibleTags)
       
      Attribute Tests: Has an Attribute-Name
      Modifier and Type Method
      boolean has​(String attributeName)
      boolean has​(Predicate<String> attributeNameTest)
      boolean has​(Pattern attributeNameRegExTest)
      boolean has​(TextComparitor tc, String... compareStrs)
       
      Attribute Tests: Has an Attribute-Name, Multiple Tokens
      Modifier and Type Method
      boolean hasAND​(boolean checkAttributeStringsForErrors, String... attributes)
      boolean hasNAND​(boolean checkAttributeStringsForErrors, String... attributes)
      boolean hasOR​(boolean checkAttributeStringsForErrors, String... attributes)
      boolean hasXOR​(boolean checkAttributeStringsForErrors, String... attributes)
       
      Attribute Tests: Test an Attribute-Value
      Modifier and Type Method
      boolean testAV​(String attributeName, String attributeValue)
      boolean testAV​(String attributeName, Predicate<String> attributeValueTest)
      boolean testAV​(String attributeName, Pattern attributeValueTest)
      boolean testAV​(String attributeName, TextComparitor attributeValueTester, String... compareStrs)
       
      Attribute Tests: Has a Key-Only / Boolean Attribute
      Modifier and Type Method
      boolean hasKeyOnlyAttribute​(String keyOnlyAttribute)
       
      Attribute Modify & Update: Set Attribute-Value
      Modifier and Type Method
      TagNode setAV​(String attribute, String value, SD quote)
      TagNode setAV​(Properties attributes, SD defaultQuote)
       
      Attribute Modify & Update: Append to Attribute-Value
      Modifier and Type Method
      TagNode appendToAV​(String attribute, String appendStr, boolean startOrEnd, SD quote)
       
      Attribute Modify & Update: Remove Attributes
      Modifier and Type Method
      TagNode remove​(String attributeName)
      TagNode removeAllAV()
      TagNode removeAttributes​(String... attributes)
      TagNode removeDataAttributes()
       
      Attribute Modify & Update: CSS-Attributes
      Modifier and Type Method
      TagNode appendCSSClass​(String cssClass, SD quote)
      TagNode setCSSClasses​(SD quote, boolean appendOrClobber, String... cssClasses)
      TagNode setCSSStyle​(Properties p, SD quote, boolean appendOrClobber)
      TagNode setID​(String id, SD quote)
       
      Attribute Retrieve
      Modifier and Type Method
      Stream<String> allAN()
      Stream<String> allAN​(boolean preserveKeysCase, boolean includeKeyOnlyAttributes)
      Properties allAV()
      Properties allAV​(boolean keepQuotes, boolean preserveKeysCase)
      Stream<String> allKeyOnlyAttributes​(boolean preserveKeysCase)
      String AV​(String innerTagAttribute)
       
      Attribute Retrieve: CSS-Attributes
      Modifier and Type Method
      Stream<String> cssClasses()
      Stream<String> cssClassesNOCSE()
      Properties cssStyle()
      String getID()
       
      Attribute Retrieve: DATA-* Attributes
      Modifier and Type Method
      String dataAV​(String dataName)
      Stream<String> getDataAN()
      Stream<String> getDataAN​(boolean preserveKeysCase)
      Properties getDataAV()
      Properties getDataAV​(boolean preserveKeysCase)
       
      Attribute Retrieve: Retrieve By Value
      Modifier and Type Method
      Map.Entry<String,​String> hasValue​(boolean retainQuotes, boolean preserveKeysCase, TextComparitor attributeValueTester, String... compareStrs)
      Map.Entry<String,​String> hasValue​(String attributeValue, boolean retainQuotes, boolean preserveKeysCase)
      Map.Entry<String,​String> hasValue​(Predicate<String> attributeValueTest, boolean retainQuotes, boolean preserveKeysCase)
      Map.Entry<String,​String> hasValue​(Pattern attributeValueRegExTest, boolean retainQuotes, boolean preserveKeysCase)
       
      Loop Optimization & 'instanceof' Operator Replacement Methods
      Modifier and Type Method
      String AVOPT​(String innerTagAttribute)
      TagNode ifTagNode()
      boolean isTagNode()
      TagNode openTag()
      TagNode openTagPWA()
       
      Methods: interface java.lang.Comparable<TagNode>
      Modifier and Type Method
      int compareTo​(TagNode n)
       
      Methods: interface java.lang.Cloneable
      Modifier and Type Method
      TagNode clone()
       
      Methods: class java.lang.Object
      Modifier and Type Method
      String toStringAV()
       
      Protected Methods
      Modifier and Type Method
      protected static String generateElementString​(String tok, Properties attributes, Iterable<String> keyOnlyAttributes, SD quotes, boolean addEndingForwardSlash)
      protected boolean hasLogicOp​(boolean checkAttributeStringsForErrors, IntFunction<Boolean> onMatchFunction, IntPredicate reachedEndFunction, String... attributes)
      • Methods inherited from class java.lang.Object

        finalize, getClass, notify, notifyAll, wait, wait, wait
      • Methods inherited from interface java.lang.CharSequence

        charAt, chars, codePoints, length, subSequence, toString
    • Field Detail

      • serialVersionUID

        🡇    
        public static final long serialVersionUID
        This fulfils the SerialVersion UID requirement for all classes that implement Java's interface java.io.Serializable. Using the Serializable Implementation offered by java is very easy, and can make saving program state when debugging a lot easier. It can also be used in place of more complicated systems like "hibernate" to store data as well.
        See Also:
        Constant Field Values
        Code:
        Exact Field Declaration Expression:
        public static final long serialVersionUID = 1;
        
      • tok

        🡅  🡇    
        public final java.lang.String tok
        This variable, always lower-case, and always final, as well retains the "token" or "element name" (of the HTML Element) in an independent field of this class. Remember that the complete element is retained in the HTMLNode.str field which is a public, final member field of the parent-class.

        IMPORTANT: As mentioned, 'class TagNode' string-field, 'public final String tok' is always stored here with lower-case characters. Furthermore, since TagNode's are an immutable class (there are no fields or methods that may be modified inside of an instance of TagNode) once the object has been constructed. This 'tok' field also may not be changed after the object is constructed.

        Here is a table of some common HTML Elements found on web-pages. In each row of this table, the HTML Element, which is precisely equal to the 'public final String str' field of the parent class ('HTMLNode') is shown in the first column of the table. The second column of the table shows what the 'public final String tok' field would contain if the primary 'str' field were as chosen from column 1.

        Field: public final String str Field: public final String tok
        <A HREF="http://some.url.com"> "a"(all lower-case).
        <div class="MyFavoriteClass"> "div"(all lower-case).
        <IMG SRC="MyImage.jpg"> "img"(all lower-case).
        </body> "body"(all lower-case).
        </SPAN> "span"(all lower-case).

        The 'tok' variable always keeps a copy of the "separated HTML tag" (first few characters of the constructor-passed-string) from the rest of the inner-tag information. This allows the programmer to extract or obtain what type of HTML-Tag this is, without re-doing the String-comparisons and String-substring operations.
        Code:
        Exact Field Declaration Expression:
        public final String tok;
        
      • isClosing

        🡅  🡇    
        public final boolean isClosing
        If the public final String str field of 'this' object-instance begins with the character "</" (which 'identifies' itself as an HTML-closing tag), then this boolean-variable will be TRUE. If the public final String str field of this object does not begin with begins with "</", (and leaves off the beginning forward-slash, then this HTML-Tag Element should be through of as either a singleton (self-closing) tag, or if this HTML-Element is not self-closing, then this is an opening-HTML tag. In this case this boolean-field will be FALSE.

        The table below shows a few examples of what an HTML Element with the field for 'public final String str' set to the values in Column 1 would look like. The values in Column 2 represent what the resulting boolean would evaluate for the field 'public final boolean isClosing'

        Field: public final String str Field: public final boolean isClosing
        <A HREF="http://some.url.com"> FALSE
        <IMG SRC="MyImage.jpg"> FALSE
        </body> TRUE. Note that the tag begins with a forward-slash '/' character.
        </SPAN> TRUE. Note that the tag begins with a forward-slash '/' character.
        Code:
        Exact Field Declaration Expression:
        public final boolean isClosing;
        
    • Constructor Detail

      • TagNode

        🡅  🡇    
        public TagNode​(java.lang.String s)
        Creates a TagNode, an inherited class of 'HTMLNode' that can be used as an element of an HTML Vector.

        NOTE: Attribute values are neither parsed, nor checked when this constructor is used. This constructor could allow malformed HTML to be passed to the public final String str field!

        Example:
        TagNode tn = new TagNode("<DIV CLASS='SUMMARY' onmouseover=\"alert('Hello!');\">");
        System.out.println(tn.str);
        
        // Prints to Terminal: <DIV CLASS='SUMMARY' onmouseover="alert('Hello!');">
        
        Parameters:
        s - Any valid HTML tag, for instance: <H1>, <A HREF="somoe url">, <DIV ID="some id"> etc...
        Throws:
        MalformedTagNodeException - If the passed String wasn't valid - meaning it did not match the regular-expression parser.
        HTMLTokException - If the String found where the usual HTML token-element is situated is not a valid HTML element then the HTMLTokException will be thrown.
        See Also:
        HTMLTags.getTag_MEM_HEAP_CHECKOUT_COPY(String)
        Code:
        Exact Constructor Body:
         super(s);
        
         // If the second character of the string is a forward-slash, this must be a closing-element
         // For Example: </SPAN>, </DIV>, </A>, etc...
        
         isClosing = s.charAt(1) == '/';
        
         // This is the Element & Attribute Matcher used by the RegEx Parser.  If this Matcher
         // doesn't find a match, the parameter 's' cannot be a valid HTML Element.  NOTE: The
         // results of this matcher are also used to retrieve attribute-values, but here below,
         // its results are ignored.
        
         Matcher m = HTMLRegEx.P1.matcher(s);
        
         if (! m.find()) throw new MalformedTagNodeException(
             "The parser's regular-expression did not match the constructor-string.\n" +
             "The exact input-string was: [" + s + "]\n" +
             "NOTE:  The parameter-string is included as a field (ex.str) to this Exception.", s
         );
        
         // MINOR/MAJOR IMPROVEMENT... REUSE THE "ALLOCATED STRING TOKEN" from HTMLTag's class
         // THINK: Let the Garbage Collector take out as many duplicate-strings as is possible..
         // AND SOONER.  DECEMBER 2019: "Optimization" or ... "Improvement"
        
         String tokTEMP = m.group(1).toLowerCase();
        
         if ((m.start() != 0) || (m.end() != s.length()))
        
             throw new MalformedTagNodeException(
                 "The parser's regular-expression did not match the entire-string-length of the " +
                 "string-parameter to this constructor: m.start()=" + m.start() + ", m.end()=" + 
                 m.end() + ".\nHowever, the length of the Input-Parameter String was " +
                 '[' + s.length() + "]\nThe exact input-string was: [" + s + "]\nNOTE: The " +
                 "parameter-string is included as a field (ex.str) to this Exception.", s
             );
        
        
         // Get a copy of the 'tok' string that was already allocated on the heap; (OPTIMIZATON)
         // NOTE: There are already myriad strings for the '.str' field.
         // ALSO: Don't pay much attention to this line if it doesn't make sense... it's not
         //       that important.  If the HTML Token found was not a valid HTML5 token, this field
         //       will be null.
         // Java 14+ has String.intern() - that's what this is....
        
         this.tok = HTMLTags.getTag_MEM_HEAP_CHECKOUT_COPY(tokTEMP);
        
         // Now do the usual error check.
         if (this.tok == null) throw new HTMLTokException(
             "The HTML Tag / Token Element that is specified by the input string " +
             "[" + tokTEMP + "] is not a valid HTML Element Name.\n" +
             "The exact input-string was: [" + s + "]"
         );
        
      • TagNode

        🡅  🡇    
        public TagNode​(java.lang.String tok,
                       java.util.Properties attributes,
                       java.lang.Iterable<java.lang.String> keyOnlyAttributes,
                       SD quotes,
                       boolean addEndingForwardSlash)
        This will build a new TagNode that contains the inner tag attributes specified here. It is some-what checked for validity - though not all possible error cases are listed. If a major issue is discovered an exception is thrown.

        Example:
        Properties attributes = new Properties();
        attributes.put("CLASS", "SUMMARY");
        attributes.put("onmouseover", "alert('Hello');");
        
        Vector<String> booleanAttributes = new Vector<>();
        booleanAttributes.add("HIDDEN");
        
        TagNode tn = new TagNode("DIV", attributes, booleanAttributes, SD.DoubleQuotes, false);
        
        System.out.println(tn);
        // Prints To Terminal: <DIV CLASS="SUMMARY" onmouseover="alert('Hello');" HIDDEN>
        


        ATTRIBUTES: This constructor accepts both key-value attributes, and boolean / key-only attributes as input. These two lists are passed through separate parameters.
        Parameters:
        tok - This is the HTML Tag-Name. String's that this parameter would often be passed include 'div', 'img', 'span', 'a', etc... If this parameter does not contain a valid HTML Tag-Name, an exception will be thrown.
        attributes - This must be a table of HTML inner-tag key-values (a.k.a. HTML Attributes) that are acceptable for using with the HTML element that is being created. Validity checking includes *only* the following tests:

        • If any key of Properties parameter 'p' contains characters outside of this ASCII-subset: [A..Za..z0..9_-]
        • If any key of 'p' does not start with attributes a character from this subset: [A..Za..z]
        • If any value of 'p' has a "quote within quote" problem - a 'value' that contains quotation marks that are the same as the quotation-parameter SD quotes

        If any of these requirements fail, the exceptions listed below will throw.

        NOTE: When specifying a java.util.Properties parameter for which quotation-marks have already been added to the values inside the table, parameter SD quote must be set to null. In such cases, if 'quote' were not null, a second set of surrounding quotes would be appended each attribute-value in the output HTML-Element - and this would likely force a QuotesException to throw, due to the "quotes within quotes" issue.

        NOTE: The 'attributes' parameter may be null, and if so, it will be ignored. In this case, no key-value attributes will be incorporated into the TagNode.
        keyOnlyAttributes - This should be a valid list of "Attribute-Only" Inner-Tags. Such attributes are often called "Boolean Attributes." They are just a stand-alone keywords, without any value assignment. The CSS keyword 'HIDDEN', for example, is a commonly used Boolean-Attribute.

        NOTE: This parameter may be null, and if so, no boolean-attributes will be included in the HTML Element.
        quotes - This is either a single-quote (') or a double-quote ("). It uses the Enumerated Type: 'SD' from this package.

        NOTE: This parameter (quotes) may be null. If 'null' is passed, then it should be expected that the contents of the Properties parameter ('p') contains values that obey these rules:

        • Either: The values in Properties parameter 'p' already have quotes surrounding their String contents.
        • Or: The values in 'p' do not contain any white-space. HTML rules state that in such cases, say quotes are actually optional.

        These quotes are used to encapsulate the value String of all key-value pairs when building the HTML Element.

        IMPORTANT: If different quotes-selections are to be used for different attribute key-value pairs, then those quotes should be provided inside the Properties data-structure - already pre-wrapped. In these cases (when individualized quotes choices are necessary), this parameter must be passed null.
        addEndingForwardSlash - There are a few (very few) instances where an "ending forward-slash" is expected at the end of the HTML -Tag. If this is desired, set this value to TRUE.

        EXAMPLE: <IMG SRC='img/myPhoto1.png' /> - Note the tag ends with a '/' (forward-slash) character.
        Throws:
        InnerTagKeyException - This exception will throw if any of the keys in the Properties parameter contain non-standard HTML Attribute / Inner-Tag key Strings. HTML expects that attribute-names conform to a set of rules in order to be processed properly by a browser. There is a 'check(...)' in class InnerTagKeyException which delineates the exact requirements made by HTML for attribute-names.
        QuotesException - If there are "quotes within quotes" problems when invoking the TagNode constructor, this exception will throw. The problem occurs when one or more of the attribute key-value pairs have a quotation-choice such that the chosen quotation-mark is also found within the attribute-value.

        QuotesException will also throw in the case that an attribute key-value pair has elected to use the "No Quotes" option, but the attribute-value contains white-space.
        HTMLTokException - if an invalid HTML 4 or 5 token is not present (check is CASE_INSENSITIVE)
        See Also:
        InnerTagKeyException.check(String, String), QuotesException.check(String, SD, String), generateElementString(String, Properties, Iterable, SD, boolean)
        Code:
        Exact Constructor Body:
         this(
             generateElementString
                 (tok, attributes, keyOnlyAttributes, quotes, addEndingForwardSlash));
        
    • Method Detail

      • generateElementString

        🡅  🡇    
        protected static java.lang.String generateElementString​
                    (java.lang.String tok,
                     java.util.Properties attributes,
                     java.lang.Iterable<java.lang.String> keyOnlyAttributes,
                     SD quotes,
                     boolean addEndingForwardSlash)
        
        This builds an HTML Element as a String. This String may be passed to the standard HTML TagNode Constructor that accepts a String as input.
        Parameters:
        tok - This is the HTML Tag-Name. String's that this parameter would often be passed include 'div', 'img', 'span', 'a', etc... If this parameter does not contain a valid HTML Tag-Name, an exception will be thrown.
        attributes - This must be a table of HTML inner-tag key-values (a.k.a. HTML Attributes) that are acceptable for using with the HTML element that is being created. Validity checking includes *only* the following tests:

        • If any key of Properties parameter 'p' contains characters outside of this ASCII-subset: [A..Za..z0..9_-]
        • If any key of 'p' does not start with attributes a character from this subset: [A..Za..z]
        • If any value of 'p' has a "quote within quote" problem - a 'value' that contains quotation marks that are the same as the quotation-parameter SD quotes

        If any of these requirements fail, the exceptions listed below will throw.

        NOTE: When specifying a java.util.Properties parameter for which quotation-marks have already been added to the values inside the table, parameter SD quote must be set to null. In such cases, if 'quote' were not null, a second set of surrounding quotes would be appended each attribute-value in the output HTML-Element - and this would likely force a QuotesException to throw, due to the "quotes within quotes" issue.

        NOTE: The 'attributes' parameter may be null, and if so, it will be ignored. In this case, no key-value attributes will be incorporated into the TagNode.
        keyOnlyAttributes - This should be a valid list of "Attribute-Only" Inner-Tags. Such attributes are often called "Boolean Attributes." They are just a stand-alone keywords, without any value assignment. The CSS keyword 'HIDDEN', for example, is a commonly used Boolean-Attribute.

        NOTE: This parameter may be null, and if so, no boolean-attributes will be included in the HTML Element.
        quotes - This is either a single-quote (') or a double-quote ("). It uses the Enumerated Type: 'SD' from this package.

        NOTE: This parameter (quotes) may be null. If 'null' is passed, then it should be expected that the contents of the Properties parameter ('p') contains values that obey these rules:

        • Either: The values in Properties parameter 'p' already have quotes surrounding their String contents.
        • Or: The values in 'p' do not contain any white-space. HTML rules state that in such cases, say quotes are actually optional.

        These quotes are used to encapsulate the value String of all key-value pairs when building the HTML Element.

        IMPORTANT: If different quotes-selections are to be used for different attribute key-value pairs, then those quotes should be provided inside the Properties data-structure - already pre-wrapped. In these cases (when individualized quotes choices are necessary), this parameter must be passed null.
        addEndingForwardSlash - There are a few (very few) instances where an "ending forward-slash" is expected at the end of the HTML -Tag. If this is desired, set this value to TRUE.

        EXAMPLE: <IMG SRC='img/myPhoto1.png' /> - Note the tag ends with a '/' (forward-slash) character.
        Returns:
        This method returns an HTML Element, as a String.
        Throws:
        InnerTagKeyException - This exception will throw if any of the keys in the Properties parameter contain non-standard HTML Attribute / Inner-Tag key Strings. HTML expects that attribute-names conform to a set of rules in order to be processed properly by a browser. There is a 'check(...)' in class InnerTagKeyException which delineates the exact requirements made by HTML for attribute-names.
        QuotesException - If there are "quotes within quotes" problems when invoking the TagNode constructor, this exception will throw. The problem occurs when one or more of the attribute key-value pairs have a quotation-choice such that the chosen quotation-mark is also found within the attribute-value.

        QuotesException will also throw in the case that an attribute key-value pair has elected to use the "No Quotes" option, but the attribute-value contains white-space.
        HTMLTokException - if an invalid HTML 4 or 5 token is not present CASE_INSENSITIVE
        See Also:
        HTMLTokException.check(String[]), InnerTagKeyException.check(String, String), QuotesException.check(String, SD, String)
        Code:
        Exact Method Body:
         String computedQuote = (quotes == null) ? "" : ("" + quotes.quote);
        
         HTMLTokException.check(tok);
        
         // The HTML Element is "built" using a StringBuilder
         StringBuilder sb = new StringBuilder();
         sb.append("<" + tok);
        
         // If there are any Inner-Tag Key-Value pairs, insert them first.
         if ((attributes != null) && (attributes.size() > 0))
        
             for (String key : attributes.stringPropertyNames())
             {
                 String value = attributes.getProperty(key);
        
                 InnerTagKeyException.check(key, value);
        
                 QuotesException.check(
                     value, quotes,
                     "parameter 'Properties' contains:\nkey:\t" + key + "\nvalue:\t" + value + "\n"
                 );
        
                 sb.append(" " + key + '=' + computedQuote + value + computedQuote);
             }
        
         // If there are any Key-Only Inner-Tags (Boolean Attributes), insert them next.
         if (keyOnlyAttributes != null)
        
             for (String keyOnlyAttribute : keyOnlyAttributes) 
             {
                 InnerTagKeyException.check(keyOnlyAttribute);
                 sb.append(" " + keyOnlyAttribute);
             }
        
         // Add a closing forward-slash
         sb.append(addEndingForwardSlash ? " />" : ">");
        
         // Build the String, using the StringBuilder, and return the newly-constructed HTML Element
         return sb.toString();
        
      • isTagNode

        🡅  🡇    
        public boolean isTagNode()
        This method identifies that 'this' instance of (abstract parent-class) HTMLNode is, indeed, an instance of sub-class TagNode.
        Overrides:
        isTagNode in class HTMLNode
        Returns:
        This method shall always return TRUE It overrides the parent-class HTMLNode method isTagNode(), which always returns FALSE.
        See Also:
        isTagNode()
        Code:
        Exact Method Body:
         return true;
        
      • ifTagNode

        🡅  🡇    
        public TagNode ifTagNode()
        This method identifies that 'this' instance of (abstract parent-class) HTMLNode is, indeed, an instance of sub-class TagNode.
        Overrides:
        ifTagNode in class HTMLNode
        Returns:
        'this' reference. This method can be used inside loops for improving the readability of loop-condition expressions. See example below:

        Example:
        Vector<HTMLNode> fileVec = HTMLPage.getPageTokens(new URL("http://some.url.com"), false);
        TagNode tn;
        
        // NOTE: The casting to class TagNode is automatically acheived with this method,
        //       which can make loops a lot easier to read.  Only a TagNode instance is allowed
        //       to have attributes - such as the CSS "Class" attribute.  Invoking method "AV(..)"
        //       on an instance of HTMLNode would cause a compile-time error.
        
        for (HTMLNode n : fileVec)
            if ((tn = fileVec.elementAt(i).ifTagNode()) != null)
                System.out.println("The CSS Class Attribute is: " + tn.AV("class"));
        


        This method-version overrides the parent-class-version, which always returns null. This method is not overriden by other HTMLNode sub-classes.
        See Also:
        ifTagNode()
        Code:
        Exact Method Body:
         return this;
        
      • openTagPWA

        🡅  🡇    
        public TagNode openTagPWA()
        This is a loop-optimization method that makes finding opening TagNode's - with attribute-values - quites a bit faster. All HTMLNode subclasses implement this method, but only TagNode instances will ever return a non-null value.

        Example:
        // Download a web-page, and parse it into a vector
        Vector<HTMLNode> someWebPage = HTMLPage.getPageTokens(pageURL, false);
        
        TagNode tn;
        
        // Search for a TagNode that has a CSS class, in a quick and efficient way.
        // NOTE: A temporary 'tn' / TagNode Variable is required in this loop.
        
        for (HTMLNode n : someWebPage)
        
            // Here, this invokation shall always receive 'null' - unless the node being asked
            // is an opening-tag, whose 'str' is guaranteed to be long enough to possibly have a
            // CSS 'CLASS' attribute.
            //
            // UNLESS 'tn' receives a non-null value, there is no need to proceed with this check.
        
            if ((tn = n.openTagNodePWA()) != null)
        
                // HERE, WE CAN INVOKE AV("class")
                if (tn.AV("class").contains("someCSSClass")
                { handle_TagNode(tn); }
        


        NOTE: The exact computation for determining whether this method returns null is to check whether the internal HTMLNode.str field is at least 5 characters longer than the this class' .tok field. This number, '5', is determined by the additional characters '<', '>', ' ', '=' and one character of attribute-name. These characters are the additional characters other than the 'tok' field of the HTML tag / token itself.
        Overrides:
        openTagPWA in class HTMLNode
        Returns:
        This method returns exactly 'this' instance, but only if 'this' instance's isClosing field is FALSE, and if 'this' instance has an internal .str field that is long enough to (possibly) have inner-tag attributes. This is an optimization-routine for search loops that have to iterate all types of nodes.
        See Also:
        openTagPWA()
        Code:
        Exact Method Body:
         // Closing TagNode's simply may not have attributes
         if (this.isClosing) return null;
        
         // A TagNode whose '.str' field is not AT LEAST 5 characters LONGER than the length of the
         // HTML-Tag / Token, simply cannot have an attribute.
         //
         // NOTE: Below is the shortest possible HTML tag that could have an attribute-value pair.
         // COMPUTE: '<' + TOK.LENGTH + SPACE + 'c' + '=' + '>'
        
         if (this.str.length() < (this.tok.length() + 5)) return null;
        
         // This TagNode is an opening HTML tag (like <DIV ...>, rather than </DIV>),
         // and there are at least two additional characters after the token, such as: <DIV A...>
         // It is not guaranteed that this tag has attributes, but it is possibly - based on these
         /// optimization methods, and further investigation would have merit.
        
         return this;
        
      • openTag

        🡅  🡇    
        public TagNode openTag()
        This is a loop-optimization method that makes finding opening TagNode's - with attribute-values - quites a bit faster. All HTMLNode subclasses implement this method, but only instances will ever return a non-null value.
        Overrides:
        openTag in class HTMLNode
        Returns:
        Returns null if and only if 'this' instance' isClosing field is false. When a non-null return-value is acheived, that value will always be 'this' instance.
        See Also:
        openTag()
        Code:
        Exact Method Body:
         return isClosing ? null : this;
        
      • isTag

        🡅  🡇    
        public boolean isTag​(java.lang.String... possibleTags)
        Receives a list of html-elements which the this.tok field must match. This method returns TRUE if any match is found.

        example
        Parameters:
        possibleTags - This non-null list of potential HTML tags.
        Returns:
        TRUE If this.tok matches at least one of these tags.
        See Also:
        tok
        Code:
        Exact Method Body:
         for (String htmlTag : possibleTags) if (htmlTag.equalsIgnoreCase(this.tok)) return true;
                
         return false;
        
      • isTagExcept

        🡅  🡇    
        public boolean isTagExcept​(java.lang.String... possibleTags)
        Receives a list of html-elements which this.tok field MAY NOT match. This method returns FALSE if any match is found.
        Parameters:
        possibleTags - This must be a non-null list of potential HTML tags.
        Returns:
        FALSE If this.tok matches any one of these tags, and TRUE otherwise.
        See Also:
        tok, isTag(String[])
        Code:
        Exact Method Body:
         for (String htmlTag : possibleTags) if (htmlTag.equalsIgnoreCase(this.tok)) return false;
                
         return true;
        
      • isTag

        🡅  🡇    
        public boolean isTag​(TC tagCriteria,
                             java.lang.String... possibleTags)
        Receives two "criteria-specifier" parameters. This method shall return TRUE if:

        • Field 'isClosing' is equal-to / consistent-with TC tagCriteria
        • Field 'tok' is equal to at least one of the 'possibleTags'


        example
        Parameters:
        tagCriteria - This ought to be either 'TC.OpeningTags' or TC.ClosingTags'. This parameter specifies what 'this' instance of TagNode is expected to contain, as this.isClosing field shall be compared against it.
        possibleTags - This is presumed to be a non-zero-length, and non-null-valued list of html tokens.
        Returns:
        TRUE If 'this' matches the specified criteria, and FALSE otherwise.
        See Also:
        TC, isClosing, tok
        Code:
        Exact Method Body:
         // Requested an "OpeningTag" but this is a "ClosingTag"
         if ((tagCriteria == TC.OpeningTags) && this.isClosing) return false;
        
         // Requested a "ClosingTag" but this is an "OpeningTag"
         if ((tagCriteria == TC.ClosingTags) && ! this.isClosing) return false;
        
         for (int i=0; i < possibleTags.length; i++)
        
             if (this.tok.equalsIgnoreCase(possibleTags[i]))
                    
                 // Found a TOKEN match, return TRUE immediately
                 return true;
        
         // None of the elements in 'possibleTags' equalled tn.tok
         return false;
        
      • isTagExcept

        🡅  🡇    
        public boolean isTagExcept​(TC tagCriteria,
                                   java.lang.String... possibleTags)
        Receives a TagNode and then two "criteria-specifier" parameters. This method shall return FALSE if:

        • Field 'isClosing' is not equal-to / not consistent-with TC tagCriteria
        • Field 'tok' is equal-to any of the 'possibleTags'
        Parameters:
        tagCriteria - tagCriteria This ought to be either 'TC.OpeningTags' or TC.ClosingTags' This parameter specifies what 'this' instance of TagNode is expected to contain, as this.isClosing field shall be compared against it.
        possibleTags - This is presumed to be a non-zero-length, and non-null-valued list of html tokens.
        Returns:
        TRUE If this TagNode 'n' matches the specified criteria explained above, and FALSE otherwise.
        See Also:
        TC, tok, isClosing
        Code:
        Exact Method Body:
         // Requested an "OpeningTag" but this is a "ClosingTag"
         if ((tagCriteria == TC.OpeningTags) && this.isClosing) return false;
        
         // Requested a "ClosingTag" but this is an "OpeningTag"
         if ((tagCriteria == TC.ClosingTags) && ! this.isClosing) return false;
        
         for (int i=0; i < possibleTags.length; i++)
        
             if (this.tok.equalsIgnoreCase(possibleTags[i]))
        
                 // The Token of the input node was a match with one of the 'possibleTags'
                 // Since this is "Except" - we must return 'false'
        
                 return false;
        
         // None of the elements in 'possibleTags' equalled tn.tok
         // since this is "Except" - return 'true'
        
         return true;
        
      • AV

        🡅  🡇    
        public java.lang.String AV​(java.lang.String innerTagAttribute)
        The letters: AV simply mean "Attribute Value". In this HTML scrape & search package, the words attribute and inner-tag are used synonymously.

        This will return the value of any "Inner Tag" inside the HTML TagNode. An inner-tag is the choice of wording used in this scrape package - partially for brevity since "tag" is usually interchangeable with "inner tag." Often HTML coders refer to this particular data as an HTML Element Attribute (or more simply, just "Attribute").

        example

        The following example will hilite what this method "AV(String attributeName)" can achieve. The output that is generated has been transcribed to the next window (after the one below).

        Example:
        // This field prints its output both to the terminal, and to an internal string-buffer.
        static final StorageWriter sw = new StorageWriter();
        
        // Load a page from a "reputable" news-source, and parse it into a vectorized-html page.
        Vector<HTMLNode> page = HTMLPage.getPageTokens(new URL("https://foxnews.com"), false);
        
        // Select all HTMLNodes that are "instanceof" TagNode, where the Element is "IMG" and,
        // also, only select images that have an "ALT" text-string inner-tag (a.k.a.
        // english-description string).
        int[] imgPosArr = InnerTagFind.all(page, "img", "alt");
        
        // This uses the "Attribute Update Mode" (AUM) 'Replace' to convert the (very-long,
        // very-unreadable) variable Image-URL's to just say the word "REPLACED" when printing
        // the node.
        //
        // NOTE: They could easily be retrieved and saved, if needed.
        
        Attributes.update(page, AUM.Replace, imgPosArr, "data-src", "REMOVED", null);
        Attributes.update(page, AUM.Replace, imgPosArr, "data-srcset", "REMOVED", null);
        
        for (int pos : imgPosArr)
        {
            // Retrieves an HTML '<IMG SRC=...>' TagNode
            TagNode tn = (TagNode) page.elementAt(pos);
        
            // Retrieves the 'IMG' ALT=... text
            String altText = tn.AV("alt");
        
            // Clean up some of the HTML 'escape' characters (for readability-printing purposes)
            altText = Escape.replaceAll(altText).replace("&apos;","'");
        
            // Print out the TagNode.  Use UNIX Color-Code "bright-yellow" (for readability),
            // and the "ALT" attribute
            sw.println(C.BYELLOW + tn + C.RESET + "\nALT: [" + C.BRED + altText + C.RESET + "]\n");
        }
        FileRW.writeFile(C.toHTML(sw.getString(), true, true), "alt-text.txt");
        

        The following text was generated by the above program, scraping Yahoo! News. It prints out the 'alt' attribute for all HTML <IMG> elements.

        UNIX Shell Command Output:
        <img src="//static.foxnews.com/static/orion/styles/img/fox-news/bg/fn-logo-watch-now.png" alt="Fox News"> ALT: [Fox News] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/b6a0e412-d473-4dab-b498-335e45d4c2e2/f65a1645-8c2d-4358-ae27-475863ff0098/1280x720/match/480/270/image.jpg" alt="Easter Sunday at Washington National Cathedral"> ALT: [Easter Sunday at Washington National Cathedral] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/957bd65c-090d-4a21-a3ce-c2a96ad810f3/fcb61255-7f3c-4011-b969-4baa7eb1a501/1280x720/match/480/270/image.jpg" alt="Easter Sunday at Saddleback Church with Rick Warren"> ALT: [Easter Sunday at Saddleback Church with Rick Warren] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/21388d3a-2474-45b0-a426-c6216579e628/6f4f9f8f-686e-41e2-859a-2b0173ec396c/1280x720/match/480/270/image.jpg" alt="Easter Sunday at First Baptist Dallas with Dr. Robert Jeffress"> ALT: [Easter Sunday at First Baptist Dallas with Dr. Robert Jeffress] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/d1edbbd6-4d73-4c15-914e-79c93a9f4292/73e97a51-0eff-4a5c-a5c6-3bdcf46db949/1280x720/match/480/270/image.jpg" alt="Easter Sunday Mass at St. Patrick's Cathedral"> ALT: [Easter Sunday Mass at St. Patrick's Cathedral] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/a34386a0-1571-47f2-af4c-3b939ae1933a/666dcd16-b5fd-49a3-89b6-98e8c738989e/1280x720/match/480/270/image.jpg" alt="Easter service at Lakewood Church with Joel Osteen"> ALT: [Easter service at Lakewood Church with Joel Osteen] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/a65bd207-e193-4dac-a22b-9befbb2277a1/dc0a4684-b98d-439f-9137-771fc7651420/1280x720/match/480/270/image.jpg" alt="Pope Francis gives Urbi et Orbi blessing"> ALT: [Pope Francis gives Urbi et Orbi blessing] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/11b72aee-0f98-4ab6-9a5f-ffbdab2eaab3/6338cc80-9038-47e8-b417-41252465ea38/1280x720/match/480/270/image.jpg" alt="Pope Francis presides over Easter Mass"> ALT: [Pope Francis presides over Easter Mass] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/53ee3ae5-c56d-4004-8fc4-deaed9a81f1d/ee9ac8f8-7c88-4d36-911f-4bb709846561/1280x720/match/480/270/image.jpg" alt="Pope Francis presides over Easter vigil ceremony"> ALT: [Pope Francis presides over Easter vigil ceremony] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/480/270/b0ac3b9b6689b080244fb89788ee7f0e.jpg" alt="President Trump tells Judge Jeanine that America will be back bigger, better and stronger than ever before"> ALT: [President Trump tells Judge Jeanine that America will be back bigger, better and stronger than ever before] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/97fc9be6-aaf0-4fe0-91d3-3a46f2b11443/f1955d76-1bc4-4b23-8161-74a2ab6f6bf5/1280x720/match/480/270/image.jpg" alt="Rudy Giuliani hits back at Democrats' pandemic politics"> ALT: [Rudy Giuliani hits back at Democrats' pandemic politics] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/1280/533/52d91505f5ca105624f439a86e062ced.jpg?tl=1&ve=1" alt="Country of coronavirus' origin blocks research on outbreak, deleted posts show"> ALT: [Country of coronavirus' origin blocks research on outbreak, deleted posts show] <img src="//a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2020/04/1024/576/AP20103813232544.jpg?tl=1&ve=1" alt="Tornadoes kill at least 6 in Mississippi, damage homes, buildings in Louisiana"/> ALT: [Tornadoes kill at least 6 in Mississippi, damage homes, buildings in Louisiana] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/1024/576/362782ebab386b42da12b464073f9395.jpg?tl=1&ve=1" alt="Dad, 20-year-old son laid to rest after losing coronavirus battles 3 days apart"/> ALT: [Dad, 20-year-old son laid to rest after losing coronavirus battles 3 days apart] <img src="//a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2020/04/1024/576/marylou-amer.jpg?tl=1&ve=1" alt="Detective died from coronavirus after twice being denied test, sister says"/> ALT: [Detective died from coronavirus after twice being denied test, sister says] <img src="//a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2020/04/1024/576/WisconsinFirefigther_1.jpg?tl=1&ve=1" alt="Check out how these firefighters paid tribute to health-care workers"/> ALT: [Check out how these firefighters paid tribute to health-care workers] <img src="//static.foxnews.com/static/orion/styles/img/fox-news/bg/fn-logo-watch-now.png" alt="Fox News"> ALT: [Fox News] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/b6a0e412-d473-4dab-b498-335e45d4c2e2/f65a1645-8c2d-4358-ae27-475863ff0098/1280x720/match/480/270/image.jpg" alt="Easter Sunday at Washington National Cathedral"> ALT: [Easter Sunday at Washington National Cathedral] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/957bd65c-090d-4a21-a3ce-c2a96ad810f3/fcb61255-7f3c-4011-b969-4baa7eb1a501/1280x720/match/480/270/image.jpg" alt="Easter Sunday at Saddleback Church with Rick Warren"> ALT: [Easter Sunday at Saddleback Church with Rick Warren] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/21388d3a-2474-45b0-a426-c6216579e628/6f4f9f8f-686e-41e2-859a-2b0173ec396c/1280x720/match/480/270/image.jpg" alt="Easter Sunday at First Baptist Dallas with Dr. Robert Jeffress"> ALT: [Easter Sunday at First Baptist Dallas with Dr. Robert Jeffress] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/d1edbbd6-4d73-4c15-914e-79c93a9f4292/73e97a51-0eff-4a5c-a5c6-3bdcf46db949/1280x720/match/480/270/image.jpg" alt="Easter Sunday Mass at St. Patrick's Cathedral"> ALT: [Easter Sunday Mass at St. Patrick's Cathedral] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/a34386a0-1571-47f2-af4c-3b939ae1933a/666dcd16-b5fd-49a3-89b6-98e8c738989e/1280x720/match/480/270/image.jpg" alt="Easter service at Lakewood Church with Joel Osteen"> ALT: [Easter service at Lakewood Church with Joel Osteen] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/a65bd207-e193-4dac-a22b-9befbb2277a1/dc0a4684-b98d-439f-9137-771fc7651420/1280x720/match/480/270/image.jpg" alt="Pope Francis gives Urbi et Orbi blessing"> ALT: [Pope Francis gives Urbi et Orbi blessing] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/11b72aee-0f98-4ab6-9a5f-ffbdab2eaab3/6338cc80-9038-47e8-b417-41252465ea38/1280x720/match/480/270/image.jpg" alt="Pope Francis presides over Easter Mass"> ALT: [Pope Francis presides over Easter Mass] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/53ee3ae5-c56d-4004-8fc4-deaed9a81f1d/ee9ac8f8-7c88-4d36-911f-4bb709846561/1280x720/match/480/270/image.jpg" alt="Pope Francis presides over Easter vigil ceremony"> ALT: [Pope Francis presides over Easter vigil ceremony] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/480/270/b0ac3b9b6689b080244fb89788ee7f0e.jpg" alt="President Trump tells Judge Jeanine that America will be back bigger, better and stronger than ever before"> ALT: [President Trump tells Judge Jeanine that America will be back bigger, better and stronger than ever before] <img src="//a57.foxnews.com/cf-images.us-east-1.prod.boltdns.net/v1/static/694940094001/97fc9be6-aaf0-4fe0-91d3-3a46f2b11443/f1955d76-1bc4-4b23-8161-74a2ab6f6bf5/1280x720/match/480/270/image.jpg" alt="Rudy Giuliani hits back at Democrats' pandemic politics"> ALT: [Rudy Giuliani hits back at Democrats' pandemic politics] <img data-src="REMOVED" alt="Pope Francis presides over Easter Mass" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Pope Francis presides over Easter Mass] <img data-src="REMOVED" alt="Oil surges after Saudi Arabia, Russia end price war with historic deal" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Oil surges after Saudi Arabia, Russia end price war with historic deal] <img data-src="REMOVED" alt="Broadway star in 'very critical condition' amid possible coronavirus fight" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Broadway star in 'very critical condition' amid possible coronavirus fight] <img data-src="REMOVED" alt="Rev. Franklin Graham's Easter Sunday message from NYC's Central Park" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Rev. Franklin Graham's Easter Sunday message from NYC's Central Park] <img data-src="REMOVED" alt="6 shot at California party that violated stay-at-home order, drew hundreds" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [6 shot at California party that violated stay-at-home order, drew hundreds] <img data-src="REMOVED" alt="JUSTIN HASKINS: AOC and friends peddling false cure for what ails US" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [JUSTIN HASKINS: AOC and friends peddling false cure for what ails US] <img data-src="REMOVED" alt="Cuban predicts new post-virus business models, talks of possible WH run" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Cuban predicts new post-virus business models, talks of possible WH run] <img data-src="REMOVED" alt="Police attacked with stones, iron bars while enforcing social distance measures" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Police attacked with stones, iron bars while enforcing social distance measures] <img data-src="REMOVED" alt="KFC mocks fans' homemade attempts to recreate fried chicken during coronavirus shutdown" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [KFC mocks fans' homemade attempts to recreate fried chicken during coronavirus shutdown] <img data-src="REMOVED" alt="Nurse confronts 'stupid' men defying coronavirus social distancing" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Nurse confronts 'stupid' men defying coronavirus social distancing] <img data-src="REMOVED" alt="What's coming to and leaving Hulu in April amid the coronavirus pandemic" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [What's coming to and leaving Hulu in April amid the coronavirus pandemic] <img data-src="REMOVED" alt="Nurses treating coronavirus patients find tires slashed" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Nurses treating coronavirus patients find tires slashed] <img data-src="REMOVED" alt="SEE IT: Woman, 93, makes coronavirus plea for more beer amid lockdown in viral photo" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [SEE IT: Woman, 93, makes coronavirus plea for more beer amid lockdown in viral photo] <img data-src="REMOVED" alt="'Tiger King': Carole, Howard Baskin say they feel 'betrayal' from filmmakers, are getting death threats" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: ['Tiger King': Carole, Howard Baskin say they feel 'betrayal' from filmmakers, are getting death threats] <img data-src="REMOVED" alt="Woman arrested for entering New Orleans airport naked amidst coronavirus lockdown" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Woman arrested for entering New Orleans airport naked amidst coronavirus lockdown] <img data-src="REMOVED" alt="Students in this country are protesting schools reopening" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Students in this country are protesting schools reopening] <img data-src="REMOVED" alt="‘Huge influx’ of COVID-19 patients putting strain on Moscow hospitals, Kremlin says" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [‘Huge influx’ of COVID-19 patients putting strain on Moscow hospitals, Kremlin says] <img data-src="REMOVED" alt="Olivia Munn on getting married someday: 'I never have ever been that girl'" src="//static.foxnews.com/static/orion/img/clear-16x9.gif" /> ALT: [Olivia Munn on getting married someday: 'I never have ever been that girl'] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/480/270/dd3f0280320f00471aa6e99ac2d14b8e.jpg" alt="Join Pete Hegseth as he focuses on one of the most important moments of the year for Christianity: Easter Sunday."> ALT: [Join Pete Hegseth as he focuses on one of the most important moments of the year for Christianity: Easter Sunday.] <img src="//a57.foxnews.com/hp.foxnews.com/images/2020/04/320/180/1c0802431b0b4b32b2cc72b49995772d.png" alt="America Together Category Page"> ALT: [America Together Category Page] <img src="//a57.foxnews.com/static.foxbusiness.com/foxbusiness.com/content/uploads/2020/04/480/270/Credible-no-cost-loan-thumbnail-1069166364.jpg" alt="How to refinance your mortgage without paying upfront closing costs"> ALT: [How to refinance your mortgage without paying upfront closing costs] <img data-src="REMOVED" alt="Paul Batura" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Paul Batura] <img data-src="REMOVED" alt="Scott Gunn" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Scott Gunn] <img data-src="REMOVED" alt="Jim Daly" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Jim Daly] <img src="//a57.foxnews.com/hp.foxnews.com/images/2017/10/128/128/63ba2426c69bdae0a03232c5b547f162.jpg" alt="Podcast Logo"> ALT: [Podcast Logo] <img data-src="REMOVED" alt="7 cool desserts to make with PEEPS" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [7 cool desserts to make with PEEPS] <img data-src="REMOVED" alt="Prankster costs pizzerias THOUSANDS" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Prankster costs pizzerias THOUSANDS] <img data-src="REMOVED" alt="Dolly's SPECIAL Easter message" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Dolly's SPECIAL Easter message] <img data-src="REMOVED" alt="'Tiger King' star getting DEATH threats" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: ['Tiger King' star getting DEATH threats] <img data-src="REMOVED" alt="Singer SLAMS Trump" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Singer SLAMS Trump] <img data-src="REMOVED" alt="Actor in 'VERY critical condition'" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Actor in 'VERY critical condition'] <img data-src="REMOVED" alt="Home tanning attempt goes WRONG" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Home tanning attempt goes WRONG] <img data-src="REMOVED" alt="Coronavirus ‘achilles heel’ FOUND?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Coronavirus ‘achilles heel’ FOUND?] <img data-src="REMOVED" alt="Are 5G towers spreading COVID-19?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Are 5G towers spreading COVID-19?] <img data-src="REMOVED" alt="10 tech spring cleaning tips for the quarantine era" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [10 tech spring cleaning tips for the quarantine era] <img data-src="REMOVED" alt="Can THIS slow coronavirus’ spread?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Can THIS slow coronavirus’ spread?] <img data-src="REMOVED" alt="Iconic singer: 'Music doesn't mean ANYTHING'" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Iconic singer: 'Music doesn't mean ANYTHING'] <img data-src="REMOVED" alt="'Tiger King' star tells ALL" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: ['Tiger King' star tells ALL] <img data-src="REMOVED" alt="Hanks hosts SPECIAL show" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Hanks hosts SPECIAL show] <img data-src="REMOVED" alt="Best dramas to STREAM" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Best dramas to STREAM] <img data-src="REMOVED" alt="BEST idea for a quarantine birthday?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [BEST idea for a quarantine birthday?] <img data-src="REMOVED" alt="WWI grenade found WHERE?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [WWI grenade found WHERE?] <img data-src="REMOVED" alt="What to do for EASTER during coronavirus crisis" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [What to do for EASTER during coronavirus crisis] <img data-src="REMOVED" alt="Adopting RABBITS the new quarantine thing?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [Adopting RABBITS the new quarantine thing?] <img data-src="REMOVED" alt="New CANNONBALL RUN record set?" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [New CANNONBALL RUN record set?] <img data-src="REMOVED" alt="America Together: YOUR inspiring pics" src="//global.fncstatic.com/static/orion/img/clear.gif" /> ALT: [America Together: YOUR inspiring pics] <img data-srcset="REMOVED" data-src="REMOVED" alt="Charlie Kirk fires back at Trump critics over economic shutdown: Time to stop 'crisis in America'" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Charlie Kirk fires back at Trump critics over economic shutdown: Time to stop 'crisis in America'] <img data-srcset="REMOVED" data-src="REMOVED" alt="Charlie Kirk fires back at Trump critics over economic shutdown: Time to stop 'crisis in America'" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Charlie Kirk fires back at Trump critics over economic shutdown: Time to stop 'crisis in America'] <img data-srcset="REMOVED" data-src="REMOVED" alt="Reporter's Notebook: How Congress may proceed in the 'After Coronavirus' era" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Reporter's Notebook: How Congress may proceed in the 'After Coronavirus' era] <img data-srcset="REMOVED" data-src="REMOVED" alt="Tornadoes raging across South kill at least 6 in Mississippi, damage hundreds of buildings in Louisiana" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Tornadoes raging across South kill at least 6 in Mississippi, damage hundreds of buildings in Louisiana] <img data-srcset="REMOVED" data-src="REMOVED" alt="Justin Haskins: Coronavirus and socialism – AOC and friends peddling false cure for what ails US" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Justin Haskins: Coronavirus and socialism – AOC and friends peddling false cure for what ails US] <img data-srcset="REMOVED" data-src="REMOVED" alt="Kathie Lee Gifford says her kids 'insisted' she self-isolate in Florida: ‘It’s quite beautiful’" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Kathie Lee Gifford says her kids 'insisted' she self-isolate in Florida: ‘It’s quite beautiful’] <img data-srcset="REMOVED" data-src="REMOVED" alt="Matthew McConaughey, wife Camila donate 80K face masks to coronavirus first-responders in Texas, Louisiana" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Matthew McConaughey, wife Camila donate 80K face masks to coronavirus first-responders in Texas, Louisiana] <img data-srcset="REMOVED" data-src="REMOVED" alt="Gov. DeSantis on protecting senior citizens, expanding coronavirus testing and supporting health care workers" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Gov. DeSantis on protecting senior citizens, expanding coronavirus testing and supporting health care workers] <img data-srcset="REMOVED" data-src="REMOVED" alt="First coronavirus relief checks deposited to Americans: IRS" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [First coronavirus relief checks deposited to Americans: IRS] <img data-srcset="REMOVED" data-src="REMOVED" alt="FBI exposes coronavirus scam after 39M masks promised from overseas fail to reach California hospitals" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [FBI exposes coronavirus scam after 39M masks promised from overseas fail to reach California hospitals] <img data-srcset="REMOVED" data-src="REMOVED" alt="Mustang crashes off California cliff during coronavirus lockdown, gets pulled out by 'Hulk' wrecker" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Mustang crashes off California cliff during coronavirus lockdown, gets pulled out by 'Hulk' wrecker] <img data-srcset="REMOVED" data-src="REMOVED" alt="Trump administration plans to open 2.3 million acres of wildlife refuges to hunting and fishing" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Trump administration plans to open 2.3 million acres of wildlife refuges to hunting and fishing] <img data-srcset="REMOVED" data-src="REMOVED" alt="Many restaurants weather coronavirus storm by converting to grocery stores" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Many restaurants weather coronavirus storm by converting to grocery stores] <img data-srcset="REMOVED" data-src="REMOVED" alt="Coronavirus: How to celebrate a birthday during the pandemic" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Coronavirus: How to celebrate a birthday during the pandemic] <img data-srcset="REMOVED" data-src="REMOVED" alt="Woman accidentally tans company logo onto her leg during coronavirus lockdown" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Woman accidentally tans company logo onto her leg during coronavirus lockdown] <img data-srcset="REMOVED" data-src="REMOVED" alt="Petition calling for WHO boss Tedros to resign nears 1M signatures" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Petition calling for WHO boss Tedros to resign nears 1M signatures] <img data-srcset="REMOVED" data-src="REMOVED" alt="REPAYE could lower your student loan payments — here's how" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [REPAYE could lower your student loan payments — here's how] <img data-srcset="REMOVED" data-src="REMOVED" alt="Matthew McConaughey, wife Camila donate 80K face masks to coronavirus first-responders in Texas, Louisiana" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Matthew McConaughey, wife Camila donate 80K face masks to coronavirus first-responders in Texas, Louisiana] <img data-srcset="REMOVED" data-src="REMOVED" alt="Fox News Sunday - Sunday, April 12" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Fox News Sunday - Sunday, April 12] <img data-srcset="REMOVED" data-src="REMOVED" alt="Dolly Parton shares special Easter message: It's 'a little different this year' with coronavirus" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: [Dolly Parton shares special Easter message: It's 'a little different this year' with coronavirus] <img data-srcset="REMOVED" data-src="REMOVED" alt="'Tiger King': Carole, Howard Baskin say they feel 'betrayal' from filmmakers, are getting death threats" src="//global.fncstatic.com/static/orion/img/clear.gif"> ALT: ['Tiger King': Carole, Howard Baskin say they feel 'betrayal' from filmmakers, are getting death threats]
        Parameters:
        innerTagAttribute - This may be any Java-String, but very common examples of HTML attributes (and their values) include:
        Attribute / Inner-Tag Commonly Found Attribute-Values
        HREF="..." where the attribute value ("...") - is a URL
        SRC='...' and the attribute value specified ('...') - is usually an Image-URL (like a "pic.jpg")
        ID=... where the attribute value (...) - would be a "CSS Identifier Tag"
        CLASS='...' and the attribute value ('...') - is the "CSS Class" to which the particular HTML element belongs
        OnClick="..." and the attribute value ("...") - is often a function call to a Java-Script module, or actual Java-Script
        href="..." SAME AS ABOVE! - Remember an "inner-tag" or "attribute" name is CASE-INSENSITIVE
        src='...' SAME AS ABOVE! - Remember an "inner-tag" or "attribute" name is CASE-INSENSITIVE
        Returns:
        The Attribute Value, which for the inner-tag named by the input String-parameter.

        NOTE: If 'this' TagNode is a closing-tag (specifically, if the .isClosing boolean-field is true), this method will exit immediately, and return null. Unlike the other Attribute-Modification Methods in this class, no ClosingTagNodeException shall throw, but rather the method will exit gracefully. This is because this method is a 'getter' only. No invalid data will be instantiated or saved - even if this method executes to completion. Note, though, valid HTML pages do not allow attributes inside of closing HTML Elements.

        ALSO: If the 'str' String-field of 'this' TagNode has a length that isn't greater than the following: 3 + tok.length() + innerTagAttribute.trim().length()), then in this case this AV method will also return null. The rational for returning null here is that the final String str field simply does not have enough characters to contain this inner-tag.
        See Also:
        isClosing, HTMLNode.str, tok, StringParse.ifQuotesStripQuotes(String), TagNode.AttrRegEx.KEY_VALUE_REGEX
        Code:
        Exact Method Body:
         // All HTML element tags that start like: </DIV> with a front-slash.
         // They may not legally contain inner-tag attributes.
        
         if (this.isClosing) return null;    
        
         // All HTML element tags that contain only <TOK> (TOK <==> Tag-Name) in their 'str' field
         // Specifically: '<', TOKEN, '>',  (Where TOKEN is 'div', 'span', 'table', 'ul', etc...)
         // are TOO SHORT to have the attribute, so don't check... return null.
        
         if (this.str.length() < 
             (3 + this.tok.length() + (innerTagAttribute = innerTagAttribute.trim()).length()))
             return null;
        
         // Matches "Attribute / Inner-Tag Key-Value" Pairs.
         Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // This loop iterates the KEY_VALUE PAIRS THAT HAVE BEEN FOUND.
         /// NOTE: The REGEX Matches on Key-Value Pairs.
        
         while (m.find())
        
             // m.group(2) is the "KEY" of the Attribute KEY-VALUE Pair
             // m.group(3) is the "VALUE" of the Attribute.
             if (m.group(2).equalsIgnoreCase(innerTagAttribute))
                 return StringParse.ifQuotesStripQuotes(m.group(3));
        
         // This means the attribute name provided to parameter 'innerTagAttribute' was not found.
         return null;
        
      • AVOPT

        🡅  🡇    
        public java.lang.String AVOPT​(java.lang.String innerTagAttribute)
        OPT: Optimized

        This is an "optimized" version of method AV(String). This method does the exact same thing as AV(...), but leaves out parameter-checking and error-checking. This is used internally (repeatedly) by the NodeSearch Package Search Loops.
        Parameters:
        innerTagAttribute - This is the inner-tag / attribute name whose value is hereby being requested.
        Returns:
        String-value of this inner-tag / attribute.
        See Also:
        StringParse.ifQuotesStripQuotes(String), HTMLNode.str, TagNode.AttrRegEx.KEY_VALUE_REGEX
        Code:
        Exact Method Body:
         // COPIED DIRECTLY FROM class TagNode, leaves off initial tests.
        
         // Matches "Attribute / Inner-Tag Key-Value" Pairs.
         Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // This loop iterates the KEY_VALUE PAIRS THAT HAVE BEEN FOUND.
         /// NOTE: The REGEX Matches on Key-Value Pairs.
        
         while (m.find())
        
             // m.group(2) is the "KEY" of the Attribute KEY-VALUE Pair
             // m.group(3) is the "VALUE" of the Attribute.
        
             if (m.group(2).equalsIgnoreCase(innerTagAttribute))
                 return StringParse.ifQuotesStripQuotes(m.group(3));
        
         // This means the attribute name provided to parameter 'innerTagAttribute' was not found.
         return null;
        
      • setAV

        🡅  🡇    
        public TagNode setAV​(java.lang.String attribute,
                             java.lang.String value,
                             SD quote)
        This function will instantiate a new TagNode which contains this newly added attribute-value pair. It uses the constructor listed above, and furthermore does some error-handling checks. It will throw an exception if the inner-tag / value pairs do not pass inspection on quotes-error cases, or contain invalid characters.

        example
        Parameters:
        attribute - Any valid HTML attribute-name. This parameter may not be null, or a NullPointerException will throw.

        NOTE: If the attribute that is specified is already contained within this tag (where a CASE-INSENSITIVE comparison to the inner-tag's returned by allAV() gets a match), then the original attribute is simply over-written. A Duplicate HTML-Element attribute will not be added.
        value - Any valid attribute-value. This parameter may not be null, or a NullPointerException will throw.
        quote - This is either a single-quote, double-quote, or null.

        • When parameter 'quote' is SingleQuotes, a single-quote is prepended and appended to the beginning and ending (respectively) of the value parameter before inserting or replacing the inner-tag of this HTML (TagNode) Element.
        • When parameter 'quote' is Doubleuotes, a double-quote is added to the beginning and ending of the value-parameter before inserting (or re-inserting, if this attribute as already present).
        • When 'quote' is null, two are three alternative results, depending on the TagNode:

          1. If the TagNode already has an inner-tag name that is equal (CASE_INSENSITIVE) to the 'key' parameter, then the original quote found in 'this' Element is used.
          2. If a new attribute, not already found in 'this' TagNode is being inserted, and parameter 'quote' is null, then no quotes will be used at all - which is a scenario sometimes found in HTML documents. In this case, the key-value inner-tag will simply contain the String <HTML-ELEMENT ... key=value ...> without any quotes present, at all.
        Returns:
        An HTML TagNode instance with updated attribute information.

        IMPORTANT: Because TagNode's are immutable (since they are just wrapped Java-Strings, which are also immutable), it is important to remember that this method does not change the contents of a TagNode, but rather returns an entirely new TagNode instance as a result instead.
        Throws:
        InnerTagKeyException - This exception will throw if a non-standard String-value is passed to parameter String 'attribute'. HTML expects that an attribute-name conform to a set of rules in order to be processed by a browser.
        QuotesException - If there are "quotes within quotes" problems when invoking the TagNode constructor, this exception will throw. The problem occurs when one or more of the attribute key-value pairs have a quotation-choice such that the chosen quotation-mark is also found within the attribute-value.

        QuotesException will also throw in the case that an attribute key-value pair has elected to use the "No Quotes" option, but the attribute-value contains white-space.
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        HTMLTokException - If an invalid HTML 4 or 5 token is not present (CASE_INSENSITIVE).
        See Also:
        ClosingTagNodeException.check(TagNode), generateElementString(String, Properties, Iterable, SD, boolean), setAV(Properties, SD), tok, HTMLNode.str, isClosing
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         if (attribute == null) throw new NullPointerException(
             "You have passed 'null' to the 'attribute' (attribute-name) String-parameter, " +
             "but this is not allowed here."
         );
        
         if (value == null) throw new NullPointerException(
             "You have passed 'null' to the 'attribute' (attribute-value) String-parameter, " +
             "but this is not allowed here."
         );
        
         // Retrieve all "Key-Only" (Boolean) Attributes from 'this' (the original) TagNode
         // Use Java Streams to filter out any that match the newly-added attribute key-value pair.
         // SAVE: Save the updated / shortened list to a List<String>
        
         List<String> prunedOriginalKeyOnlyAttributes = allKeyOnlyAttributes(true)
             .filter((String originalKeyOnlyAttribute) -> 
                 ! originalKeyOnlyAttribute.equalsIgnoreCase(attribute))
             .collect(Collectors.toList());
        
         // Retrieve all Inner-Tag Key-Value Pairs.  Preserve the Case of the Attributes.  Preserve
         // the Quotation-Marks.
        
         Properties  p                       = allAV(true, true);
         String      originalValueWithQuotes = null;
         String      computedQuote           = null;
        
         // NOTE, there should only be ONE instance of an attribute in an HTML element, but
         // malformed HTML happens all the time, so to keep this method safe, it checks
         // (and removes) the entire attribute-list for matches - not just the first found instance.
        
         for (String key : p.stringPropertyNames())
        
             if (key.equalsIgnoreCase(attribute))
             {
                 Object temp = p.remove(key);
                 if (temp instanceof String) originalValueWithQuotes = (String) temp;
             }
        
         // If the user does not wish to "change" the original quote choice, then find out what
         // the original-quote choice was...
        
         if (
                 (quote == null) 
             &&  (originalValueWithQuotes != null)
             &&  (originalValueWithQuotes.length() >= 2)
         )
         {
             char s = originalValueWithQuotes.charAt(0);
             char e = originalValueWithQuotes.charAt(originalValueWithQuotes.length() - 1);
        
             if ((s == e) && (s == '\''))        computedQuote = "" + SD.SingleQuotes.quote;
        
             else if ((s == e) && (s == '"'))    computedQuote = "" + SD.DoubleQuotes.quote;
        
             else                                computedQuote = "";
         }
         else if (quote == null)                 computedQuote = "";
        
         else                                    computedQuote = "" + quote.quote;
        
         p.put(attribute, computedQuote + value + computedQuote);
        
         return new TagNode(
             generateElementString(
                 // Rather than using '.tok' here, preserve the case of the original HTML Element
                 this.str.substring(1, 1 + tok.length()), p,
                 prunedOriginalKeyOnlyAttributes, null /* SD */, this.str.endsWith("/>")
             ));
        
      • setAV

        🡅  🡇    
        public TagNode setAV​(java.util.Properties attributes,
                             SD defaultQuote)
        This allows for inserting or updating multiple TagNode inner-tag key-value pairs with a single method invocation.
        Parameters:
        attributes - These are the new attribute key-value pairs to be inserted.
        defaultQuote - This is the default quotation mark to use, if the 'attribute' themselves do not already have quotations.

        IMPORTANT: If this value is used, then none of the provided Property-values of the input java.lang.Properties instance should have quotes already. Each of these new-values will be wrapped in the quote that is provided as the value to this parameter.

        HOWEVER: If this parameter is passed a value of 'null', then no quotes will be added to the new keys - unless the attribute being inserted is replacing a previous attribute that was already present in the element. In this case, the original quotation shall be used. If this parameter receives 'null' and any of the new Properties were not already present in the original ('this') element, then no quotation marks will be used, which may throw a QuotesException if the attribute value contains any white-space.
        Returns:
        An HTML TagNode instance with updated TagNode information.

        IMPORTANT: Because TagNode's are immutable (since they are just wrapped-java-String's, which are also immutable), it is important to remember that this method does not change the contents of a TagNode, but rather returns an entirely new TagNode as a result instead.
        Throws:
        InnerTagKeyException - This exception will throw if any of the keys in the Properties parameter contain non-standard HTML Attribute / Inner-Tag key Strings. HTML expects that attribute-names conform to a set of rules in order to be processed properly by a browser. There is a 'check(...)' in class InnerTagKeyException which delineates the exact requirements made by HTML for attribute-names.
        QuotesException - if there are "quotes within quotes" problems, due to the values of the key-value pairs.
        HTMLTokException - if an invalid HTML 4 or 5 token is not present (CASE_INSENSITIVE)
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        See Also:
        ClosingTagNodeException.check(TagNode), setAV(String, String, SD), allKeyOnlyAttributes(boolean), tok, HTMLNode.str, isClosing
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         // Check that this attributes has elements.
         if (attributes.size() == 0) throw new IllegalArgumentException(
             "You have passed an empty java.util.Properties instance to the " +
             "setAV(Properties, SD) method"
         );
        
         // Retrieve all Inner-Tag Key-Value Pairs.
         //      Preserve: the Case of the Attributes.
         //      Preserve: the Quotation-Marks.
        
         Properties originalAttributes = allAV(true, true);
        
         // Retrieve all "Key-Only" (Boolean) attributes from the new / update attribute-list
         Set<String> newAttributeKeys = attributes.stringPropertyNames();
        
         // Retrieve all "Key-Only" (Boolean) Attributes from 'this' (the original) TagNode
         // Use Java Streams to filter out all the ones that need to be clobbered by-virtue-of
         // the fact that they are present in the new / parameter-updated attribute key-value list.
         // SAVE: Save the updated / shortened list to a List<String>
        
         List<String> prunedOriginalKeyOnlyAttributes = allKeyOnlyAttributes(true)
             .filter((String originalKeyOnlyAttribute) ->
             {
                 // Returns false when the original key-only attribute matches one of the
                 // new attributes being inserted.  Notice that a case-insensitive comparison
                 // must be performed - to preserve case.
        
                 for (String newKey : newAttributeKeys) 
                     if (newKey.equalsIgnoreCase(originalKeyOnlyAttribute)) 
                         return false;
        
                 return true;
             })
             .collect(Collectors.toList());
        
         // NOTE: There is no need to check the validity of the new attributes.  The TagNode
         //       constructor that is invoked on the last line of this method will do a 
         //       validity-check on the attribute key-names provided to the 'attributes' 
         //       java.util.Properties instance passed to to this method.
        
         for (String newKey : newAttributeKeys)
         {
             String      originalValueWithQuotes = null;
             String      computedQuote           = null;
        
             // NOTE, there should only be ONE instance of an attribute in an HTML element, but
             // malformed HTML happens all the time, so to keep this method safe, it checks (and
             // removes) the entire attribute-list for matches - not just the first found instance.
        
             for (String originalKey : originalAttributes.stringPropertyNames())
        
                 if (originalKey.equalsIgnoreCase(newKey))
                 {
                     // Remove the original key-value inner-tag pair.
                     Object temp = originalAttributes.remove(originalKey);
                     if (temp instanceof String) originalValueWithQuotes = (String) temp;
                 }
        
             // If the user does not wish to "change" the original quote choice, then find out what
             // the original-quote choice was...
        
             if (
                     (defaultQuote == null) 
                 &&  (originalValueWithQuotes != null)
                 &&  (originalValueWithQuotes.length() >= 2)
             )
             {
                 char s = originalValueWithQuotes.charAt(0);
                 char e = originalValueWithQuotes.charAt(originalValueWithQuotes.length() - 1);
        
                 if ((s == e) && (s == '\''))        computedQuote = "" + SD.SingleQuotes.quote;
        
                 else if ((s == e) && (s == '"'))    computedQuote = "" + SD.DoubleQuotes.quote;
        
                 else                                computedQuote = "";
             }
        
             else if (defaultQuote == null)          computedQuote = "";
        
             else                                    computedQuote = "" + defaultQuote.quote;
        
             // Insert the newly, updated key-value inner-tag pair.  This 'Properties' will be
             // used to construct a new TagNode.
        
             originalAttributes.put(newKey, computedQuote + attributes.get(newKey) + computedQuote);
         }
        
         return new TagNode(
             generateElementString(
                 // Rather than using '.tok' here, preserve the case of the original HTML Element
                 this.str.substring(1, 1 + tok.length()),
                 originalAttributes, prunedOriginalKeyOnlyAttributes, null /* SD */,
                 this.str.endsWith("/>")
             ));
        
      • appendToAV

        🡅  🡇    
        public TagNode appendToAV​(java.lang.String attribute,
                                  java.lang.String appendStr,
                                  boolean startOrEnd,
                                  SD quote)
        This will append a substring to the attribute value of an HTML TagNode. This method can be very useful, for instance when dealing with CSS tags that are inserted inside the HTML node itself. For example, in order to add a 'color: red; background: white;' portion to the CSS 'style' tag of an HTML <TABLE STYLE="..."> element, without clobbering the style-information that is already inside the element, using this method will achieve that.
        Parameters:
        attribute - The name of the attribute to which the value must be appended. This parameter may not be null, or a NullPointerException will throw.
        appendStr - The String to be appended to the attribute-value.
        startOrEnd - If this parameter is TRUE then the append-String will be inserted at the beginning (before) whatever the current attribute- value is. If this parameter is FALSE then the append-String will be inserted at the end (after) the current attribute-value String.

        NOTE: If tag element currently does not posses this attribute, then the attribute/value pair will be created and inserted with its value set to the value of 'appendStr'.
        quote - This is the quote that will be used when defining the attribute's key-value pair inside the tag element. The programmer is expected to decide between using: SingleQuotes, DoubleQuotes or none / no-quotes (by passing null, with caveats). If null is passed to this parameter, then the complete list of rules that are applied, as explained in detail in method setAV.

        Please review the complete delineation of the rules for how quotation marks are added and used in an HTML Tag Attribute, vis-a-vis the 'quote' parameter.

        It is important to note that "appending" a String to an attribute's value will often (but not always) mean that the new attribute-value will have a space character. If this parameter were passed null, and if the original tag had a value, but did not use any quotes, then the attribute's ultimate inclusion into the tag would generate invalid HTML, and the invocation of setAV(String, String, SD) would throw a QuotesException.
        Returns:
        Since all instances of TagNode are immutable, this method will not actually alter the TagNode element, but rather create a new object reference that contains the updated attribute.
        Throws:
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        QuotesException - The rules for quotation usage apply here too, and see that explanation for how how this exception could be thrown.
        See Also:
        AV(String), setAV(String, String, SD), ClosingTagNodeException.check(TagNode)
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         if (attribute == null) throw new NullPointerException(
             "You have passed 'null' to the 'attribute' (attribute-name) String-parameter, " +
             "but this is not allowed here."
         );
        
         if (appendStr == null) throw new NullPointerException(
             "You have passed 'null' to the 'appendStr' (attribute-value-append-string) " +
             "String-parameter, but this is not allowed here."
         );
        
         String curVal = AV(attribute);
        
         if (curVal == null) curVal = "";
        
         // This decides whether to insert the "appendStr" before the current value-string,
         // or afterwards.  This is based on the passed boolean-parameter 'startOrEnd'
        
         curVal = startOrEnd ? (appendStr + curVal) : (curVal + appendStr);
        
         // Reuse the 'setAV(String, String, SD)' method already defined in this class.
         return setAV(attribute, curVal, quote);
        
      • removeAttributes

        🡅  🡇    
        public TagNode removeAttributes​(java.lang.String... attributes)
        This will remove all inner-tag's whose names match (using CASE-INSENSITIVE comparisons) the specified attribute-names in this input parameter list 'attributes'.

        NOTE: This will remove all inner-tags that match the listed attributes provided. This means removing BOTH boolean 'key-only' attributes, AND any key-value inner-tags that have names which match the requested remove-list of names.
        Parameters:
        attributes - This is a list of attribute-names (inner-tags) to be removed from 'this' instance of TagNode. Each String in this var-args String... parameter will have String.toLowerCase() invoked before performing these attribute-name comparisons.

        NOTE: If 'this' instance of TagNode node does not contain any of these attributes, then nothing shall happen; however, a new TagNode instance shall still be constructed and returned.
        Returns:
        An HTML TagNode instance with updated TagNode information.

        IMPORTANT: Because TagNode's are immutable (since they are just wrapped-java-String's, which are also immutable), it is important to remember that this method does not change the contents of a TagNode, but rather returns an entirely new TagNode as a result instead.
        Throws:
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        See Also:
        ClosingTagNodeException.check(TagNode), tok, isClosing, HTMLNode.str, TagNode(String), generateElementString(String, Properties, Iterable, SD, boolean)
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         // Retrieve all Inner-Tag Key-Value Pairs.  Preserve the Case of the Attributes.  Preserve
         // the Quotation-Marks.
        
         Properties originalAttributes = allAV(true, true);
        
         // Remove any attributes from the "Attributes Key-Value Properties Instance" which MATCH
         // the attribute names that have been EXPLICITLY REQUESTED FOR REMOVAL
        
         for (String key : originalAttributes.stringPropertyNames())
             for (String attribute : attributes)
                 if (key.equalsIgnoreCase(attribute))
                     originalAttributes.remove(key);
        
         // Retrieve all "Boolean Attributes" (key-no-value).  Preserve the Case of these Attributes
         // Retain only the attributes in the 'filteredKeyOnlyAttributes' String-Array which have
         // PASSED THE FILTER OPERATION.  The filter operation only returns TRUE if the 
         // requested-attribute-list does not contain a copy of the Key-Only-Attribute
         //
         // NOTE: 'true' is passed as input to the 'allKeyOnlyAttributes(boolean)' method to request
         //       that CASE be PRESERVED.
        
         Iterable<String> prunedKeyOnlyAttributes = allKeyOnlyAttributes(true)
        
             .filter((String attribute) ->
             {
                 // Returns false when the original key-only attribute matches one of the attributes
                 // that was requested to to be removed.  Notice that a case-insensitive comparison 
                 // must be performed.
        
                 for (String removeAttributes : attributes)
                     if (removeAttributes.equalsIgnoreCase(attribute))
                         return false;
        
                 return true;
             })
             .collect(Collectors.toList());
        
         return new TagNode(
             generateElementString(
                 // Rather than using '.tok' here, preserve the case of the original HTML Element
                 this.str.substring(1, 1 + tok.length()),
                 originalAttributes, prunedKeyOnlyAttributes, /* SD */ null, 
                 this.str.endsWith("/>")
             ));
        
      • removeAllAV

        🡅  🡇    
        public TagNode removeAllAV()
        TagNode's are immutable. And because of this, calling removeAllAV() is actually the same as retrieving the standard, zero-attribute, pre-instantiated instance of an HTML Element. Pre-instantiated factory-instances of class TagNode for every HTML-Element are stored by class HTMLTags inside a Hashtable. They can be retrieved in multiple ways, two of which are found in methods in this class.

        Point of Interest: Calling these three different methods will all return identical Object references:

        • TagNode v1 = myTagNode.removeAllAV();
        • TagNode v2 = TagNode.getInstance(myTagToken, openOrClosed);
        • TagNode v3 = HTMLTag.hasTag(myTagToken, openOrClosed);
        • assert((v1 == v2) && (v2 == v3));


        example
        Returns:
        An HTML TagNode instance with all inner attributes removed.

        NOTE: If this tag contains an "ending forward slash" that ending slash will not be included in the output TagNode.

        IMPORTANT: Because TagNode's are immutable (since they are just wrapped-java-String's, which are also immutable), it is important to remember that this method does not change the contents of a TagNode, but rather returns an entirely new TagNode as a result instead.
        Throws:
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        See Also:
        ClosingTagNodeException.check(TagNode), getInstance(String, TC), HTMLNode.str, tok, TC.OpeningTags
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         // NOTE: We *CANNOT* use the 'tok' field to instantiate the TagNode here, because the 'tok'
         // String-field is *ALWAYS* guaranteed to be in a lower-case format.  The 'str'
         // String-field, however uses the original case that was found on the HTML Document by the
         // parser (or in the Constructor-Parameters that were passed to construct 'this' instance
         // of TagNode.
        
         return getInstance(this.str.substring(1, 1 + tok.length()), TC.OpeningTags);
        
      • allAV

        🡅  🡇    
        public java.util.Properties allAV()
        Convenience Method
        Invokes: allAV(boolean, boolean)
        Attribute-names will be in lower-case.
        Code:
        Exact Method Body:
         return allAV(false, false);
        
      • allAV

        🡅  🡇    
        public java.util.Properties allAV​(boolean keepQuotes,
                                          boolean preserveKeysCase)
        This will copy every attribute key-value pair inside 'this' HTML TagNode element into a java.util.Properties Hash-Table.

        RETURN-VALUE NOTE: This method shall not return any "Key-Only Attributes" (a.k.a. "Boolean Attributes"). The most commonly used "Boolean Attribute" example is the 'HIDDEN' key-word that is used to prevent the browser from displaying an HTML Element. Inner-tags that represent attribute key-value pairs are the only attributes that may be included in the returned 'Properties' instance.

        example
        Parameters:
        keepQuotes - If this parameter is passed TRUE, then any surrounding quotation marks will be included for each the values of each attribute key-value pair.
        preserveKeysCase - If this parameter is passed TRUE, then the method String.toLowerCase() will not be invoked on any of the keys (attribute-names) of each inner-tag key-value pair.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        Returns:
        This returns a list of each and every attribute-name - and the associate value of the attribute - found in 'this' TagNode. An instance of class java.util.Properties is used to store the attribute key-value pairs.

        NOTE: This method will NOT return any boolean, key-only attributes present in 'this' TagNode.

        ALSO: This method shall not return 'null'. If there do not exist any Attribute-Value Pairs, or if 'this' node is a closing-element, then an empty 'Properties' instance shall be returned.
        See Also:
        StringParse.ifQuotesStripQuotes(String), TagNode.AttrRegEx.KEY_VALUE_REGEX, tok, HTMLNode.str
        Code:
        Exact Method Body:
         Properties ret = new Properties();
        
         // NOTE:    OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str' field is
         //          is only longer than the token, itself, by 3 or less characters cannot have
         //          attributes.
         // CHARS:   '<', TOKEN, SPACE, '>'
         // RET:     In that case, just return an empty 'Properties' instance.
        
         if (isClosing || (str.length() <= (tok.length() + 3))) return ret;
        
         // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs.
         // m.group(1): UN-USED!  (Includes Key, Equals-Sign, and Value).  Not w/leading white-space
         // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign)
         // m.group(3): returns the 'value' portion of the key-value pair, after an '='
        
         Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // MORE-CODE, but MORE-EFFICIENT (slightly)
        
         if      (keepQuotes     && preserveKeysCase)
             while (m.find()) ret.put(m.group(2), m.group(3));
        
         else if (!keepQuotes    && preserveKeysCase)
             while (m.find()) ret.put(m.group(2), StringParse.ifQuotesStripQuotes(m.group(3)));
        
         else if (keepQuotes     && !preserveKeysCase)
             while (m.find()) ret.put(m.group(2).toLowerCase(), m.group(3));
        
         else if (!keepQuotes    && !preserveKeysCase)
             while (m.find()) 
                 ret.put(m.group(2).toLowerCase(), StringParse.ifQuotesStripQuotes(m.group(3)));
        
         return ret;
        
      • allAN

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> allAN()
        Convenience Method
        Invokes: allAN(boolean, boolean)
        Attribute-names will be in lower-case
        Code:
        Exact Method Body:
         return allAN(false, false);
        
      • allAN

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> allAN​
                    (boolean preserveKeysCase,
                     boolean includeKeyOnlyAttributes)
        
        This method will only return a list of attribute-names. The attribute-values shall NOT be included in the result. The String's returned can have their "case-preserved" by passing TRUE to the input boolean parameter 'preserveCase'.
        Parameters:
        preserveKeysCase - If this is parameter receives TRUE then the case of the attribute-names shall be preserved.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        includeKeyOnlyAttributes - When this parameter receives TRUE, then any "Boolean Attributes" or "Key-Only, No-Value-Assignment" Inner-Tags will ALSO be included in the Stream<String> returned by this method.
        Returns:
        an instance of Stream<String> containing all attribute-names identified in 'this' instance of TagNode. A java.util.stream.Stream is used because it's contents can easily be converted to just about any data-type.

        Conversion-Target Stream-Method Invocation
        String[] Stream.toArray(String[]::new);
        List<String> Stream.collect(Collectors.toList());
        Vector<String> Stream.collect(Collectors.toCollection(Vector::new));
        TreeSet<String> Stream.collect(Collectors.toCollection(TreeSet::new));
        Iterator<String> Stream.iterator();

        NOTE: This method shall never return 'null' - even if there are no attribute key-value pairs contained by 'this' TagNode. If there are strictly zero attributes, an empty Stream shall be returned, instead.
        See Also:
        allKeyOnlyAttributes(boolean), allAN()
        Code:
        Exact Method Body:
         // If there is NO ROOM in the "str" field for attributes, then there is now way attributes
         // could exist in this element.  Return "empty" immediately.
         // 
         // NOTE:    OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str' field
         //          is only longer than the token, itself, by 3 or less characters cannot have
         //          attributes.
         //
         // CHARS:   '<', TOKEN, SPACE, '>'
         // RET:     In that case, just return an empty Stream.
        
         if (isClosing || (str.length() <= (tok.length() + 3))) return Stream.empty();
        
         // Use Java Streams.  A String-Stream is easily converted to just about any data-type
         Stream.Builder<String> b = Stream.builder();
        
         // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs.
         // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign)
        
         Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // Retrieve all of the keys of the attribute key-value pairs.
         while (m.find()) b.add(m.group(2));
        
         // This Stream contains only keys that were once key-value pairs, if there are "key-only" 
         // attributes, they have not been added yet.
        
         Stream<String> ret = b.build();
        
         // Convert these to lower-case, (if requested)
         if (! preserveKeysCase) ret = ret.map((String attribute) -> attribute.toLowerCase());
        
         // Now, add in all the "Key-Only" attributes (if there are any).  Note, "preserve-case"
         // and "to lower case" are handled, already, in method "allKeyOnlyAttributes(boolean)"
        
         if (includeKeyOnlyAttributes)
             return Stream.concat(ret, allKeyOnlyAttributes(preserveKeysCase));
        
         return ret;
        
      • allKeyOnlyAttributes

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> allKeyOnlyAttributes​
                    (boolean preserveKeysCase)
        
        This method returns a Stream<String> of all token-String's that are found between attribute key-value pairs in 'this' TagNode instance. The attribute-names which are returned will be the ones to which no values have been assigned.

        Inner-Tags with value-assignments are inelligible for being included in the returned result-set.

        example
        Parameters:
        preserveKeysCase - If this parameter is passed TRUE, then the method String.toLowerCase() will not be invoked on any of the key's (attribute-names) returned by this method.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        Returns:
        a java Stream that contains any and all character text that resides in-between attribute key-value pairs that have matched. Generally, in well formed HTML, this should correspond directly to what are normally called "Boolean Attributes." Boolean Attributes are just words inside of an HTML Element that describe the elements contents. The primary issue regarding these types of inner-tags is that they do not have any values - they are strictly a key, alone.
        NOTE: This method shall never return 'null' - even if there are no attribute key-value pairs contained by the TagNode. If there are strictly zero attributes, Stream.empty() shall be returned, instead.

        Conversion-Target Stream-Method Invocation
        String[] Stream.toArray(String[]::new);
        List<String> Stream.collect(Collectors.toList());
        Vector<String> Stream.collect(Collectors.toCollection(Vector::new));
        TreeSet<String> Stream.collect(Collectors.toCollection(TreeSet::new));
        Iterator<String> Stream.iterator();
        See Also:
        tok, HTMLNode.str
        Code:
        Exact Method Body:
         // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
         //       field is only longer than the token, itself, by 3 or less characters cannot have
         //       attributes.  In that case, just return an empty 'Stream' instance.
        
         int len = str.length();
         if (isClosing || (len <= (tok.length() + 3))) return Stream.empty();
        
         // Leaves off the opening 'token' and less-than '<' symbol  (leaves off "<DIV " for example)
         // Also leave off the "ending-forward-slash" (if there is one) and ending '>'
        
         String  s = str.substring(tok.length() + 2, len - ((str.charAt(len - 2) == '/') ? 2 : 1));
        
         // if all lower-case is requested, do that here.
         if (! preserveKeysCase) s = s.toLowerCase();
        
         // java.util.regex.Pattern.split(CharSequence) is sort of an "inverse reg-ex" in that it 
         // returns all of the text that was present BETWEEN the matches 
         // NOTE: This is the "opposite of the matches, themselves)" - a.k.a. all the stuff that was
         //       left-out.
        
         Stream.Builder<String> b = Stream.builder();
        
         // 'split' => inverse-matches  (text between KEY-VALUE pairs)
         for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(s))
        
             // Of that stuff, now do a white-space split for connected characters
             for (String keyWord : unMatchedStr.split("\\s+"))
        
                 // Call String.trim() and String.length()
                 if ((keyWord = keyWord.trim()).length() > 0)
        
                     // Check for valid Attribute-Name's only
                     if (AttrRegEx.ATTRIBUTE_KEY_REGEX_PRED.test(keyWord))
        
                         // ... put it in the return stream.
                         // NOTE: This has the potential to slightly change the original HTML
                         //       It will "leave out any guck" that was in the Element
                         b.add(keyWord);
        
         // Build the Stream<String>, and return;
         return b.build();
        
      • hasKeyOnlyAttribute

        🡅  🡇    
        public boolean hasKeyOnlyAttribute​(java.lang.String keyOnlyAttribute)
        Will identify if a Boolean-Attribute - a.k.a. a token-name that exists BETWEEN inner-tag key-value pairs - is present in the TagNode. One of the most common of these Boolean-Attributes ("Key-Word-Only Attributes") is the inner-tag HIDDEN. Hidden HTML Elements have their CSS feature style.display set to none.
        Parameters:
        keyOnlyAttribute - This may be the name of any inner-tag.

        NOTE: This parameter, is not checked for validity against the attribute-name regular-expression.
        Returns:
        Will return TRUE if the named keyOnlyAttribute is present in the HTML Element as a stand-alone inner-tag - i.e., it is lacking any value assignments The comparison performed is case-insensitive, since HTML Attribute-names are considered fundamentally case-insensitive.

        If 'this' instance of TagNode is a closing-version of the element, this method shall return FALSE immediately, and exit.
        Throws:
        java.lang.IllegalArgumentException - If the input-parameter receives a String that contains any white-space itself, an exception will throw. The search-logic splits the String's based on white-space, so if a user passes a white-space containing String, a match would simply never occur.

        IMPORTANT: This method does not check the validity of the 'keyOnlyAttribute' parameter against the Attribute-name regular-expression, because this method uses the 'split(String)' method of the Regular-Expression Matcher. All this means, is that this method may actually be used to check for any-text inside of an HTML Element - so long as that text does not contain white-space. This is not an encouraged use of this method, but it will work.
        See Also:
        TagNode.AttrRegEx.KEY_VALUE_REGEX
        Code:
        Exact Method Body:
         // Closing TagNode's do not have attributes, return false immediately.
         if (this.isClosing) return false;
        
         // ONLY CHECKS FOR WHITE-SPACE, *NOT* VALIDITY...
         if (StringParse.hasWhiteSpace(keyOnlyAttribute)) throw new IllegalArgumentException(
             "The attribute you have passed [" + keyOnlyAttribute + "] has white-space, " +
             "This is not allowed here, because the search routine splits on whitespace, and " +
             "therefore a match would never be found."
         );
        
         // NOTE: TagNode's whose 'str' field is only longer than the token, itself, by 3 or less
         //       characters cannot have attributes.  In that case, just return false.
        
         int len = str.length();
         if (len <= (tok.length() + 3)) return false;
        
         // Leaves off the opening 'token' and less-than '<' symbol  (leaves off "<DIV " for example)
         // Also leave off the "ending-forward-slash" (if there is one), and edning '>'
        
         String s = str.substring(tok.length() + 2, len - ((str.charAt(len - 2) == '/') ? 2 : 1));
        
         // java.util.regex.Pattern.split(CharSequence) is sort of an "inverse reg-ex" in that it 
         // returns all of the text that was present BETWEEN the matches 
        
         // 'split' => inverse-matches (text between KEY-VALUE pairs)
         for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(s))
        
             // Of that stuff, now do a white-space split for connected characters
             for (String keyWord : unMatchedStr.split("\\s+"))
        
                 // trim, check-length...
                 if ((keyWord = keyWord.trim()).length() > 0)
        
                     if (keyOnlyAttribute.equalsIgnoreCase(keyWord)) return true;
        
         // Was not found, return false;
         return false;
        
      • testAV

        🡅  🡇    
        public boolean testAV​(java.lang.String attributeName,
                              java.lang.String attributeValue)
        Convenience Method
        Invokes: testAV(String, Predicate)
        Passes: String.equals(attributeValue) as the test-Predicate
        Code:
        Exact Method Body:
         return testAV(attributeName, (String s) -> s.equals(attributeValue));
        
      • testAV

        🡅  🡇    
        public boolean testAV​(java.lang.String attributeName,
                              java.util.regex.Pattern attributeValueTest)
        Convenience Method
        Invokes: testAV(String, Predicate)
        Passes: attributeValueTest.asPredicate()
        Code:
        Exact Method Body:
         return testAV(attributeName, attributeValueTest.asPredicate());
        
      • testAV

        🡅  🡇    
        public boolean testAV​
                    (java.lang.String attributeName,
                     java.util.function.Predicate<java.lang.String> attributeValueTest)
        
        Test the value of the inner-tag named 'attributeName' (if that attribute exists, and has a non-empty value) using a provided Predicate<String>.

        example
        Parameters:
        attributeName - Any String will suffice - but only valid attribute names will match the internal regular-expression.

        NOTE: The validity of this parameter is not checked with the HTML attribute-name Regular-Expression exception checker.
        attributeValueTest - Any java.util.function.Predicate<String>
        Returns:
        Method will return TRUE if and only if:

        • 'this' instance of TagNode has an inner-tag named 'attributeName'.

        • The results of the provided String-Predicate, when applied against the value of the requested attribute, returns TRUE.
        See Also:
        TagNode.AttrRegEx.KEY_VALUE_REGEX, HTMLNode.str, isClosing, StringParse.ifQuotesStripQuotes(String)
        Code:
        Exact Method Body:
         // Closing TagNode's (</DIV>, </A>) cannot attributes, or attribute-values
         if (isClosing) return false;
        
         // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
         // are simply too short to have the attribute named by the input parameter
        
         if (this.str.length() < (this.tok.length() + attributeName.length() + 4)) return false;
        
         // This Reg-Ex will allow us to iterate through each attribute key-value pair
         // contained / 'inside' this instance of TagNode.
        
         Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // Test each attribute key-value pair, and return the test results if an attribute
         // whose name matches 'attributeName' is found.
        
         while (m.find())
             if (m.group(2).equalsIgnoreCase(attributeName))
                 return attributeValueTest.test
                     (StringParse.ifQuotesStripQuotes(m.group(3)));
        
         // No attribute key-value pair was found whose 'key' matched input-parameter
         // 'attributeName'
        
         return false;
        
      • hasAND

        🡅  🡇    
        public boolean hasAND​(boolean checkAttributeStringsForErrors,
                              java.lang.String... attributes)
        Convenience Method
        Invokes: hasLogicOp(boolean, IntFunction, IntPredicate, String[])
        Passes: AND Boolean Logic
        Code:
        Exact Method Body:
         // First-Function:  Tells the logic to *IGNORE* intermediate matches (returns NULL)
         //                  (This is *AND*, so wait until all attributes have been found, or at
         //                  the very least all tags in the element tested, and failed.
         //
         // Second-Function: At the End of the Loops, all Attributes have either been found, or
         //                  at least all attributes in 'this' tag have been tested.  Note that the
         //                  first-function is only called on a MATCH, and tht 'AND' requires to
         //                  defer a response until all attributes have been tested..  Here, simply
         //                  RETURN WHETHER OR NOT the MATCH-COUNT equals the number of matches in
         //                  the user-provided String-array.
        
         return hasLogicOp(
             checkAttributeStringsForErrors,
             (int matchCount) -> null,
             (int matchCount) -> (matchCount == attributes.length),
             attributes
         );
        
      • hasOR

        🡅  🡇    
        public boolean hasOR​(boolean checkAttributeStringsForErrors,
                             java.lang.String... attributes)
        Convenience Method
        Invokes: hasLogicOp(boolean, IntFunction, IntPredicate, String[])
        Passes: OR Boolean Logic
        Code:
        Exact Method Body:
         // First-Function:  Tells the logic to return TRUE on any match IMMEDIATELY
         //
         // Second-Function: At the End of the Loops, all Attributes have been tested.  SINCE the
         //                  previous function returns on match immediately, AND SINCE this is an
         //                  OR, therefore FALSE must be returned (since there were no matches!)
        
         return hasLogicOp(
             checkAttributeStringsForErrors,
             (int matchCount) -> true,
             (int matchCount) -> false,
             attributes
         );
        
      • hasNAND

        🡅  🡇    
        public boolean hasNAND​(boolean checkAttributeStringsForErrors,
                               java.lang.String... attributes)
        Convenience Method
        Invokes: hasLogicOp(boolean, IntFunction, IntPredicate, String[])
        Passes: NAND Boolean Logic
        Code:
        Exact Method Body:
         // First-Function: Tells the logic to return FALSE on any match IMMEDIATELY
         //
         // Second-Function: At the End of the Loops, all Attributes have been tested.  SINCE
         //                  the previous function returns on match immediately, AND SINCE this is
         //                  a NAND, therefore TRUE must be returned (since there were no matches!)
        
         return hasLogicOp(
             checkAttributeStringsForErrors,
             (int matchCount) -> false,
             (int matchCount) -> true,
             attributes
         );
        
      • hasXOR

        🡅  🡇    
        public boolean hasXOR​(boolean checkAttributeStringsForErrors,
                              java.lang.String... attributes)
        Convenience Method
        Invokes: hasLogicOp(boolean, IntFunction, IntPredicate, String[])
        Passes: XOR Boolean Logic
        Code:
        Exact Method Body:
         // First-Function: Tells the logic to IGNORE the FIRST MATCH, and any matches afterwards
         //                 should produce a FALSE result immediately
         //                 (XOR means ==> one-and-only-one)
         //
         // Second-Function: At the End of the Loops, all Attributes have been tested.  Just
         //                  return whether or not the match-count is PRECISELY ONE.
        
         return hasLogicOp(
             checkAttributeStringsForErrors,
             (int matchCount) -> (matchCount == 1) ? null : false,
             (int matchCount) -> (matchCount == 1),
             attributes
         );
        
      • hasLogicOp

        🡅  🡇    
        protected boolean hasLogicOp​
                    (boolean checkAttributeStringsForErrors,
                     java.util.function.IntFunction<java.lang.Boolean> onMatchFunction,
                     java.util.function.IntPredicate reachedEndFunction,
                     java.lang.String... attributes)
        
        Provides the Logic for methods:
        Boolean EvaluationMethod
        Checks that all Attributes are found hasAND(boolean, String[])
        Checks that at least one Attribute matches hasOR(boolean, String[])
        Checks that precisely-one Attribute is found hasXOR(boolean, String[])
        Checks that none of the Attributes match hasNAND(boolean, String[])

        Example
        Parameters:
        attributes - This is a list of HTML Element Attribute-Names or "Inner Tags" as they are called in this Search and Scrape Package.
        checkAttributeStringsForErrors - Some may argue this is confusing. The purpose of this boolean is actually somewhat important. People who have written software libraries start to see that "Error Checking" (Proper Exceptions with consistent error messages) is one of the more beneficial features of the library. The catch is that it can add a layer of redundancy and inefficiency - where the same set of data are tested and retested (inside a loop, for instance) over and over.

        If the programmer has already checked the attributes / inner-tags for validity, and intends to use this boolean-method inside of a loop-construct, it should be obvious that FALSE needs to be passed to this parameter. If this test is not within a loop, it is much less of an issue.

        NOTE: When this variable is *FALSE*, the passed attribute parameters will not be checked for validity. When *TRUE* the attributes Strings will be tested on each method invocation. This is included to encourage programmers to check their HTML and avoid invalid attribute strings (has spaces, are null, etc.) earlier.
        Returns:
        TRUE if at least one of these attribute-names are present in 'this' instance, and FALSE otherwise.

        NOTE: If this method is passed a zero-length String-array to the 'attributes' parameter, this method shall exit immediately and return FALSE.
        Throws:
        InnerTagKeyException - If any of the 'attributes' are not valid HTML attributes, and the user has passed TRUE to parameter checkAttributeStringsForErrors.
        java.lang.NullPointerException - If any of the 'attributes' are null.
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        java.lang.IllegalArgumentException - If the 'attributes' parameter has length zero.
        See Also:
        InnerTagKeyException.check(String[]), AV(String), TagNode.AttrRegEx.KEY_VALUE_REGEX
        Code:
        Exact Method Body:
         ClosingTagNodeException.check(this);
        
         // Keep a tally of how many of the input attributes are found
         int matchCount = 0;
        
         // Don't clobber the user's input
         attributes = attributes.clone();
        
         // If no attributes are passed to 'attributes' parameter, throw exception.
         if (attributes.length == 0) throw new IllegalArgumentException
             ("Input variable-length String[] array parameter, 'attributes', has length zero.");
        
         // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
         // are simply too short to have any attribute-value pairs.
         // 4 (characters) are: '<', '>', ' ' and 'X'
         // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
         //
         // This TagNode doesn't have any attributes in it.
         // There is no need to check anything, so return FALSE immediately ("OR" fails)
        
         if (this.str.length() < (this.tok.length() + 4)) return false;
        
         if (checkAttributeStringsForErrors) InnerTagKeyException.check(attributes);
        
        
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
         // Check the main key=value attributes
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        
         // Get all inner-tag key-value pairs.  If even one of these is inside the 'attributes'
         // input-parameter string-array,  Then we must return true, since this is OR
        
         Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         while (keyValueInnerTags.find())
         {
             // Retrieve the key of the key-value pair
             String innerTagKey = keyValueInnerTags.group(2);                   
        
             // Iterate every element of the String[] 'attributes' parameter
             SECOND_FROM_TOP:
             for (int i=0; i < attributes.length; i++)
        
                 // No need to check attributes that have already been matched.
                 // When an attribute matches, it's place in the array is set to null
                 if (attributes[i] != null)
        
                     // Does the latest keyOnlyInnerTag match one of the user-requested
                     // attribute names?
                     if (innerTagKey.equalsIgnoreCase(attributes[i]))
                     {
                         // NAND & OR will exit immediately on a match.  XOR and AND
                         // will return 'null' meaning they are not sure yet.
        
                         Boolean whatToDo = onMatchFunction.apply(++matchCount);
        
                         if (whatToDo != null) return whatToDo;
        
                         // Increment the matchCounter, if this ever reaches the length
                         // of the input array, there is no need to continue with the loop
        
                         if (matchCount == attributes.length)
                             return reachedEndFunction.test(matchCount); 
        
                         // There are still more matches to be found (not every element in
                         // this array is null yet)... Keep Searching, but eliminated the
                         // recently identified attribute from the list, because it has
                         // already been found.
        
                         attributes[i] = null;
                         continue SECOND_FROM_TOP;
                     }
         }
        
        
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
         // Check the main key-only (Boolean) Attributes
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
         //
         // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
         // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
         // real matches)
        
         // substring eliminates the leading "<TOK ..." and the trailing '>' character
         // 2: '<' character *PLUS* the space (' ') character
         String strToSplit = this.str.substring(
             2 + tok.length(),
             this.str.length() - ((str.charAt(this.str.length() - 2) == '/') ? 2 : 1)
         ).trim();
        
         // 'split' => inverse-matches  (text between KEY-VALUE pairs)
         for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(strToSplit))  
        
             // Of that stuff, now do a white-space split for connected characters
             SECOND_FROM_TOP:
             for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))          
        
                 // Just-In-Case, usually not necessary
                 if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)
        
                     // Iterate all the input-parameter String-array attributes
                     for (int i=0; i < attributes.length; i++)
        
                         // No need to check attributes that have already been matched.
                         // When an attribute matches, it's place in the array is set to null
                         if (attributes[i] != null)
        
                             // Does the latest keyOnlyInnerTag match one of the user-requested
                             // attribute names?
                             if (keyOnlyInnerTag.equalsIgnoreCase(attributes[i]))
                             {
                                 // NAND & OR will exit immediately on a match.  XOR and AND
                                 // will return 'null' meaning they are not sure yet.
        
                                 Boolean whatToDo = onMatchFunction.apply(++matchCount);
        
                                 if (whatToDo != null) return whatToDo;
                
                                 // Increment the matchCounter, if this ever reaches the length
                                 // of the input array, there is no need to continue with the loop
                
                                 if (matchCount == attributes.length)
                                     return reachedEndFunction.test(matchCount); 
        
                                 // There are still more matches to be found (not every element in
                                 // this array is null yet)... Keep Searching, but eliminated the
                                 // recently identified attribute from the list, because it has
                                 // already been found.
        
                                 attributes[i] = null;
                                 continue SECOND_FROM_TOP;
                             }
        
         // Let them know how many matches there were
         return reachedEndFunction.test(matchCount);
        
      • has

        🡅  🡇    
        public boolean has​(java.lang.String attributeName)
        Convenience Method
        Invokes: has(Predicate)
        Passes: String.equalsIgnoreCase(attributeName) as the test-Predicate
        Code:
        Exact Method Body:
         return has((String s) -> s.equalsIgnoreCase(attributeName));
        
      • has

        🡅  🡇    
        public boolean has​(java.util.regex.Pattern attributeNameRegExTest)
        Convenience Method
        Invokes: has(Predicate)
        Passes: Pattern.asPredicate()
        Code:
        Exact Method Body:
         return has(attributeNameRegExTest.asPredicate());
        
      • has

        🡅  🡇    
        public boolean has​
                    (java.util.function.Predicate<java.lang.String> attributeNameTest)
        
        Will search this TagNode to determine if any inner-tag key-value pairs have a key (attribute-name) that is accepted by the Predicate<String> parameter 'attributeNameTest'.

        This method will also check the boolean-attributes, which are attributes that include a key, but do not have a value assigned to them (such as the commonly used boolean-attribute 'hidden' - which indicates that a particular element should not be rendered-visible).

        NOTE: If 'this' instance of TagNode is a closing-version of the HTML Tag (SPECIFICALLY: this.isClosing == TRUE), then this method shall exit gracefully - and immediately - returning FALSE. The (sometimes) requisite exception for such cases - OpeningTagNodeExpectedException - shall NOT throw. Remember that HTML does not actually allow for attributes placed inside of closing HTML elements (</DIV>, </TABLE>, or </SPAN> for instance).
        Parameters:
        attributeNameTest - A String Predicate parameter that is used to accept or reject a match with the list of attribute keys (including the Boolean-Attribute keys) in 'this' HTML Tag.

        NOTE: The StrFilter in package Torello.Java is capable of generating a rather wide range of test-Predicate's using regular-expressions. This is one option to think about when using this method.
        Returns:
        Will return TRUE if there are any inner-tag's whose name is accepted by the input-parameter Predicate 'attributeNameTest' (and FALSE otherwise).
        See Also:
        TagNode.AttrRegEx.KEY_VALUE_REGEX, StrFilter
        Code:
        Exact Method Body:
         // Closing HTML Elements may not have attribute-names or values.
         // Exit gracefully, and immediately.
        
         if (this.isClosing) return false;
        
         // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
         // are simply too short to have such an attribute-value pair.
         // 4 (characters) are: '<', '>', ' ' and 'X'
         // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
        
         if (this.str.length() < (this.tok.length() + 4)) return false;
        
         // Get all inner-tag key-value pairs.  If any of them match with the 'attributeNameTest'
         // Predicate, return TRUE immediately.
         Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         // the matcher.group(2) has the key (not the value)
         while (keyValueInnerTags.find())
             if (attributeNameTest.test(keyValueInnerTags.group(2)))
                 return true;
        
         // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
         // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
         // real matches)
        
         // 'split' => inverse-matches  (text between KEY-VALUE pairs)
         for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))  
        
             // Of that stuff, now do a white-space split for connected characters
             for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))          
        
                 // Just-In-Case, usually not necessary
                 if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)   
        
                     if (attributeNameTest.test(keyOnlyInnerTag))
                         return true;
        
         // A match was not found in either the "key-value pairs", or the boolean "key-only list."
         return false;
        
      • hasValue

        🡅  🡇    
        public java.util.Map.Entry<java.lang.String,​java.lang.String> hasValue​
                    (java.lang.String attributeValue,
                     boolean retainQuotes,
                     boolean preserveKeysCase)
        
        Convenience Method
        Invokes: hasValue(Predicate, boolean, boolean)
        Passes: String.equals(attributeValue) as the test-Predicate
        Code:
        Exact Method Body:
         return hasValue((String s) -> attributeValue.equals(s), retainQuotes, preserveKeysCase);
        
      • hasValue

        🡅  🡇    
        public java.util.Map.Entry<java.lang.String,​java.lang.String> hasValue​
                    (java.util.regex.Pattern attributeValueRegExTest,
                     boolean retainQuotes,
                     boolean preserveKeysCase)
        
        Convenience Method
        Invokes: hasValue(Predicate, boolean, boolean)
        Passes: attributeValueRegExTest.asPredicate()
        Code:
        Exact Method Body:
         return hasValue(attributeValueRegExTest.asPredicate(), retainQuotes, preserveKeysCase);
        
      • hasValue

        🡅  🡇    
        public java.util.Map.Entry<java.lang.String,​java.lang.String> hasValue​
                    (java.util.function.Predicate<java.lang.String> attributeValueTest,
                     boolean retainQuotes,
                     boolean preserveKeysCase)
        
        Will search this TagNode to determine if any inner-tag key-value pairs have an attribute-value that is accepted by the String-Predicate parameter 'attributeValueTest'.

        NOTE: If 'this' instance of TagNode is a closing-version of the HTML Tag (SPECIFICALLY: this.isClosing == TRUE), then this method shall exit gracefully - and immediately - returning 'null'. The (sometimes) requisite exception for such cases - OpeningTagNodeExpectedException - shall NOT throw. Remember that HTML will not permit attributes placed inside closing HTML Tags (</DIV>, </TABLE>, or </SPAN> for instance).
        Parameters:
        attributeValueTest - A String-Predicate parameter that is used to accept or reject a match with the list of all attribute values in 'this' HTML Element

        NOTE: The class StrFilter s capable of generating a rather wide range of test-Predicate's using regular-expressions. This is one option to think about when using this method.
        retainQuotes - The parameter is required to inform the program whether or not the quotation marks should be included along with the returned value inside the Map.Entry. This can sometimes be useful, for example, when complicated script-containing TagNode's are involved.
        preserveKeysCase - When this parameter is TRUE, the program will not invoke String.toLowerCase() on the Map.Entry's key. If FALSE, then the returned Map.Entry will have a key (attribute-name) that is strictly in lower-case format.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        Returns:
        Returns a Map.Entry<String, String> Key-Value Pair - if and only if there are any inner-tag's whose value is accepted by the input-parameter Predicate-test 'attributeNameTest'.

        If no such match is found, this method will return 'null'
        See Also:
        TagNode.AttrRegEx.KEY_VALUE_REGEX, StrFilter
        Code:
        Exact Method Body:
         // Closing HTML Elements may not have attribute-names or values.
         // Exit gracefully, and immediately.
        
         if (this.isClosing) return null;
        
         // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
         // are simply too short to have such an attribute-value pair.
         // 5 (characters) are: '<', '>', ' ', 'X' and '=' 
         // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X=>
        
         if (this.str.length() < (this.tok.length() + 5)) return null;
        
         // Get all inner-tag key-value pairs.  If any are 'equal' to parameter attributeName,
         // return TRUE immediately.
        
         Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
        
         while (keyValueInnerTags.find())
         {
             // Matcher.group(3) has the key's value, of the inner-tag key-value pair
             // (matcher.group(2) has the key's name)
        
             String foundAttributeValue = keyValueInnerTags.group(3);
        
             // The comparison must be performed on the version of the value that DOES NOT HAVE the
             // surrounding quotation-marks
        
             String foundAttributeValueNoQuotes =
                 StringParse.ifQuotesStripQuotes(foundAttributeValue);
        
             // Matcher.group(3) has the key-value, make sure to remove quotation marks (if present)
             // before comparing.
        
             if (attributeValueTest.test(foundAttributeValueNoQuotes))
        
                 // matcher.group(2) has the key's name, not the value.  This is returned via the
                 // Map.Entry key
        
                 return retainQuotes
        
                     ? new AbstractMap.SimpleImmutableEntry<>(
                         preserveKeysCase
                             ? keyValueInnerTags.group(2)
                             : keyValueInnerTags.group(2).toLowerCase(),
                         foundAttributeValue
                     )
        
                     : new AbstractMap.SimpleImmutableEntry<>(
                         preserveKeysCase
                             ? keyValueInnerTags.group(2)
                             : keyValueInnerTags.group(2).toLowerCase(),
                         foundAttributeValueNoQuotes
                     );
         }
        
         // No match was identified, return null.
         return null;
        
      • getInstance

        🡅  🡇    
        public static TagNode getInstance​(java.lang.String tok,
                                          TC openOrClosed)
        This is an "identical" method to HTMLTags.hasTag, except that this method will do some parameter error-checking, and throw exceptions if possible, rather than simply returning null.

        NOTE: All three classes which inherit HTMLNode - TagNode, TextNode and CommentNode - are immutable. The instances cannot be changed. In order to update, tweak, or modify an HTML page, you must instantiate or retrieve another instance of HTMLNode, and replace the position in the containing-Vector with a new node.

        This method, 'getInstance' interacts with the class HTMLTags to retrieve one of the pre-instantiated OpeningTags or ClosingTags. It is possible to 're-use' the same instance of a TagNode in different pages and different Vector's in the same way that Java String's can be re-used, exactly because they are immutable.

        If a TagNode instance is used in different pages, the in-ability to change a TagNode's contents is what allows multiple, different pages to use them in multiple Vector's without worrying about their contents becoming affected by concurrency issues. class HTMLTags maintains 4 complete lists of already-instantiated TagNode's (upper-case, lower-case, opening-tag, closing-tag) in a java.util.TreeMap<String, TagNode> since usually a very high percentage of HTML Element's are elements with no attribute information at all.

        ALSO: It should be obvious to the reader, by now, that keeping 'pre-instantiated' TagNode elements that do contain attributes would be orders of magnitude more costly (and difficult}. This practice is not performed by the Java-HTML library.

        SIMILARLY: All pre-instantiated HTML TagNode's have ZERO attributes or "inner-tags" inside. To generate a TagNode with attributes, use the class TagNode constructor. Make sure not to leave out the rest of the inner-tags from the element-body String.
        Parameters:
        tok - Any valid HTML tag.
        openOrClosed - If TC.OpeningTags is passed, then an "open" version of the HTML tag will be returned,
        If TC.ClosingTags is passed, then a closing version will be returned.
        If TC.Both is accidentally passed - an IllegalArgumentException is thrown.
        Returns:
        An instance of this class
        Throws:
        java.lang.IllegalArgumentException - If parameter TC openOrClose is null or TC.Both
        HTMLTokException - If the parameter String tok is not a valid HTML-tag
        SingletonException - If the token requested is a singleton (self-closing) tag, but the Tag-Criteria 'TC' parameter is requesting a closing-version of the tag.
        See Also:
        HTMLTags.hasTag(String, TC), HTMLTags.isSingleton(String)
        Code:
        Exact Method Body:
         if (openOrClosed == null)
             throw new NullPointerException("The value of openOrClosed cannot be null.");
        
         if (openOrClosed == TC.Both)
             throw new IllegalArgumentException("The value of openOrClosed cannot be TC.Both.");
        
         if (HTMLTags.isSingleton(tok) && (openOrClosed == TC.ClosingTags))
        
             throw new SingletonException(
                 "The value of openOrClosed is TC.ClosingTags, but unfortunately you have asked " +
                 "for a [" + tok + "] HTML element, which is a singleton element, and therefore " +
                 "cannot have a closing-tag instance."
             );
        
         TagNode ret = HTMLTags.hasTag(tok, openOrClosed);
        
         if (ret == null)
             throw new HTMLTokException
                 ("The HTML-Tag provided isn't valid!\ntok: " + tok + "\nTC: " + openOrClosed);
        
         return ret;
        
      • cssClassesNOCSE

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> cssClassesNOCSE()
        Convenience Method
        Invokes: cssClasses()
        Catches-Exception
        Code:
        Exact Method Body:
         try { return cssClasses(); } catch (CSSStrException e) { return Stream.empty(); }
        
      • cssClasses

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> cssClasses()
        Method cssClasses is to be used as a tool for splitting the results of invoking method TagNode.AV("class") into String's - separating it by white-space. The code here is a rather 'naïve' attempt to retrieve CSS classes, because it follows the 95% percent rule: 95% of the time there should be no problems! More advanced HTML Generator Tools have taken advantage of HTML's 'flexibility' and have created CSS classes that my contain anything from parenthesis '(' and ')', to semi-colons ';', to many other formats that proprietary web-developers so choose. And rather than attempting to 'predict' all future changes to CSS Class-Names, this method is intended to help developers retrieve basic (READ: 'non-preprocessor') CSS class-information. This is the nature of changing web-internet tools.

        Primarily, recognizing that dynamically, script and pre-processor generated class information, cannot be known, without having script-processing performed. This package is designed for parsing, searching, and updating HTML Pages, but it does not execute script (at least not now). Therefore, rather than returning possibly fallacious results - an exception throws when non-standard HTML CSS Classes are used, instead. This does not mean that the CSS String 'attribute-value' cannot be retrieved, but rather just that the method TagNode.AV("class") should always work and be used instead.

        If a return value is provided, it is guaranteed to be correct.

        COUNTER-EXAMPLE: The following output was generated by a small, 8-line program that scrapes a web-page from 'Yahoo! News', and then prints out the CSS Class Information by invoking this, 'cssClasses(...)' method. Notice, that after the very fourth iteration of the loop (which iterates over instances of 'TagNode' - skipping TextNode and HTMLNode instances), the exception 'CSSStrException' is thrown. This is because Yahoo! uses something known as "CSS Pre-Processor" and "SCSS", and though the scrape-functions, themselves, cannot interpret the script-generted CSS Class-Names in this example, React-JS is able to update the class-strings (after-download, and in-the-browser) easily. The first text-box, below, contains the hilited source-code that produced the output. The next text-box contains the ouptut of this short program to the terminal-shell window.

        Counter Example:
         1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        // Load the top-level news page for Yahoo! news
        Vector<HTMLNode> v = HTMLPage.getPageTokens(new URL("https://news.yahoo.com"), false);
        
        // Iterate through each and every "TagNode" element that contains a CSS "Class" Attribute
        for (TagNode tn : InnerTagGet.all(v, "class"))
        {
            // Print out the actual 'text' of the TagNode.  Use the "Shell.C" Unix-Colors class to print them in "Bright Red"
            System.out.println(C.BRED + tn.str + C.RESET);
        
            // Retrieve each / all / any of the CSS 'class' attributes, and print them.
            tn.cssClasses().forEach(c -> System.out.print("[" + c + "],\t"));
        
            // Before starting next TagNode class-attribute information, send a CRLF to the terminal
            System.out.println();
        }
        

        The following text was generated by the above short program, scraping Yahoo! News. It prints out relevant CSS Class Information. Notice, perhaps, the EXCEPTION which is eventually thrown is due to complicated Dynamically-Loaded CSS classes. Do keep in mind that their are simple alternatives, though.

        UNIX or DOS Shell Command:
        Downloading, Scraping HTTPS://news.yahoo.com/, Printing all TagNode Elements, and their CSS-Classes by invoking (and printing) the results of: TagNode.cssClasses() <html id="atomic" class="NoJs chrome featurephone" lang="en-US"> [nojs], [chrome], [featurephone], <div class="render-target-active render-target-default" id="render-target-default" data-reactid="2"> [render-target-active], [render-target-default], <div class="news US en-US" data-reactid="3"> [news], [us], [en-us], <div id="YDC-MainCanvas" class="YDC-MainCanvas Bgc($bg-body) Bxz(bb) Mih(100%) W(100%) Pos(a) lightweight_Miw(1247px)" data-reactid="4"> Exception in thread "main" Torello.HTML.NodeSearch.CSSStrException: One of the compare-strings passed to a search-method's var-args String parameter 'compareStr': [bgc($bg-body)] Did not pass the CSS Token-Naming Testing Regular-Expression: [^-?[_a-zA-Z]+[_\-a-zA-Z0-9]*$] And this means it has been identified as an invalid CSS Token. This is not allowed here. If you are using TagNode.cssClasses(), switch to TagNode.AV('class'). If you are using TextComparitor.CONTAINS_CSS_CLASS*, switch to TextComparitor.EQ_CI_TRM
        Returns:
        The String-Value of the Attribute-Name 'class' where that String has been 'split' - using String.split(...) and a Match-White-Space Regular-Expression. This is a method that will work just fine, unless proprietary non-standard HTML 5 CSS Class-Names have been used.

        NOTE: This method shall never return 'null' - even if there are no attribute key-value pairs contained by the TagNode. If there are strictly zero classes, Stream.empty() shall be returned, instead.

        Conversion-Target Stream-Method Invocation
        String[] Stream.toArray(String[]::new);
        List<String> Stream.collect(Collectors.toList());
        Vector<String> Stream.collect(Collectors.toCollection(Vector::new));
        TreeSet<String> Stream.collect(Collectors.toCollection(TreeSet::new));
        Iterator<String> Stream.iterator();
        Throws:
        CSSStrException - IMPORTANT: This exception will throw if *any* of the identified sub-String's of the CSS 'class' attributes contain non-standard characters, or do not meet the requirements of a standard CSS class name. The Regular-Expression describing a properly-formed CSS Name can be viewed on the internet, or in class CSSStrException field VALID_CSS_CLASS_OR_NAME_TOKEN. This is an unchecked-RuntimeException. Beware before using this method on pages generated by different, proprietary, Web-Design Tools.

        NOTE: To avoid an exception throw and retrieve the CSS 'class' attribute, regardless of whether it is standard CSS, use TagNode.AV("class"). This will return the raw-contents of the 'class' inner-tag inside 'this' HTML Element
        See Also:
        cssClasses(), AV(String), StringParse.WHITE_SPACE_REGEX, CSSStrException.check(Stream)
        Code:
        Exact Method Body:
         // The CSS Class is just an attribute/inner-tag within an HTML Element.
         String classes = AV("class"); 
        
         // IF the "class" attribute was not present, OR (after trimming) was empty, return
         // "empty stream"
        
         if ((classes == null) || ((classes = classes.trim()).length() == 0))
             return Stream.empty();
        
         // STEP 1: Split the string on white-space
         // STEP 2: Check each element of the output Stream using the "CSSStrException Checker"
        
         return CSSStrException.check(StringParse.WHITE_SPACE_REGEX.splitAsStream(classes));
        
      • setCSSClasses

        🡅  🡇    
        public TagNode setCSSClasses​(SD quote,
                                     boolean appendOrClobber,
                                     java.lang.String... cssClasses)
        This method sets the CSS Class attribute-value, applying either one, or many, CSS Class-Names.

        example
        Parameters:
        quote - This is the quote that will be used when defining the attribute's key-value pair inside the tag element. The programmer is expected to decide between using: SingleQuotes, DoubleQuotes or none / no-quotes (by passing null, with caveats). If null is passed to this parameter, then the complete list of rules that are applied, as explained in detail in method setAV.

        Please review the complete delineation of the rules for how quotation marks are added and used in an HTML Tag Attribute, vis-a-vis the 'quote' parameter.
        appendOrClobber - When this parameter is TRUE, new class-names will be appended to any, already-existing, class-names in the TagNode 'class' attribute-value. When this parameter is FALSE, the new class-names will replace the current TagNode's 'class' attribute's value/definition. If FALSE is used, and this TagNode does not have a 'class' attribute-value assigned to it yet, a new one will be created.
        cssClasses - This var-args (String[] array, or 'list-of-String's) will accept any combination of valid CSS Class-Names. It is important to know that the elements of the input-parameter 'cssClasses' will each be checked for validity based on the standard CSS Class-Naming Rules. There is a regular-expression in the exception class CSSStrException that depicts exactly what constitutes a valid CSS Class-Name.

        NOTE: If the programmer is using a web-development tool that relies on bypassing the browser's CSS Styling Rules, and intends to dynamically update class-names (using some form of CSS pre-processor), then perhaps he/she may wish to use non-standard CSS class-names in his TagNode elements. If so, inserting non-standard names into the 'class' attribute-value of a TagNode should be done by calling the general-purpose method TagNode.setAV("class", "any-CSS-Class-String").

        The user may also use the general-purpose TagNode.appendAV("class", "any-CSS-Class-String", boolean, SD) , if appending a non-standard CSS class-name String, rather than clobbering or setting it is the desired result. In this way, the programmer can avoid the "Invalid CSS Class" Exception, which would certainly be thrown for most pre-processor CSS directives.
        Returns:
        This will return an updated TagNode. TagNode's are immutable, and cannot be changed. This is identical behavior to Java's class String Libraries and Classes. The updated TagNode that is returned will have it's original 'class' attribute eliminated (if it had one), and a new 'class' attribute-value will be assigned to contain the CSS classes named by the var-args String-parameter, each separated by a space character.
        Throws:
        CSSStrException - This exception shall throw if any of the 'cssClasses' in the var-args String... parameter do not meet the HTML 5 CSS Class naming rules.
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        QuotesException - Quotation marks are not intended to be used in a class inner-tag definition. However, if there happened to be one in a non-standard class-attribute definition and the same quote is selected to wrap the updated class definition, this exception will throw. If this parameter is passed 'null', and the updated HTML Element class-attribute definition would require quotation marks, then this exception will also throw - because an unquoted attribute-value would have white-space in its definition, which is not allowed either.
        See Also:
        CSSStrException.check(String[]), CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN, appendToAV(String, String, boolean, SD), setAV(String, String, SD)
        Code:
        Exact Method Body:
         // Throw CSSStrException if any of the input strings are invalid CSS Class-Names.
         CSSStrException.check(cssClasses);
        
         // Build the CSS 'class' Attribute String.  This will be inserted into the TagNode Element
         StringBuilder   sb      = new StringBuilder();
         boolean         first   = true;
        
         for (String c : cssClasses) 
             { sb.append((first ? "" : " ") + c.trim()); first=false; }
        
         return appendOrClobber
             ? appendToAV("class", " " + sb.toString(), false, quote)
             : setAV("class", sb.toString(), quote);
        
      • appendCSSClass

        🡅  🡇    
        public TagNode appendCSSClass​(java.lang.String cssClass,
                                      SD quote)
        Adds another CSS Class Name into 'this' TagNode

        example
        Parameters:
        cssClass - This is the CSS-Class name that is being inserted into 'this' instance of TagNode
        quote - This is the quote that will be used when defining the attribute's key-value pair inside the tag element. The programmer is expected to decide between using: SingleQuotes, DoubleQuotes or none / no-quotes (by passing null, with caveats). If null is passed to this parameter, then the complete list of rules that are applied, as explained in detail in method setAV.

        Please review the complete delineation of the rules for how quotation marks are added and used in an HTML Tag Attribute, vis-a-vis the 'quote' parameter.
        Returns:
        A newly instantiated TagNode with updated CSS Class Name(s).
        Throws:
        CSSStrException - This exception shall throw if any of the 'cssClasses' in the var-args String... parameter do not meet the HTML 5 CSS Class naming rules.
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        QuotesException - Quotation marks within the provided CSS Class are not compapitable with the quotation marks used by the attribute, itself. Generally, quotations are not used with CSS Classes; however, if a proprietary-implementation requires them, it becomes necessary to heed the requirements set forth by a browser's parser.
        See Also:
        CSSStrException.check(String[]), setAV(String, String, SD), appendToAV(String, String, boolean, SD)
        Code:
        Exact Method Body:
         // Do a validity check on the class.  If this is "problematic" due to use of specialized / 
         // pre-processed CSS Class directives, use the general purpose "setAV(...)" method
        
         CSSStrException.check(cssClass);
        
         String curCSSClassSetting = AV("class");
        
         // If there wasn't a CSS Class already defined, use "setAV(...)", 
         // otherwise use "appendToAV(...)"
        
         if ((curCSSClassSetting == null) || (curCSSClassSetting.length() == 0))
             return setAV("class", cssClass, quote);
        
         else
             return appendToAV("class", cssClass + ' ', true, quote);
        
      • cssStyle

        🡅  🡇    
        public java.util.Properties cssStyle()
        This is a regular-expression matching for picking out all CSS Style Elements. There are myriad 'ways and means' when it comes to CSS style elements. The official line is that CSS declarations and definitions, when inside an HTML TagNode Element, all 'style' attribute-names must obey a few simple rules.

        First, the declaration-name - for example CSS Properties such as width, height, font-weight, border etc... - are listed. Then that style-declaration must be followed by a colon ':' character followed by the definition for that . Finally, the Style-Definition itself must either end, or be followed by a semi-colon ';' and another property definition may follow.

        Some examples of HTML Tags with "In-Line CSS Properties" are listed here:

        • <DIV ID=MainDIV STYLE='background: black; color: white;'>
        • <UL STYLE='font-family: monospace; font-size: 110%;'>
        • <IMG CLASS=MyPhotos STYLE='border: 1px solid red; padding 1em;'>


        SANITY-CHECK: At any point, an invocation of method AV("style"), should always return the complete inline CSS-Style Attribute Value as a String.

        Because of the sheer-number of style-property variations, this method refrains from any validity-checking. So long as the rules declared by the CSS-Style Regular-Expression parser are obeyed (defined in CSS_INLINE_STYLE_REGEX ) this method will always return a usable Properties instance.

        The following example will hilite what this method "cssStyle()" can achieve. The output that is generated has been transcribed to the next window (after the one below).

        Example:
        // Load a page, and parse it into a vectorized-html page.
        Vector<HTMLNode> page = HTMLPage.getPageTokens(new URL("https://news.yahoo.com"), false);
        
        // Select all HTMLNodes that are "instanceof" TagNode, and have a STYLE="..." inner-tag
        Vector<TagNode> withStyle = InnerTagGet.all(page, "style");
        
        // The 'class' Attribute on Yahoo! News is exceptionally long.
        // For 'readability' (here), remove those attributes.
        // This removes all 'class' attributes in the 'withStyle' TagNode-Vector
        Attributes.remove(withStyle, "class");
        
        for (TagNode tn : withStyle)
        {
            // retrieve all CSS Style-Properties from the TagNode
            Properties styleProps = tn.cssStyle();
        
            // Print out the TagNode.  Use UNIX Color-Code "bright-yellow" (for readability)
            System.out.println(C.BYELLOW + tn + C.RESET);
        
            // Temp loop variable
            String s;
        
            // Iterate through every property key-value definition found in 'styleProps'.
            for (String property : styleProps.stringPropertyNames())
        
                System.out.println(
                    "\t" +  // make output readable
                    String.format("%-" + 30 + "s", (C.BCYAN + property + C.RESET + " : ")) +
                        // Print the Property 'Name' with some 'right-padding' and UNIX color "Bright-Cyan"
                    (s=styleProps.get(property).toString()).substring(0, Math.min(s.length(), 75))
                        // For the Property "value" - only print the first 75 characters...
                );
        }
        

        The following text was generated by the above short program, scraping Yahoo! News. It prints out relevant CSS Style Information. Each CSS Attribute that has been defined within-the-confines of the Element itself (via the 'STYLE' inner-tag) is printed out to the UNIX terminal.

        UNIX Shell Command Output:
        <div style="height:86px;" data-reactid="5" id="YDC-UH"> height : 86px <div style="background-color:transparent;" data-reactid="8"> background-color : transparent <input autocomplete="off" style="-webkit-appearance:none;" name="p" data-reactid="19" aria-label="Search" autocorrect="off" type="text" placeholder="Search" value="" autocapitalize="off" /> -webkit-appearance : none <button style="filter:chroma(color=#000);" type="submit" aria-label="Search" data-reactid="22" id="search-button"> filter : chroma(color=#000) <svg height="34" viewbox="0 0 48 48" width="34" style="fill:#400090;stroke:#400090;stroke-width:0;vertical-align:bottom;" data-icon="profile" data-reactid="27"> stroke-width : 0 vertical-align : bottom fill : #400090 stroke : #400090 <svg height="35" viewbox="0 0 512 512" width="30" style="fill:#400090;stroke:#400090;stroke-width:0;vertical-align:bottom;" data-icon="NavMail" data-reactid="31"> stroke-width : 0 vertical-align : bottom fill : #400090 stroke : #400090 <nav style="width:auto;" data-reactid="14" role="navigation"> width : auto <svg height="20" viewbox="0 0 96 96" width="20" style="vertical-align:middle;cursor:pointer;margin-top:-2px;color:#26282a;fill:#26282a;stroke:#26282a;stroke-width:0;" data-icon="StreamShare" data-reactid="51"> color : #26282a vertical-align : middle stroke-width : 0 margin-top : -2px fill : #26282a stroke : #26282a cursor : pointer <div id="defaultdestMAST" style=""> <div id="defaultdestLDRB" style=""> <div style="position:relative;top:0px;" data-reactid="22"> position : relative top : 0px <div style="max-width:900px;" data-reactid="2" id="YDC-Stream"> max-width : 900px <div tabindex="-1" style="padding-bottom:56.3%;" data-reactid="9"> padding-bottom : 56.3% <div style="-webkit-tap-highlight-color:transparent;" data-reactid="21"> -webkit-tap-highlight-color : transparent <div style="margin:-10px;" data-reactid="22"> margin : -10px <button style="padding:10px;" aria-label="Share Menu" data-reactid="23"> padding : 10px <svg height="20" viewbox="0 0 48 48" width="20" style="fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="share" data-reactid="24"> stroke-width : 0 vertical-align : bottom fill : #fff stroke : #fff <svg height="20" viewbox="0 0 16 16" width="20" style="position:relative;top:1px;margin:auto;vertical-align:middle;fill:#fff;stroke:#fff;stroke-width:0;" data-icon="comments" data-reactid="27"> position : relative vertical-align : middle stroke-width : 0 margin : auto top : 1px fill : #fff stroke : #fff <ul style="top:0;transition:top .4s;" data-reactid="30"> top : 0 transition : top .4s <div data-test-locator="streamhero-controls" style="height:26px;" data-reactid="49"> height : 26px <svg height="23" viewbox="0 0 48 48" width="23" style="margin-left:12px;opacity:0.6;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="caret-up" data-reactid="52"> margin-left : 12px opacity : 0.6 stroke-width : 0 vertical-align : bottom fill : #fff stroke : #fff <svg height="23" viewbox="0 0 48 48" width="23" style="margin-left:12px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="caret-down" data-reactid="55"> margin-left : 12px stroke-width : 0 vertical-align : bottom fill : #fff stroke : #fff <div style="padding-bottom:56%;" data-reactid="61"> padding-bottom : 56% <div style="padding-bottom:88%;" data-reactid="75"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="113"> padding-bottom : 56% <div style="padding-bottom:88%;" data-reactid="127"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="154"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="181"> padding-bottom : 56% <div style="padding-bottom:88%;" data-reactid="195"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="222"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="248"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="275"> padding-bottom : 56% <div style="padding-bottom:56%;" data-reactid="289"> padding-bottom : 56% <div style="padding-bottom:56%;" data-reactid="303"> padding-bottom : 56% <div style="padding-bottom:88%;" data-reactid="317"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="344"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="371"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="398"> padding-bottom : 56% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="400"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="401"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:56%;" data-reactid="415"> padding-bottom : 56% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="417"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 48 48" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="gallery" data-reactid="418"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:88%;" data-reactid="432"> padding-bottom : 88% <div style="padding-bottom:88%;" data-reactid="459"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="486"> padding-bottom : 56% <div style="padding-bottom:88%;" data-reactid="500"> padding-bottom : 88% <div style="padding-bottom:56%;" data-reactid="527"> padding-bottom : 56% <div style="padding-bottom:56%;" data-reactid="541"> padding-bottom : 56% <div style="padding-bottom:56%;" data-reactid="555"> padding-bottom : 56% <div style="height:0;width:0;" data-reactid="3" id="defaultFOOT-sizer"> height : 0 width : 0 <div id="defaultdestFOOT" style=""> <div style="height:0;width:0;" data-reactid="3" id="defaultFSRVY-sizer"> height : 0 width : 0 <div id="defaultdestFSRVY" style=""> <div style="position:relative;top:0px;" data-reactid="39"> position : relative top : 0px <div id="defaultdestLREC" style=""> <div style="max-width:900px;" data-reactid="2" id="Col2-1-Stream"> max-width : 900px <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/zHVKAUu2r5TTwV6qakrkGA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/2UIXIIqm4LTnxZg0X5i.yA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-04/6f703ff0-7d04-11ea-beef-801a4e71e676);" data-reactid="10"> background-image : url(https://s.yimg.com/ny/api/res/1.2/zHVKAUu2r5TTwV6qakrkGA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/Ys5hJWbOLmM_KAo5BwK0BQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/gqSAvUFANziiTJbCyeJmqw--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/os/creatr-images/2019-11/38e0d390-fcd6-11e9-9dbf-19fe4fa04817);" data-reactid="22"> background-image : url(https://s.yimg.com/ny/api/res/1.2/Ys5hJWbOLmM_KAo5BwK0BQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/G5wdl.JqIzh2ahAdWE.1tA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/4TK6IvbcM6B2WT8aN60UKg--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-US/reuters-finance.com/6203123d7024cdfb221604ba4a7b8aa0);" data-reactid="34"> background-image : url(https://s.yimg.com/ny/api/res/1.2/G5wdl.JqIzh2ahAdWE.1tA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/5fhqXTevZUHU.nbsEW7dpQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/UI0jsUElQl1vdfCIvRVrJg--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/insidermonkey.com/f32ccf5bbd02501b27ef965a1a7aad26);" data-reactid="46"> background-image : url(https://s.yimg.com/ny/api/res/1.2/5fhqXTevZUHU.nbsEW7dpQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/a42nb3icSWNkNKgdFIe4Rg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/bddUizWuPmPkXWok0myBOg--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/os/creatr-uploaded-images/2020-04/f1a6a890-7b76-11ea-b7ab-69a6e125abb4);" data-reactid="58"> background-image : url(https://s.yimg.com/ny/api/res/1.2/a42nb3icSWNkNKgdFIe4Rg--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/y9Bfyy_r5d0fClE.ibKusQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/ua9rm3_4SLLffTmAoSvmvA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/VDzkhWpF77whaWcOA1yXPA--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e937918b43fec6780d02a0f/5e937918b43fec6780d02a10_o_U_v2.jpg);" data-reactid="70"> background-image : url(https://s.yimg.com/ny/api/res/1.2/y9Bfyy_r5d0fClE.ibKusQ--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="71"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="72"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/QVFAZDpObZQbly2hVJFTtQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/oyvxAW4tgdtJIKnCZL29wQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-04/991c2290-79b9-11ea-be7f-c79a30d5ee49);" data-reactid="85"> background-image : url(https://s.yimg.com/ny/api/res/1.2/QVFAZDpObZQbly2hVJFTtQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/EX.pCQqbU3p6B6WUj.NaaA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/hKCLVgBst8AbLJPMxG5jgQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-04/f337b140-7aa4-11ea-a7bd-b72f2d3182a5);" data-reactid="97"> background-image : url(https://s.yimg.com/ny/api/res/1.2/EX.pCQqbU3p6B6WUj.NaaA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/CwPc0kK56oBN0mhFxrRieA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/M5DLYlWDOnTYEM0vNum_ow--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-US/reuters.com/637ba4f5a27bbf8ff710aff35be65228);" data-reactid="109"> background-image : url(https://s.yimg.com/ny/api/res/1.2/CwPc0kK56oBN0mhFxrRieA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/Q.id3lA_2RVHUipYYwwsIA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/7I4Kuij80NUX0cESxgrbyA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/os/creatr-images/2020-04/89c52ea0-7c15-11ea-a77d-36d3c609ef8a);" data-reactid="121"> background-image : url(https://s.yimg.com/ny/api/res/1.2/Q.id3lA_2RVHUipYYwwsIA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/ePSJEsrMO0RGuQ4zmFlz0w--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/xaPl5Hb_YfC7WgER0wse9Q--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/hQHVFPzBzVVtEbOx2hMNqg--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e93679884a08c660eef5be7/5e93679884a08c660eef5be8_o_U_v2.jpg);" data-reactid="133"> background-image : url(https://s.yimg.com/ny/api/res/1.2/ePSJEsrMO0RGuQ4zmFlz0w--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="134"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="135"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/mRRPKIyX0R7oo73K8vfstw--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/8ge4QvIM3jGA1QvKW0fZTw--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-US/reuters-finance.com/601f4a3964811a269aca0168f2f7c0f0);" data-reactid="148"> background-image : url(https://s.yimg.com/ny/api/res/1.2/mRRPKIyX0R7oo73K8vfstw--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/N9h9d6z6dhQzREy.jTeqKQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/0SF8UZWrj81i6jkuKcScJQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-04/b97691d0-784a-11ea-b7fe-3877f2dd45e0);" data-reactid="160"> background-image : url(https://s.yimg.com/ny/api/res/1.2/N9h9d6z6dhQzREy.jTeqKQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/19PqKm5gqlAMpJyAvSVRxg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/gdmNOvRuw_vkH8Z.3ldbQw--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en/Benzinga/af3b980f0214633c97c8d1f5f6b8d8e2);" data-reactid="172"> background-image : url(https://s.yimg.com/ny/api/res/1.2/19PqKm5gqlAMpJyAvSVRxg--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/i047mKFU5JlnMz5secHo5w--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/igypgBdqYfdqBDzF63ucUQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/os/creatr-images/2020-04/b8c9afa0-7ce2-11ea-be7d-2d4971ed799b);" data-reactid="184"> background-image : url(https://s.yimg.com/ny/api/res/1.2/i047mKFU5JlnMz5secHo5w--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/bznBrHaGLHNUpRfgWb6fiA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/RMNQiT657O98l17qpLRdHA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/7zVY3vVQ9.6pXGCkzSopig--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e93679484a08c660eef5be5/5e93679484a08c660eef5be6_o_U_v2.jpg);" data-reactid="196"> background-image : url(https://s.yimg.com/ny/api/res/1.2/bznBrHaGLHNUpRfgWb6fiA--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="197"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="198"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/M44IU2wp144myS9tImV5Bw--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/P7Pp2DyLpAD70AwYWF0H1A--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/kiplinger.com/d4bcd9f2a6ca8fde407b4053ec26eb2f);" data-reactid="211"> background-image : url(https://s.yimg.com/ny/api/res/1.2/M44IU2wp144myS9tImV5Bw--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/yMDGmcU5O6POWraLEe9QCg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/iXVvN_KBUDNiisYS9nwhyA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/09/5e8e0ed96ca6be6d5fb31241/5e8e799e549920624eefd3b9_o_U_v3.jpg);" data-reactid="223"> background-image : url(https://s.yimg.com/ny/api/res/1.2/yMDGmcU5O6POWraLEe9QCg--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="224"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="225"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/uSq36UD_AsyJ6WberW8Ggw--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/Wa_XWc8qgTfYTG.smVQl4g--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-03/26/5e7cfc135f6f6542b5f1b0b0/5e7d0b6a6ca6be4217587c49_o_U_v4.jpg);" data-reactid="238"> background-image : url(https://s.yimg.com/ny/api/res/1.2/uSq36UD_AsyJ6WberW8Ggw--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="239"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="240"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/apIwx3T.jLSqpz3t6UNKgQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/wTkDXBHN2KmyTuC5OUdylA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/os/creatr-uploaded-images/2020-04/8e406100-7cda-11ea-a7fd-1c1af5d5aef1);" data-reactid="253"> background-image : url(https://s.yimg.com/ny/api/res/1.2/apIwx3T.jLSqpz3t6UNKgQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/yZpN1gdQt6vdqr3kPuAQ3g--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/QFEsHBuvrVZCGcF6iUAypg--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/Q0NsiY9KtIUJhPYY_P6opw--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e935f4292fc28752b7df245/5e935f4525ffdb000102745c_1280x720_FES_v1.jpg);" data-reactid="265"> background-image : url(https://s.yimg.com/ny/api/res/1.2/yZpN1gdQt6vdqr3kPuAQ3g--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="266"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="267"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/5kA1NwyfUsX2ayNbkc4.bw--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/EnKSPWKjn4VFfYlO7bZ3KA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/usa_today_money_325/6e7e2347716edec37842707988d62d02);" data-reactid="280"> background-image : url(https://s.yimg.com/ny/api/res/1.2/5kA1NwyfUsX2ayNbkc4.bw--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/j7U2HxAm1fBqgijDZQ_G6A--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/IIiNwcYu3W1X_c5r2NryvQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/kiplinger.com/852b73caf9727bab7320b50cd15c4adf);" data-reactid="292"> background-image : url(https://s.yimg.com/ny/api/res/1.2/j7U2HxAm1fBqgijDZQ_G6A--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/uHKZoXXRPlg3I0SJ.Sd7SA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/4iDa4lNkS3R_BZBueOsd7g--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-03/910edf60-705a-11ea-8b77-8ac3eef493cf);" data-reactid="304"> background-image : url(https://s.yimg.com/ny/api/res/1.2/uHKZoXXRPlg3I0SJ.Sd7SA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/5YLdKjYKMGL0UTdt.TvcwA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/pt.rGJPR5grq4xye.U9uwQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/usa_today_money_325/fe2532294f100b45f097dd6e7bb17be7);" data-reactid="316"> background-image : url(https://s.yimg.com/ny/api/res/1.2/5YLdKjYKMGL0UTdt.TvcwA--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/vE19PDNIRyV3iCxoLhMX5A--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/l.DYogf.Ixzc8XTQ.4.9Kw--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/03/5e879dce5f6f65601e7e592a/5e879dce5f6f65601e7e592b_o_U_v2.jpg);" data-reactid="328"> background-image : url(https://s.yimg.com/ny/api/res/1.2/vE19PDNIRyV3iCxoLhMX5A--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="329"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="330"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/eYIUwigcXI9U4rBnTiyNsA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/TLJkSQf78GxPiW.aGhKwrw--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/kdrZPJoCx_t5tcxtWPWpEQ--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e93555a90a80d79a635231b/5e93555a90a80d79a635231c_o_U_v2.jpg);" data-reactid="343"> background-image : url(https://s.yimg.com/ny/api/res/1.2/eYIUwigcXI9U4rBnTiyNsA--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="344"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="345"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/qb6TUw7SrnCAr9HIPjnmjQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/hKr9PAu0Pe5YralkmbEAHQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en/fx_empire_176/7487ed84f0b1640fcfe8e7b1872a1881);" data-reactid="358"> background-image : url(https://s.yimg.com/ny/api/res/1.2/qb6TUw7SrnCAr9HIPjnmjQ--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/Z9lqib01GToEG0diuwJQVg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/yURaMJJjbFn.vHqiJjyWdg--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-01/eccde4d0-43a6-11ea-bb75-e378299e78d9);" data-reactid="370"> background-image : url(https://s.yimg.com/ny/api/res/1.2/Z9lqib01GToEG0diuwJQVg--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/F7Kzs5KAyEiqSQEw1lMA0w--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/BQhLWVPcIJZu0E_8.S0svA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en/fx_empire_176/f9c062884e57ad93391eb3b680a419b9);" data-reactid="382"> background-image : url(https://s.yimg.com/ny/api/res/1.2/F7Kzs5KAyEiqSQEw1lMA0w--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/36NzXnShtJF9otBOdCqX2w--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/3nrr93yHkmoalfbGb9rsSA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s1.yimg.com/uu/api/res/1.2/cXtj3GsNYMhGTr0c5QBq9Q--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e93465e549920624eefde60/5e9346623260720001fd89bc_1280x720_FES_v1.jpg);" data-reactid="394"> background-image : url(https://s.yimg.com/ny/api/res/1.2/36NzXnShtJF9otBOdCqX2w--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="395"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="396"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/7jUts.to8VGXohMjcw2c0A--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/L4Trp2E6Gld.gpEE8sbrGQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-03/24/5e7a3e11da8e831cd730e3da/5e7a3e11da8e831cd730e3db_o_U_v2.jpg);" data-reactid="409"> background-image : url(https://s.yimg.com/ny/api/res/1.2/7jUts.to8VGXohMjcw2c0A--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="410"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="411"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/uPL33Yks5vrekTM0apCTOg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/WGPamrfVQ3HzXf2AQ6DipQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-us/usa_today_money_325/9f41ba2e34b0dfb012e3e38b461bdff5);" data-reactid="424"> background-image : url(https://s.yimg.com/ny/api/res/1.2/uPL33Yks5vrekTM0apCTOg--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/haQWWFistKffxTQb.LI8fA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l2.yimg.com/uu/api/res/1.2/C3aCT3_5CjlznKD38Nx_jA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2019-09/1af93af0-db18-11e9-bff7-e34690b9be52);" data-reactid="436"> background-image : url(https://s.yimg.com/ny/api/res/1.2/haQWWFistKffxTQb.LI8fA--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="437"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="438"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/B__47T2HNVGCd8y4oJ1Sww--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/LXPYkIG9XVOhLwFXsRKYTA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/b7F3OOxxajxDuM_JODhS3w--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e93397b9bb2c06b40c89e33/5e93397b9bb2c06b40c89e34_o_U_v2.jpg);" data-reactid="451"> background-image : url(https://s.yimg.com/ny/api/res/1.2/B__47T2HNVGCd8y4oJ1Sww--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="452"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="453"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/Uo5MF7aeN.kqQZBectYv6Q--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/L0huYimDIMz4wbWlMdb_1Q--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en-US/the_block_83/dfc4cd7cac9a2413be149f9d69d34c6d);" data-reactid="466"> background-image : url(https://s.yimg.com/ny/api/res/1.2/Uo5MF7aeN.kqQZBectYv6Q--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/ackt9j5fZ48Wwcv8ZFExWg--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l.yimg.com/uu/api/res/1.2/at5cquxDMds8sMRsc_NRsA--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-03/25/5e7b5d75da8e831cd730e6bd/5e7b5d75da8e831cd730e6be_o_U_v2.jpg);" data-reactid="478"> background-image : url(https://s.yimg.com/ny/api/res/1.2/ackt9j5fZ48Wwcv8ZFExWg--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="479"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="480"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/x3lcnEBxqUd6XfpTruJHbw--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/fq3eSPTj3sVy5009DEM3ug--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://media.zenfs.com/en/bloomberg_opinion_268/3305b3219b8ca4274de1ce2d105a4665);" data-reactid="493"> background-image : url(https://s.yimg.com/ny/api/res/1.2/x3lcnEBxqUd6XfpTruJHbw--~A/YXBwaWQ9aG padding-bottom : 52% <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/amaw5PNz2O5GGD2ylfgAqA--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/nTGrYvbX9LdQZop5GNlb2A--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/uu/api/res/1.2/aOJYFJ3Aouv7clfF0BcVSQ--~B/dz0xMjgwO2g9NzIwO2FwcGlkPXl0YWNoeW9u/https://s.yimg.com/hd/cp-video-transcode/prod/2020-04/12/5e932c7284a08c660eef5b98/5e932c7b25ffdb000102713c_1280x720_FES_v1.jpg);" data-reactid="505"> background-image : url(https://s.yimg.com/ny/api/res/1.2/amaw5PNz2O5GGD2ylfgAqA--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="506"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="507"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div style="padding-bottom:52%;background-image:url(https://s.yimg.com/ny/api/res/1.2/wtFH3BocnUekbSQZLOEcAQ--~A/YXBwaWQ9aGlnaGxhbmRlcjtoPTMxMjt3PTYwMDtxPTc1O2ZpPXN0cmlt/http://l1.yimg.com/uu/api/res/1.2/7geayxyFnaPr5ozhWsIWqQ--/YXBwaWQ9eXRhY2h5b247cT03NTs-/https://s.yimg.com/hd/cp-video-transcode/prod/2020-03/23/5e78faffcbde734796205b1e/5e7916f969e6c577d70ae714_o_U_v4.jpg);" data-reactid="520"> background-image : url(https://s.yimg.com/ny/api/res/1.2/wtFH3BocnUekbSQZLOEcAQ--~A/YXBwaWQ9aG padding-bottom : 52% <span dir="ltr" style="height:50px;width:50px;background-size:50px;background-image:url(https://s.yimg.com/dh/ap/default/150604/orb.png);" data-reactid="521"> height : 50px background-image : url(https://s.yimg.com/dh/ap/default/150604/orb.png) background-size : 50px width : 50px <svg height="20" viewbox="0 0 512 512" width="20" style="left:15px;top:15px;fill:#fff;stroke:#fff;stroke-width:0;vertical-align:bottom;" data-icon="CorePlay" data-reactid="522"> stroke-width : 0 vertical-align : bottom left : 15px top : 15px fill : #fff stroke : #fff <div id="defaultdestLREC2" style="">
        Returns:
        This shall return an instance of Java's 'Properties' class, and it will contain all the style definitions and their values.

        NOTE: All style-declaration names shall be saved using lower-case. The style attribute-values, however, will not be converted to lower-case.

        NULL NOTE: This method will not return null. Rather, if 'this' instance of TagNode Element does not have a style attribute, an empty (non-null) instance of Properties which contains zero CSS Style-Declaration Key-Value Pairs will be returned instead.
        See Also:
        TagNode.AttrRegEx.CSS_INLINE_STYLE_REGEX, AV(String)
        Code:
        Exact Method Body:
         Properties  p           = new Properties();
         String      styleStr    = AV("style");
             // Returns the complete attribute-value of "style" in the TagNode
        
         // There was no STYLE='...' attribute found, return empty Properties.
         if (styleStr == null) return p;
        
         // Standard String-trim routine
         styleStr = styleStr.trim();
        
         if (styleStr.length() == 0) return p;
        
         // This reg-ex "iterates" over matches of strings that follow the (very basic) form:
         // declaration-name: declaration-string;
         //
         // Where the ":" (color), and ";" (semi-colon) are the only watched/monitored tokens.
         // See the reg-ex definition in "See Also" tag.
        
         Matcher m = AttrRegEx.CSS_INLINE_STYLE_REGEX.matcher(styleStr);
        
         // m.group(1): declaration-name     (stuff before the ":")
         // m.group(2): declaration-string   (stuff before the next ";", or end-of-string)
         // 
         // For Example, if the style attribute-value was specified as:
         // STYLE="font-weight: bold;   margin: 1em 1em 1em 2em;   color: #0000FF"
         //
         // The returned Properties object would contain the string key-value pair elements:
         // "font-weight"    -> "bold"
         // "margin"         -> "1em 1em 1em 2em"
         // "color"          -> "#0000FF"
        
         while (m.find()) p.put(m.group(1).toLowerCase(), m.group(2));
        
         return p;
        
      • setCSSStyle

        🡅  🡇    
        public TagNode setCSSStyle​(java.util.Properties p,
                                   SD quote,
                                   boolean appendOrClobber)
        This will set 'this' instance of TagNode's CSS-Style Attribute such that it contains the CSS Definition Property-Value Pairs that are contained in the Properties parameter 'p'
        Parameters:
        p - This should be a map between CSS Definition Property-Names to Property-Values. Some examples would include (below):
        Property-NameProperty-Value
        font-weightbold
        backgroundlightyellow
        width200px
        border5px 10px 5px 15px
        box-shadowinset 0 0 1em brown
        quote - This is the quote that will be used when defining the attribute's key-value pair inside the tag element. The programmer is expected to decide between using: SingleQuotes, DoubleQuotes or none / no-quotes (by passing null, with caveats). If null is passed to this parameter, then the complete list of rules that are applied, as explained in detail in method setAV.

        Please review the complete delineation of the rules for how quotation marks are added and used in an HTML Tag Attribute, vis-a-vis the 'quote' parameter.
        appendOrClobber - When this parameter is TRUE new style-definitions will be appended to any, already-existing, style-definitions in the TagNode 'style' attribute-value. When this parameter is FALSE, the new style-definitions will replace the HTML Element 'style' attributes current-definition (or build a new one, if 'this' TagNode does not have a 'style' attribute).
        Returns:
        This will return a new, updated HTML TagNode that has its style-attribute inserted (or replaced). The class TagNode is immutable (the original TagNode will be unmodified).
        Throws:
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        CSSStrException - If there is an invalid CSS Style Property Name.
        QuotesException - If the style-element's quotation marks are incompatible with any and all quotation marks employed by the style-element definitions.
        See Also:
        CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN, appendToAV(String, String, boolean, SD), setAV(String, String, SD)
        Code:
        Exact Method Body:
         // this is used in the "exception constructor" below (which means, it might not be used).
         int counter = 0;
        
         // Follows the "FAIL-FAST" philosophy, and does not allow invalid CSS declaration-name
         // tokens.  Use TagNode.setAV("style", ...), or TagNode.appendToAV("style", ...), to
         // bypass exception-check.
        
         for (String key : p.stringPropertyNames())
        
             if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(key))
             {
                 String[] keyArr = new String[p.size()];
        
                 throw new CSSStrException(
        
                     "CSS Style Definition Property: [" + key + "] does not conform to the " +
                     "valid, HTML 5, regular-expression for CSS Style Definitions Properties:\n[" +
                     CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "].",
        
                     // One minor "PRESUMPTION" that is the Iterator will return the elements of 
                     // Properties in the EXACT SAME ORDER on both creations / instantiations of the
                     // iterator.  Specifically: two invocations of method .iterator(), will return
                     // the same-list of property-keys, in the same order, BOTH TIMES.  Once for the
                     // for-loop, and once for the exception message.  This only matters if there is
                     // an exception.
        
                     p.stringPropertyNames().toArray(keyArr),
                     ++counter
                 );
             }
             else ++counter; 
        
         // Follows the "FAIL-FAST" philosophy, and does not allow "quotes-within-quote" problems
         // to occur.  An attribute-value surrounded by single-quotes, cannot contain a
         // single-quote inside, and double-within-double.
        
         counter = 0;
        
         for (String key : p.stringPropertyNames())
        
             if (StrCmpr.containsOR(p.get(key).toString(), ("" + quote.quote), ";"))
             {
                 String[] keyArr = new String[p.size()];
        
                 throw new CSSStrException(
                     "CSS Style Definition Property: [" + key + "], which maps to style-" +
                     "definition property-value:\n[" + p.get(key) + "], contains either a " +
                     "semi-colon ';' character, or the same quotation-mark specified: [" + 
                     quote.quote + "], and is therefore not a valid assignment for a CSS " +
                     "Definition Property.",
        
                     p   .stringPropertyNames()
                         .stream()
                         .map((String propertyName) -> p.get(propertyName))
                         .toArray((int i) -> new String[i]),
        
                     ++counter
                 );
             }
             else ++counter;
        
         // ERROR-CHECKING: FINISHED, NOW JUST BUILD THE ATTRIBUTE-VALUE STRING
         // (using StringBuilder), AND INSERT IT.
        
         StringBuilder sb = new StringBuilder();
        
         for (String key : p.stringPropertyNames()) sb.append(key + ": " + p.get(key) + ";");
        
         return appendOrClobber
             ? appendToAV("style", " " + sb.toString(), false, quote)
             : setAV("style", sb.toString(), quote);
        
      • getID

        🡅  🡇    
        public java.lang.String getID()
        Convenience Method
        Invokes: AV(String)
        Passes: String "id", the CSS-ID attribute-name
        Code:
        Exact Method Body:
         return AV("id").trim();
        
      • setID

        🡅  🡇    
        public TagNode setID​(java.lang.String id,
                             SD quote)
        This merely sets the current CSS 'ID' Attribute Value.
        Parameters:
        id - This is the new CSS 'ID' attribute-value that the user would like applied to 'this' instance of TagNode.
        quote - This is the quote that will be used when defining the attribute's key-value pair inside the tag element. The programmer is expected to decide between using: SingleQuotes, DoubleQuotes or none / no-quotes (by passing null, with caveats). If null is passed to this parameter, then the complete list of rules that are applied, as explained in detail in method setAV.

        Please review the complete delineation of the rules for how quotation marks are added and used in an HTML Tag Attribute, vis-a-vis the 'quote' parameter.
        Returns:
        Returns a new instance of TagNode that has an updated 'ID' attribute-value.
        Throws:
        java.lang.IllegalArgumentException - This exception shall throw if an invalid String-token has been passed to parameter 'id'.

        BYPASS NOTE: If the user would like to bypass this exception-check, for instance because he / she is using a CSS Pre-Processor, then applying the general-purpose method TagNode.setAV("id", "some-new-id") ought to suffice. This other method will not apply validity checking, beyond scanning for the usual "quotes-within-quotes" problems, which is always disallowed.
        ClosingTagNodeException - This exception will throw if an attempt is made to invoke this method on a TagNode whose 'isClosing' boolean-field is set to TRUE. The exception throws because HTML browsers do not permit the closing-versions of any TagNode to contain inner-tags (attributes).
        See Also:
        CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN, setAV(String, String, SD)
        Code:
        Exact Method Body:
         if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(id))
        
             throw new IllegalArgumentException(
                 "The id parameter provide: [" + id + "], does not conform to the standard CSS " +
                 "Names.\nEither try using the generic TagNode.setAV(\"id\", yourNewId, quote); " +
                 "method to bypass this check, or change the value passed to the 'id' parameter " +
                 "here."
             );
        
         return setAV("id", id.trim(), quote);
        
      • dataAV

        🡅  🡇    
        public java.lang.String dataAV​(java.lang.String dataName)
        Convenience Method
        Invokes: AV(String)
        Passes: "data-" prepended to parameter 'dataName' for the attribute-name
        Code:
        Exact Method Body:
         return AV("data-" + dataName);
        
      • removeDataAttributes

        🡅  🡇    
        public TagNode removeDataAttributes()
        This method will remove any HTML 'data' Attributes - if there are any present. "Data Inner-Tags" are simply the attributes inside of HTML Elements whose names begin with "data-".

        Since class TagNode is immutable, a new TagNode must be instantiated, if any data-inner-tags are removed. If no data-attributes are removed, 'this' instance TagNode shall be returned instead.
        Returns:
        This will return a newly constructed 'TagNode' instance, if there were any "Data Attributes" that were removed by request. If the original TagNode has remained unchanged, a reference to 'this' shall be returned.
        Throws:
        ClosingTagNodeException - This exception throws if 'this' instance of TagNode is a closing-version of the HTML Element. Closing HTML Elements may not have data attributes, because they simply are not intended to contain any attributes.
        Code:
        Exact Method Body:
         // Because this method expects to modify the TagNode, this exception-check is necessary.
         ClosingTagNodeException.check(this);
        
         // Make sure to keep the quotes that were already used, we are removing attributes, and the
         // original attributes that aren't removed need to preserve their quotation marks.
        
         Properties          p       = this.allAV(true, true);
         Enumeration<Object> keys    = p.keys();
         int                 oldSize = p.size();
        
         // Visit each Property Element, and remove any properties that have key-names which
         // begin with the word "data-"
        
         while (keys.hasMoreElements())
         {
             String key = keys.nextElement().toString();
             if (key.startsWith("data-")) p.remove(key);
         }
        
         // If any of properties were removed, we have to rebuild the TagNode, and replace it.
         // REMEMBER: 'null' is passed to the 'SD' parameter, because we preserved the original
         //           quotes above.
        
         return (p.size() == oldSize)
             ? this
             : new TagNode(this.tok, p, null, this.str.endsWith("/>"));
        
      • getDataAV

        🡅  🡇    
        public java.util.Properties getDataAV()
        Convenience Method
        Invokes: getDataAV(boolean)
        Attribute-names will be in lower-case
        Code:
        Exact Method Body:
         return getDataAV(false);
        
      • getDataAV

        🡅  🡇    
        public java.util.Properties getDataAV​(boolean preserveKeysCase)
        This will retrieve and return any/all 'data' HTML Attributes. "Data Inner-Tags" are simply the attributes inside of HTML Elements whose names begin with "data-".
        Parameters:
        preserveKeysCase - When this parameter is passed TRUE, the case of the attribute names in the returned Properties table will have been preserved. When FALSE is passed, all Properties keys shall have been converted to lower-case first.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        Returns:
        This will return a java.util.Properties of all data-attributes which are found in 'this' HTML Element. If no such attributes were found, 'null' shall not be returned by this method, but rather an empty Properties instance will be provided, instead.
        See Also:
        isClosing, HTMLNode.str, tok, TagNode.AttrRegEx.DATA_ATTRIBUTE_REGEX
        Code:
        Exact Method Body:
         Properties ret = new Properties();
        
         // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
         //       field is only longer than the token, itself, by 3 or less characters cannot have
         //       attributes.v In that case, just return an empty 'Properties' instance.
        
         if (isClosing || (str.length() <= (tok.length() + 3))) return ret;
        
         // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs
         //      ONLY PAIRS WHOSE KEY BEGINS WITH "data-" WILL MATCH
         //
         // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign)
         // m.group(3): returns the 'value' portion of the key-value pair, after an '='
        
         Matcher m = AttrRegEx.DATA_ATTRIBUTE_REGEX.matcher(this.str);
        
         // NOTE: HTML mandates attributes must be 'case-insensitive' to the attribute 'key-part'
         //      (but not necessarily the 'value-part')
         //
         // HOWEVER: Java does not require anything for the 'Properties' class.
         // ALSO:    Case is PRESERVED for the 'value-part' of the key-value pair.
        
         if (preserveKeysCase)
             while (m.find())
                 ret.put(m.group(2), StringParse.ifQuotesStripQuotes(m.group(3)));
        
         else
             while (m.find())
                 ret.put(m.group(2).toLowerCase(), StringParse.ifQuotesStripQuotes(m.group(3)));
         
         return ret;
        
      • getDataAN

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> getDataAN()
        Convenience Method
        Invokes: getDataAN(boolean)
        Attribute-names will be in lower-case
        Code:
        Exact Method Body:
         return getDataAN(false);
        
      • getDataAN

        🡅  🡇    
        public java.util.stream.Stream<java.lang.String> getDataAN​
                    (boolean preserveKeysCase)
        
        This method will only return a list of all data-attribute names. The data-attribute values shall not be included in the result. An HTML Element "data-attribute" is any attribute inside of an HTML TagNode whose key-value pair uses a key that begins with "data-"... It is that simple!
        Parameters:
        preserveKeysCase - When this parameter is passed TRUE, the case of the attribute names that are returned by this method will have been preserved. When FALSE is passed, they shall be converted to lower-case first.

        HTML expects that all attribute names function in a CASE INSENSITIVE fashion. Java, however, makes no such expectations on it's class java.util.Properties tables, or most other String-containers. Preserving the case of an attribute-name will produce more consistent looking HTML. But for more easily-searchable java-Strings, converting to lower-case can make coding easier.
        Returns:
        Returns an instance of Stream<String>. The attribute names that are returned, are all converted to lower-case.

        A return type of Stream<String> is used. Please see the list below for how to convert a Stream to another data-type.

        Conversion-Target Stream-Method Invocation
        String[] Stream.toArray(String[]::new);
        List<String> Stream.collect(Collectors.toList());
        Vector<String> Stream.collect(Collectors.toCollection(Vector::new));
        TreeSet<String> Stream.collect(Collectors.toCollection(TreeSet::new));
        Iterator<String> Stream.iterator();

        NOTE: This method shall never return 'null' - even if there are no data-attribute key-value pairs contained by the TagNode. If there are strictly zero such attributes, (Stream.empty()) shall be returned, instead.
        See Also:
        HTMLNode.str, tok, TagNode.AttrRegEx.DATA_ATTRIBUTE_REGEX
        Code:
        Exact Method Body:
         // Java Stream's can be quickly and easily converted to any data-structure the user needs.
         Stream.Builder<String> b = Stream.builder();
        
         // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
         //       field is only longer than the token, itself, by 3 or less characters cannot have
         //       attributes.  In that case, just return an empty 'Properties' instance.
        
         if (isClosing || (str.length() <= (tok.length() + 3))) return Stream.empty();
        
         // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs
         //      ONLY PAIRS WHOSE KEY BEGINS WITH "data-" WILL MATCH
         // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign).
         // m.group(3): returns the 'value' portion of the key-value pair, after an '='
        
         Matcher m = AttrRegEx.DATA_ATTRIBUTE_REGEX.matcher(this.str);
        
         // NOTE: HTML mandates attributes must be 'case-insensitive' to the attribute 'key-part'
         //      (but not necessarily the 'value-part')
         // HOWEVER: Java does not require anything for the 'Properties' class.
         // ALSO: Case is PRESERVED for the 'value-part' of the key-value pair.
        
         if (preserveKeysCase)   while (m.find()) b.accept(m.group(2));
         else                    while (m.find()) b.accept(m.group(2).toLowerCase());
         
         return b.build();
        
      • toStringAV

        🡅  🡇    
        public java.lang.String toStringAV()
        This does a "longer version" of the parent toString() method. This is because it also parses and prints inner-tag key-value pairs. The ordinary public String toString() method that is inherited from parent class HTMLNode will just return the value of class HTMLNode field: public final String str.
        Returns:
        A String with the inner-tag key-value pairs specified.

        Example:
         // The following code, would output the text below
         TagNode tn = new TagNode("<BUTTON CLASS='MyButtons' ONCLICK='MyListener();'>");
         System.out.println(tn.toStringAV());
        
         // Outputs the following Text:
         
         // TagNode.str: [<BUTTON class='MyButtons' onclick='MyListener();'>], TagNode.tok: [button],
         //      TagNode.isClosing: [false]
         // CONTAINS a total of (2) attributes / inner-tag key-value pairs:
         // (KEY, VALUE):   [onclick], [MyListener();]
         // (KEY, VALUE):   [class], [MyButtons]
        
        See Also:
        allAV(), tok, isClosing, HTMLNode.toString()
        Code:
        Exact Method Body:
         StringBuilder sb = new StringBuilder();
        
         // Basic information.  This info is common to ALL instances of TagNode
         sb.append(
             "TagNode.str: [" + this.str + "], TagNode.tok: [" + this.tok + "], " +
             "TagNode.isClosing: [" + this.isClosing + "]\n"
         );
        
         // Not all instances of TagNode will have attributes.
         Properties attributes = this.allAV(false, true);
        
         sb.append(
             "CONTAINS a total of (" + attributes.size() + ") attributes / inner-tag " +
             "key-value pairs" + (attributes.size() == 0 ? "." : ":") + "\n"
         );
        
         // If there are inner-tags / attributes, then add them to the output-string, each on a
         // separate line.
        
         for (String key : attributes.stringPropertyNames())
             sb.append("(KEY, VALUE):\t[" + key + "], [" + attributes.get(key) + "]\n");
        
         // Build the string from the StringBuilder, and return it.
         return sb.toString();
        
      • clone

        🡅  🡇    
        public TagNode clone()
        Java's interface Cloneable requirements. This instantiates a new TagNode with identical String str fields, and also identical boolean isClosing and String tok fields.
        Specified by:
        clone in class HTMLNode
        Returns:
        A new TagNode whose internal fields are identical to this one.
        Code:
        Exact Method Body:
         return new TagNode(str);
        
      • compareTo

        🡅    
        public int compareTo​(TagNode n)
        This sorts by:

        1. by String 'tok' fields character-order (ASCII-alphabetical).
          The following final String 'tok' fields are ASCII ordered: 'a', 'button', 'canvas', 'div', 'em', 'figure' ...
        2. then (if 'tok' fields are equal) by the public final boolean 'isClosing' field.
          TagNode's that have a 'isClosing' set to FALSE come before TagNode's whose 'isClosing' field is set to TRUE
        3. finally, if the 'tok' and 'isClosing' fields are equal, they are sorted by the integer-length of final String 'str' field.
        Specified by:
        compareTo in interface java.lang.Comparable<TagNode>
        Parameters:
        n - Any other TagNode to be compared to 'this' TagNode
        Returns:
        An integer that fulfils Java's interface Comparable<T> public boolean compareTo(T t) method requirements.
        See Also:
        tok, isClosing, HTMLNode.str
        Code:
        Exact Method Body:
         // Utilize the standard "String.compare(String)" method with the '.tok' string field.
         // All 'tok' fields are stored as lower-case strings.
         int compare1 = this.tok.compareTo(n.tok);
        
         // Comparison #1 will be non-zero if the two TagNode's being compared had different
         // .tok fields
         if (compare1 != 0) return compare1;
        
         // If the '.tok' fields were the same, use the 'isClosing' field for comparison instead.
         // This comparison will only be used if they are different.
         if (this.isClosing != n.isClosing) return (this.isClosing == false) ? -1 : 1;
            
         // Finally try using the entire element '.str' String field, instead.  
         return this.str.length() - n.str.length();