001package Torello.HTML;
002
003import java.util.*;
004import java.util.regex.*;
005import java.util.stream.*;
006
007import java.util.function.Predicate;
008
009import Torello.Java.StringParse;
010import Torello.Java.StrCmpr;
011import Torello.Java.StrFilter;
012
013import Torello.HTML.parse.HTMLRegEx;
014import Torello.HTML.NodeSearch.CSSStrException;
015import Torello.HTML.NodeSearch.TextComparitor;
016
017import Torello.Java.Shell.C;
018
019/**
020 * <CODE>TagNode - Documentation.</CODE><BR /><BR />
021 * <EMBED CLASS="external-html" DATA-FILE-ID="TGND">
022 * <EMBED CLASS="external-html" DATA-FILE-ID="HTMLNODESUB">
023 * @see TextNode
024 * @see CommentNode
025 * @see HTMLNode
026 */
027public final class TagNode 
028    extends HTMLNode 
029    implements CharSequence, java.io.Serializable, Cloneable, Comparable<TagNode>
030{
031    /** <EMBED CLASS="external-html" DATA-FILE-ID="SVUID"> */
032    public static final long serialVersionUID = 1;
033
034    // ********************************************************************************************
035    // NON-STATIC FIELDS
036    // ********************************************************************************************
037
038    /** <EMBED CLASS="external-html" DATA-FILE-ID="TGNDTOK"> */
039    public final String tok;
040
041    /** <EMBED CLASS="external-html" DATA-FILE-ID="TGNDCLOSE"> */
042    public final boolean isClosing;
043
044    // ********************************************************************************************
045    // Constructors
046    // ********************************************************************************************
047
048    /**
049     * Creates a {@code TagNode}, an inherited class of {@code 'HTMLNode'} that can be used as an 
050     * element of an HTML {@code Vector}.
051     *
052     * <BR /><BR /><B>NOTE:</B> Attribute values are neither parsed, nor checked when this
053     * constructor is used.  This constructor could allow malformed HTML to be passed to the
054     * {@code public final String str} field!
055     * 
056     * <DIV CLASS="EXAMPLE">{@code
057     * TagNode tn = new TagNode("<DIV CLASS='SUMMARY' onmouseover=\"alert('Hello!');\">");
058     * System.out.println(tn.str);
059     * 
060     * // Prints to Terminal: <DIV CLASS='SUMMARY' onmouseover="alert('Hello!');">
061     * }</DIV>
062     *
063     * @param s Any valid HTML tag, for instance: {@code <H1>, <A HREF="somoe url">,
064     * 
065     * <DIV ID="some id">} etc...
066     * 
067     * @throws MalformedTagNodeException If the passed {@code String} wasn't valid - meaning <I>it
068     * did not match the regular-expression {@code parser}.</I> 
069     * 
070     * @throws HTMLTokException If the {@code String} found where the usual HTML token-element is
071     * situated <I>is not a valid HTML element</I> then the {@code HTMLTokException} will be
072     * thrown.
073     * 
074     * @see HTMLTags#getTag_MEM_HEAP_CHECKOUT_COPY(String)
075     */
076    public TagNode(String s)
077    {
078        super(s);
079
080        // If the second character of the string is a forward-slash, this must be a closing-element
081        // For Example: </SPAN>, </DIV>, </A>, etc...
082
083        isClosing = s.charAt(1) == '/';
084
085        // This is the Element & Attribute Matcher used by the RegEx Parser.  If this Matcher doesn't
086        // find a match, the parameter 's' cannot be a valid HTML Element.  NOTE: The results of this
087        // matcher are also used to retrieve attribute-values, but here below, its results are ignored.
088
089        Matcher m = HTMLRegEx.P1.matcher(s);
090
091        if (! m.find()) throw new MalformedTagNodeException(
092            "The parser's regular-expression did not match the constructor-string.\n" +
093            "The exact input-string was: [" + s + "]\n" +
094            "NOTE:  The parameter-string is included as a field (ex.str) to this Exception.", s
095        );
096
097        String tokTEMP = m.group(1).toLowerCase();
098            // MINOR/MAJOR IMPROVEMENT... REUSE THE "ALLOCATED STRING TOKEN" from the HTMLTag's class
099            // THINK: Let the Garbage Collector take out as many duplicate-strings as is possible... 
100            // AND SOONER.  DECEMBER 2019: "Optimization" or ... "Improvement"
101
102        if ((m.start() != 0) || (m.end() != s.length()))
103            throw new MalformedTagNodeException(
104                "The parser's regular-expression did not match the entire-string-length of the " +
105                "string-parameter to this constructor: m.start()=" + m.start() + ", m.end()=" + 
106                m.end() + ".\nHowever, the length of the Input-Parameter String was=" + s.length() +
107                "\nThe exact input-string was: [" + s + "]\nNOTE: The parameter-string is included " +
108                "as a field (ex.str) to this Exception.", s
109            );
110
111        this.tok = HTMLTags.getTag_MEM_HEAP_CHECKOUT_COPY(tokTEMP);
112            // Get a copy of the 'tok' string that was already allocated on the heap; (OPTIMIZATON)
113            // NOTE: There are already myriad strings for the '.str' field.
114            // ALSO: Don't pay much attention to this line if it doesn't make sense... it's not that
115            // important.  If the HTML Token found was not a valid HTML5 token, this field will be null.
116
117        // Now do the usual error check.
118        if (this.tok == null) throw new HTMLTokException(
119            "The HTML Tag / Token Element that is specified by the input string " +
120            "[" + tokTEMP + "] is not a valid HTML Element Name.\n" +
121            "The exact input-string was: [" + s + "]"
122        );
123    }
124
125    // USED-INTERNALLY - bypasses all checks.  used when creating new HTML Element-Names
126    // ONLY: class 'HTMLTags' via method 'addTag(...)' shall ever invoke this constructor.
127    // NOTE: This only became necessary because of the MEM_COPY_HEAP optimization.  This
128    //       optimization expects that there is already a TagNode with element 'tok' in
129    //       the TreeSet, which is always OK - except for the method that CREATES NEW HTML
130    //       TAGS... a.k.a. HTMLTags.addTag(String).
131    TagNode(String token, TC openOrClosed)
132    {
133        super("<" + ((openOrClosed == TC.ClosingTags) ? "/" : "") + token + ">");
134
135        // ONLY CHANGE CASE HERE, NOT IN PREVIOUS-LINE.  PAY ATTENTION.  
136        this.tok = token.toLowerCase();
137
138        this.isClosing = (openOrClosed == TC.ClosingTags) ? true : false;
139    }
140
141    /**
142     * This will build a new {@code TagNode} that contains the inner tag values specified here.  It
143     * is some-what checked for validity - though not all possible error cases are listed.  If a
144     * major issue is discovered an exception is thrown.
145     *
146     * <BR /><BR /><B><SPAN STYLE='color: red;'>ATTRIBUTES:</SPAN></B> This constructor accepts
147     * only <B STYLE="color: red;">key-value</B> attributes,  Boolean /
148     * <B STYLE="color: red;">key-only</B> attributes are <B>NOT accepted</B> by this constructor.
149     * 
150     * <DIV CLASS="EXAMPLE">{@code
151     * Properties attributes = new Properties();
152     * attributes.put("CLASS", "SUMMARY");
153     * attributes.put("onmouseover", "alert('Hello');");
154     * 
155     * TagNode tn = new TagNode("DIV", attributes, SD.DoubleQuotes, false);
156     * 
157     * System.out.println(tn);
158     * // Prints To Terminal: <DIV CLASS="SUMMARY" onmouseover="alert('Hello');">
159     * }</DIV>
160     *
161     * @param tok This may be any valid HTML Element name.  When this constructor is used,
162     * {@code 'tok'} will always be rendered to <I>{@code ASCII} lower-case</I>.  If this
163     * parameter does not contain a valid HTML element name, then an exception is thrown.
164     * 
165     * @param attributes This must be a table of HTML inner-tag
166     * <B STYLE="color: red;">key-values</B> <B>(<I>a.k.a.</I></B> HTML Attributes) that are
167     * acceptable for using with the HTML element that is being created.  Validity checking
168     * <I><B>includes *only*</B></I> the following tests:
169     * 
170     * <BR /><BR /><UL CLASS="JDUL">
171     * <LI> If any <B STYLE="color: red;">key</B> of {@code Properties} parameter {@code 'p'}
172     *      contains characters outside of this ASCII-subset: {@code [A..Za..z0..9_-]}
173     * </LI>
174     * <LI> If any <B STYLE="color: red;">key</B> of parameter {@code 'p'} does not start with
175     *      attributes a character from this subset: {@code [A..Za..z]}
176     * </LI>
177     * <LI> If any <B STYLE="color: red;">value</B> of {@code Properties} parameter {@code 'p'}
178     *      has a "quote within quote" problem -  a {@code 'value'} that contains quotation marks
179     *      that are the same as the quotation-parameter {@code SD quotes}
180     * </LI>
181     * </UL>
182     *
183     * <BR />If any of these requirements <B>fail</B>, the <B>exceptions</B> listed below
184     * will throw.
185     *
186     * <BR /><BR /><B>NOTE:</B> When specifying a {@code java.util.Properties} parameter for which
187     * <I>quotation-marks have already been added to the <B STYLE="color: red;">values</B></I>
188     * inside the table, <I>parameter {@code SD quote} must be set to null</I>.  In such cases, if
189     * {@code 'quote'} were not null, a second set of surrounding quotes would be appended each
190     * attribute-<B STYLE="color: red;">value</B> in the output HTML-Element - and this would
191     * likely force a {@code QuotesException} to throw, due to the <I>"quotes within quotes"</I>
192     * issue.
193     *
194     * <BR /><BR /><B>NOTE:</B> The {@code 'attributes'} parameter may be null, and if so, it will
195     * be ignored.  In this case, no <B STYLE="color: red;">key-value</B> attributes will be
196     * incorporated into the {@code TagNode}.
197     *
198     * @param quotes This is either a single-quote {@code (')} or a double-quote {@code (")}.  The
199     * {@code 'quotes'} parameter is an instance of the {@code Enumerated-Type: SD} from this
200     * package.
201     *
202     * <BR /><BR /><B>NOTE ABOUT {@code 'SD'}:</B> This parameter (quotes) may be null.  If 'null'
203     * is passed, then it should be expected that the contents of the {@code Properties p}
204     * parameter contains <B STYLE="color: red;">values</B> that obey these rules:
205     *
206     * <BR /><BR /><UL CLASS="JDUL">
207     * <LI> <B>Either:</B> The <B STYLE="color: red;">values</B> in {@code Properties}
208     *      parameter {@code 'p'} <I>already have quotes surrounding their {@code String}
209     *      contents.</I>
210     * </LI>
211     * <LI> <B>Or:</B> The <B STYLE="color: red;">values</B> in {@code 'p'} do not contain
212     *      any white-space.  HTML rules state that in such cases, say quotes are actually
213     *      optional.
214     * </LI>
215     * </UL>
216     *
217     * @param addEndingForwardSlash There are a few (very few) instances where an "ending
218     * forward-slash" {@code ('/')} is expected at the end of the HTML-Tag.  If this is desired,
219     * set this value to <B>true</B>.
220     *
221     * @throws InnerTagKeyException <EMBED CLASS="external-html" DATA-FILE-ID="ITKEYEXPROP">
222     * 
223     * @throws QuotesException If there are "quotes within quotes" problems, due to the
224     * <B STYLE="color: red;">values</B> of the <B STYLE="color: red;">key-value</B> pairs inside
225     * the input {@code Properties} hash-table, this exception will throw.  This also throws if the
226     * the {@code 'quotes'} parameter is passed 'null', and any of the
227     * property-<B STYLE="color: red;">values</B> in the table contain white-space.
228     * 
229     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present
230     * <B>(check is {@code CASE_INSENSITIVE})</B>, this exception throws.
231     * 
232     * @see #tok
233     * @see InnerTagKeyException#check(String, String)
234     * @see QuotesException#check(String, SD, String)
235     * @see #generateElementString(String, Properties, Iterable, SD, boolean)
236     */
237    public TagNode(String tok, Properties attributes, SD quotes, boolean addEndingForwardSlash) 
238    { 
239        this(generateElementString(
240                tok, attributes, null /* keyOnlyAttributes */, quotes, addEndingForwardSlash
241        ));
242    }
243
244    /**
245     * This will build a new {@code TagNode} that contains the inner tag attributes specified here.
246     * It is some-what checked for validity - though not all possible error cases are listed.  If a
247     * major issue is discovered an exception is thrown.
248     *
249     * <DIV CLASS="EXAMPLE">{@code
250     * Properties attributes = new Properties();
251     * attributes.put("CLASS", "SUMMARY");
252     * attributes.put("onmouseover", "alert('Hello');");
253     *
254     * Vector<String> booleanAttributes = new Vector<>();
255     * booleanAttributes.add("HIDDEN");
256     * 
257     * TagNode tn = new TagNode("DIV", attributes, booleanAttributes, SD.DoubleQuotes, false);
258     * 
259     * System.out.println(tn);
260     * // Prints To Terminal: <DIV CLASS="SUMMARY" onmouseover="alert('Hello');" HIDDEN>
261     * }</DIV>
262     * <BR /><BR /><B><SPAN STYLE='color: red;'>ATTRIBUTES:</SPAN></B> This constructor accepts
263     * both <B STYLE="color: red;">key-value</B> attributes, and boolean /
264     * <B STYLE="color: red;">key-only</B> attributes as input.  These two lists are passed through
265     * two different input-parameters.
266     *
267     * @param tok This may be any valid HTML Element name.    Valid {@code String's} that this
268     * parameter can accept include {@code 'div', 'img', 'span', 'a',} etc...  If this parameter
269     * does not contain a valid HTML element name, then an exception is thrown.
270     * 
271     * @param attributes This must be a table of HTML inner-tag
272     * <B STYLE="color: red;">key-values</B> <B>(<I>a.k.a.</I></B> HTML Attributes) that are
273     * acceptable for using with the HTML element that is being created.  Validity checking
274     * <I><B>includes *only*</B></I> the following tests:
275     * 
276     * <BR /><BR /><UL CLASS="JDUL">
277     * <LI> If any <B STYLE="color: red;">key</B> of {@code 'Properties'} parameter
278     *      {@code 'attributes'} contains characters outside of this ASCII-subset:
279     *      {@code [A..Za..z0..9_-]}
280     * </LI>
281     * <LI> If any <B STYLE="color: red;">key</B> of {@code 'attributes'} does not start with a
282     *      character from this subset: {@code [A..Za..z]}
283     * </LI>
284     * <LI> If any <B STYLE="color: red;">value</B> of {@code 'attributes'} has a "quote within
285     *      quote" problem - a {@code 'value'} that contains quotation marks that are the same as
286     *      the quotation parameter {@code SD quotes}
287     * </LI>
288     * </UL>
289     * 
290     * <BR />If any of these requirements <B>fail</B>, the <B>exceptions</B> listed below
291     * will throw.
292     *
293     * <BR /><BR /><B>NOTE:</B> When specifying a {@code java.util.Properties} parameter for which
294     * <I>quotation-marks have already been added to the <B STYLE="color: red;">values</B></I>
295     * inside the table, <I>parameter {@code SD quote} must be set to null</I>.  In such cases, if
296     * {@code 'quote'} were not null, a second set of surrounding quotes would be appended each
297     * attribute-<B STYLE="color: red;">value</B> in the output HTML-Element - and this would
298     * likely force a {@code QuotesException} to throw, due to the <I>"quotes within quotes"</I>
299     * issue.
300     *
301     * <BR /><BR /><B>NOTE:</B> The {@code 'attributes'} parameter may be null, and if so, it will
302     * be ignored.  In this case, no <B STYLE="color: red;">key-value</B> attributes will be
303     * incorporated into the {@code TagNode}.
304     *
305     * @param keyOnlyAttributes This should be a valid list of "Attribute-Only" Inner-Tags.
306     * Such attributes are often called "Boolean Attributes."  They are just a stand-alone
307     * keywords, without any <B STYLE="color: red;">value</B> assignment.  The CSS keyword
308     * {@code 'HIDDEN'} is a commonly used Boolean-Attribute.
309     *
310     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if so, no boolean-attributes will
311     * be included in the HTML Element.
312     *
313     * @param quotes This is either a single-quote {@code (')} or a double-quote {@code (")}.  It
314     * uses the {@code Enumerated Type: 'SD'} from this package.
315     *
316     * <BR /><BR /><B>NOTE ABOUT {@code 'SD'}:</B> This parameter (quotes) may be null.  If 'null'
317     * is passed, then it should be expected that the contents of the {@code Properties p}
318     * parameter contains <B STYLE="color: red;">values</B> that obey these rules:
319     *
320     * <BR /><BR /><UL CLASS="JDUL">
321     * <LI> <B>Either:</B> The <B STYLE="color: red;">values</B> in {@code Properties}
322     *      parameter {@code 'p'} <I>already have quotes surrounding their {@code String}
323     *      contents.</I>
324     * </LI>
325     * <LI> <B>Or:</B> The <B STYLE="color: red;">values</B> in {@code 'p'} do not contain
326     *      any white-space.  HTML rules state that in such cases, say quotes are actually
327     *      optional.
328     * </LI>
329     * </UL>
330     *
331     * @param addEndingForwardSlash There are a few (very few) instances where an "ending
332     * forward-slash" is expected at the end of the HTML -Tag.  If this is desired, set this value
333     * to <B>TRUE</B>.
334     * 
335     * @throws InnerTagKeyException <EMBED CLASS="external-html" DATA-FILE-ID="ITKEYEXPROP">
336     * 
337     * @throws QuotesException <EMBED CLASS="external-html" DATA-FILE-ID="QEX">
338     * 
339     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present
340     * <B>(check is {@code CASE_INSENSITIVE})</B>
341     * 
342     * @see InnerTagKeyException#check(String, String)
343     * @see QuotesException#check(String, SD, String)
344     * @see #generateElementString(String, Properties, Iterable, SD, boolean)
345     */
346    public TagNode(
347        String tok, Properties attributes, Iterable<String> keyOnlyAttributes,
348        SD quotes, boolean addEndingForwardSlash
349    )
350    {
351        this(generateElementString(
352            tok, attributes, keyOnlyAttributes, quotes, addEndingForwardSlash
353        ));
354    }
355
356    /**
357     * This builds an HTML Element as a {@code String.}  This {@code String} may be passed to the
358     * standard HTML {@code TagNode} Constructor that accepts a {@code String} as input.
359     * 
360     * @param tok This is the HTML Element name.  Valid {@code String's} that this parameter can
361     * accept include {@code 'div', 'img', 'span', 'a',} etc...  If this parameter does not contain
362     * a valid HTML element name, then an exception is thrown.
363     *
364     * @param p This is a {@code java.util.Properties} table of HTML Attribute 
365     * <B STYLE="color: red;">Key-Value</B> Pairs.
366     * 
367     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if so, no attribute-values will be
368     * included in the {@code TagNode}.
369     *
370     * @param keyOnlyAttributes This is a list of keyword attributes that do not have 
371     * <B STYLE="color: red;">value</B> assignments, and are to be inserted into the HTML Element.
372     * This parameter could also be named 'Boolean Attributes.'
373     * 
374     * <BR /><BR /><B>NOTE:</B> This parameter may be null, and if so, no boolean-attributes will
375     * be included in the {@code TagNode}.
376     *
377     * @param quotesChoice These quotes are used to encapsulate the
378     * <B STYLE="color: red;">value</B> {@code String} of all key-value pairs when building the
379     * HTML Element.  This parameter may be null, and if it is - then no quotes will be added to
380     * the <B STYLE="color: red;">value</B> {@code String's} that are inserted
381     * 
382     * <BR /><BR /><B><SPAN STYLE='color: red;'>IMPORTANT:</B></SPAN> If different
383     * quotes-selections are to be used for different attribute key-value pairs, then those quotes
384     * should be provided <I><B>inside the {@code Properties}</I></B> data-structure - already
385     * pre-wrapped. When individualized quotes are needed, this parameter should be passed null.
386     *
387     * @param addEndingForwardSlash When this receives <B>TRUE</B>, an ending {@code '/'} forward
388     * slash is appended to the second-to-last character of the output {@code String.}
389     *
390     * @throws InnerTagKeyException <EMBED CLASS="external-html" DATA-FILE-ID="ITKEYEXPROP">
391     * 
392     * @throws QuotesException <EMBED CLASS="external-html" DATA-FILE-ID="QEX">
393     * 
394     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present
395     * <B>{@code CASE_INSENSITIVE}</B>
396     * 
397     * @return This method returns an HTML Element, as a {@code String.}
398     * 
399     * @see HTMLTokException#check(String[])
400     * @see InnerTagKeyException#check(String, String)
401     * @see QuotesException#check(String, SD, String)
402     */
403    protected static String generateElementString(
404        String tok, Properties p, Iterable<String> keyOnlyAttributes,
405        SD quotesChoice, boolean addEndingForwardSlash
406    )
407    {
408        String computedQuote = (quotesChoice == null) ? "" : ("" + quotesChoice.quote);
409        HTMLTokException.check(tok);
410
411        // The HTML Element is "built" using a StringBuilder
412        StringBuilder sb = new StringBuilder();
413        sb.append("<" + tok);
414
415        // If there are any Inner-Tag Key-Value pairs, insert them first.
416        if ((p != null) && (p.size() > 0))
417            for (String key : p.stringPropertyNames())
418            {
419                String value = p.getProperty(key);
420
421                InnerTagKeyException.check(key, value);
422
423                QuotesException.check(
424                    value, quotesChoice,
425                    "parameter 'Properties' contains:\nkey:\t" + key + "\nvalue:\t" + value + "\n"
426                );
427
428                sb.append(" " + key + '=' + computedQuote + value + computedQuote);
429            }
430
431        // If there are any Key-Only Inner-Tags (Boolean Attributes), insert them next.
432        if (keyOnlyAttributes != null)
433            for (String keyOnlyAttribute : keyOnlyAttributes) 
434            {
435                InnerTagKeyException.check(keyOnlyAttribute);
436                sb.append(" " + keyOnlyAttribute);
437            }
438
439        // Add a closing forward-slash
440        sb.append(addEndingForwardSlash ? " />" : ">");
441
442        // Build the String, using the StringBuilder, and return the newly-constructed HTML Element.
443        return sb.toString();
444    }
445
446    // ********************************************************************************************
447    // isTag
448    // ********************************************************************************************
449
450    /**
451     * This method identifies that {@code 'this'} instance of {@code 'HTMLNode'} is, indeed, 
452     * actually an instance of the (sub-class) {@code TagNode}.
453     *
454     * @return This method shall always return <B>TRUE</B>  It overrides the parent-class
455     * {@code HTMLNode} method {@link #isTagNode()}, which always returns <B>FALSE</B>.
456     */
457    @Override
458    public boolean isTagNode() { return true; }
459
460    /**
461     * Receives a list of html-elements which the {@code this.tok} field must match.  
462     * This method returns <B>TRUE</B> if any match is found.
463     * 
464     * <BR /><BR /><IMG SRC='isTag.png' CLASS=JDIMG ALT='example'>
465     * 
466     * @param possibleTags This non-null list of potential HTML tags.
467     * 
468     * @return <B>TRUE</B> If {@code this.tok} matches at least one of these tags.
469     * 
470     * @see #tok
471     */
472    public boolean isTag(String... possibleTags)
473    { 
474        for (String htmlTag : possibleTags)
475            if (htmlTag.equalsIgnoreCase(this.tok))
476                return true;
477        
478        return false;
479    }
480
481    /**
482     * Receives a list of html-elements which {@code this.tok} field <B>MAY NOT</B> match.
483     * This method returns <B>FALSE</B> if any match is found.
484     * 
485     * @param possibleTags This must be a non-null list of potential HTML tags.
486     * 
487     * @return <B>FALSE</B> If {@code this.tok} matches any one of these tags, and <B>TRUE</B>
488     * otherwise.
489     * 
490     * @see #tok
491     * @see #isTag(String[])
492     */
493    public boolean isTagExcept(String... possibleTags)
494    { 
495        for (String htmlTag : possibleTags)
496            if (htmlTag.equalsIgnoreCase(this.tok))
497                return false;
498        
499        return true;
500    }
501
502    /**
503     * Receives two "criteria-specifier" parameters.  This method shall return <B>TRUE</B> if:
504     *
505     * <BR /><BR /><UL CLASS="JDUL">
506     * <LI>Field {@code 'isClosing'} is equal-to / consistent-with {@code TC tagCriteria}</LI>
507     * <LI>Field {@code 'tok'} is equal to at least one of the {@code 'possibleTags'}</LI>
508     * </UL>
509     * 
510     * <BR /><BR /><IMG SRC='isTag2.png' CLASS=JDIMG ALT='example'>
511     * 
512     * @param tagCriteria This ought to be either {@code 'TC.OpeningTags'} or
513     * {@code TC.ClosingTags'}.  This parameter specifies what {@code 'this'} instance of
514     * {@code TagNode} is expected to contain, as {@code this.isClosing} field shall be compared
515     * against it.
516     * 
517     * @param possibleTags This is presumed to be a non-zero-length, and non-null-valued list of
518     * html tokens.
519     * 
520     * @return <B>TRUE</B> If {@code 'this'} matches the specified criteria, and <B>FALSE</B> 
521     * otherwise.
522     * 
523     * @see TC
524     * @see #isClosing
525     * @see #tok
526     */
527    public boolean isTag(TC tagCriteria, String... possibleTags)
528    {
529        // Requested an "OpeningTag" but this is a "ClosingTag"
530        if ((tagCriteria == TC.OpeningTags) && this.isClosing)
531            return false;
532
533        // Requested a "ClosingTag" but this is an "OpeningTag"
534        if ((tagCriteria == TC.ClosingTags) && ! this.isClosing)
535            return false;
536
537        for (int i=0; i < possibleTags.length; i++)
538            if (this.tok.equalsIgnoreCase(possibleTags[i]))
539                return true;
540                        // Found a TOKEN match, return TRUE immediately
541
542        return false;   // None of the elements in 'possibleTags' equalled tn.tok
543    }
544
545    /**
546     * Receives a {@code TagNode} and then two "criteria-specifier" parameters.  This method shall
547     * return <B>FALSE</B> if:
548     * 
549     * <BR /><BR /><UL CLASS="JDUL">
550     * <LI> Field {@code 'isClosing'} is <B><I>not</I></B> equal-to / 
551     *      <B><I>not</I></B> consistent-with {@code TC tagCriteria}</LI>
552     * <LI> Field {@code 'tok'} is <B><I>equal-to</I></B> any of the {@code 'possibleTags'}</LI>
553     * </UL>
554     *
555     * @param tagCriteria tagCriteria This ought to be either {@code 'TC.OpeningTags'} or
556     * {@code TC.ClosingTags'} This parameter specifies what {@code 'this'} instance of
557     * {@code TagNode} is expected to contain, as {@code this.isClosing} field shall be compared
558     * against it.
559     * 
560     * @param possibleTags This is presumed to be a non-zero-length, and non-null-valued list of
561     * html tokens.
562     * 
563     * @return <B>TRUE</B> If this {@code TagNode 'n'} matches the specified criteria explained
564     * above, and <B>FALSE</B> otherwise.
565     * 
566     * @see TC
567     * @see #tok
568     * @see #isClosing
569     */
570    public boolean isTagExcept(TC tagCriteria, String... possibleTags)
571    {
572        // Requested an "OpeningTag" but this is a "ClosingTag"
573        if ((tagCriteria == TC.OpeningTags) && this.isClosing)
574            return false;
575
576        // Requested a "ClosingTag" but this is an "OpeningTag"
577        if ((tagCriteria == TC.ClosingTags) && ! this.isClosing)
578            return false;
579
580        for (int i=0; i < possibleTags.length; i++)
581            if (this.tok.equalsIgnoreCase(possibleTags[i]))
582                return false;
583                        // The Token of the input node was a match with one of the 'possibleTags'
584                        // Since this is "Except" - we must return 'false'
585
586        return true;    // None of the elements in 'possibleTags' equalled tn.tok
587                        // since this is "Except" - return 'true'
588    }
589
590
591    // ********************************************************************************************
592    // Main Method 'AV'
593    // ********************************************************************************************
594
595    /**
596     * The letters: <B>{@code AV}</B> simply mean <B>"Attribute Value"</B>.  In this HTML scrape
597     * &amp; search package, the words <I><B>attribute</B></I> and <I><B>inner-tag</B></I> are used
598     * synonymously.
599     * 
600     * <BR /><BR />This will return the <B STYLE="color: red;">value</B> of any "Inner Tag" inside
601     * the HTML {@code TagNode}.  An inner-tag is the choice of wording used in this scrape package
602     * - partially for brevity since "tag" is usually interchangeable with "inner tag."  Often
603     * HTML coders refer to this particular data as an <B>HTML Element Attribute</B> (or more
604     * simply, just <B>"Attribute"</B>).
605     * 
606     * <BR /><BR /><IMG SRC="AV.png" CLASS=JDIMG ALT="example">
607     * 
608     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDCSSAV">
609     * 
610     * @param innerTagAttribute  This may be any Java-{@code String}, but very common examples
611     * of HTML attributes (and their <B STYLE="color: red;">values</B>) include:
612     * 
613     * <BR /><BR />
614     * 
615     * <TABLE CLASS="BRIEFSAMPLETABLE"><TBODY>
616     * <TR> <TH>Attribute / Inner-Tag</TH>
617     *      <TH>Commonly Found Attribute-Values</TH>
618     * </TR>
619     * <TR> <TD>HREF="..."</TD>
620     *      <TD>where the attribute <B STYLE="color: red;">value</B> ("...") - is a URL</TD>
621     * </TR>
622     * <TR> <TD>SRC='...'</TD>
623     *      <TD>and the attribute <B STYLE="color: red;">value</B> specified
624     *          ('...') - is usually an Image-URL (like a "pic.jpg")</TD>
625     * </TR>
626     * <TR> <TD>ID=...</TD>
627     *      <TD>where the attribute <B STYLE="color: red;">value</B> (...) -
628     *          would be a "CSS Identifier Tag"</TD>
629     * </TR>
630     * <TR> <TD>CLASS='...'</TD>
631     *      <TD>and the attribute <B STYLE="color: red;">value</B> ('...') -
632     *          is the "CSS Class" to which the particular HTML element belongs</TD>
633     * </TR>
634     * <TR> <TD>OnClick="..."</TD>
635     *      <TD>and the attribute <B STYLE="color: red;">value</B> ("...")
636     *          - is often a function call to a Java-Script module, or actual Java-Script</TD>
637     * </TR>
638     * <TR> <TD>href="..."</TD>
639     *      <TD><I>SAME AS ABOVE!</I> - Remember an "inner-tag" or
640     *          "attribute" <B STYLE="color: red;">name</B> is <I>{@code CASE-INSENSITIVE}</I></TD>
641     * </TR>
642     * <TR> <TD>src='...'</TD>
643     *      <TD><I>SAME AS ABOVE!</I> - Remember an "inner-tag" or "attribute"
644     *      <B STYLE="color: red;">name</B> is <I>{@code CASE-INSENSITIVE}</I></TD>
645     * </TR>
646     * </TBODY></TABLE> 
647     * 
648     * @return The <B>"Attribute </B><B STYLE="color: red;">Value"</B>, which for the inner-tag
649     * named by the input {@code String}-parameter.
650     *
651     * <BR /><BR /><B><SPAN STYLE="color: red;">NOTE:</SPAN></B> If {@code 'this' TagNode} is a
652     * closing-tag (specifically, if the {@code 'isClosing'} boolean-field is true), this method
653     * will exit immediately, and return null.  Unlike the other Attribute-Modification Methods in
654     * this class, no {@code ClosingTagNodeException} shall throw, but rather the method will exit
655     * gracefully. This is because this method is a 'getter' only.  No invalid data will be
656     * instantiated or saved - even if this method executes to completion.  Note, though, valid
657     * HTML pages do not allow attributes inside of closing HTML Elements.
658     *
659     * <BR /><BR /><B><SPAN STYLE="color: red;">ALSO:</SPAN></B> If the {@code 'str'}
660     * {@code String}-field of {@code 'this' TagNode} has a length that isn't greater than the
661     * following: {@code 3 + tok.length() + innerTagAttribute.trim().length())}, then in this case
662     * this {@code AV} method will also return {@code null}.  The rational for returning null here
663     * is that the {@code final String str} field simply does not have enough characters to contain
664     * this inner-tag.
665     *
666     * @see #isClosing
667     * @see #str
668     * @see #tok
669     * @see StringParse#ifQuotesStripQuotes(String)
670     * @see AttrRegEx#KEY_VALUE_REGEX
671     */
672    public String AV(String innerTagAttribute)
673    {
674        // All HTML element tags that start like: </DIV> with a front-slash.
675        // They may not legally contain inner-tag attributes.
676
677        if (this.isClosing) return null;    
678
679        // All HTML element tags that contain only <TOK> (TOK <==> Element-Name) in their .str field...
680        // Specifically: '<', TOKEN, '>',  (Where TOKEN is 'div', 'span', 'table', 'ul', etc...)
681        // are TOO SHORT to have the attribute, so don't check... return null.
682
683        if (this.str.length() < 
684            (3 + this.tok.length() + (innerTagAttribute = innerTagAttribute.trim()).length()))
685            return null;
686
687        // Matches "Attribute / Inner-Tag Key-Value" Pairs.
688        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
689
690        // This loop iterates the KEY_VALUE PAIRS THAT HAVE BEEN FOUND.
691        /// NOTE: The REGEX Matches on Key-Value Pairs.
692        while (m.find())
693
694            // m.group(2) is the "KEY" of the Attribute KEY-VALUE Pair
695            // m.group(3) is the "VALUE" of the Attribute.
696            if (m.group(2).equalsIgnoreCase(innerTagAttribute))
697                return StringParse.ifQuotesStripQuotes(m.group(3));
698
699        // This means the attribute name provided to parameter 'innerTagAttribute' was not found.
700        return null;
701    }
702
703    /**
704     * <SPAN STYLE="color: red;"><B>OPT: Optimized</B></SPAN>
705     * 
706     * <BR /><BR /> This is an "optimized" version of method {@link #AV(String)}.  This method does
707     * the exact same thing as {@code AV(...)}, but leaves out parameter-checking and
708     * error-checking. This is used internally (repeatedly) by the NodeSearch Package Search Loops.
709     * 
710     * @param innerTagAttribute This is the inner-tag / attribute <B STYLE="color: red;">name</B>
711     * whose <B STYLE="color: red;">value</B> is hereby being requested.
712     * 
713     * @return {@code String}-<B STYLE="color: red;">value</B> of this inner-tag / attribute.
714     * 
715     * @see StringParse#ifQuotesStripQuotes(String)
716     * @see #str
717     * @see TagNode.AttrRegEx#KEY_VALUE_REGEX
718     */
719    public String AVOPT(String innerTagAttribute)
720    {
721        // COPIED DIRECTLY FROM class TagNode, leaves off initial tests.
722
723        // Matches "Attribute / Inner-Tag Key-Value" Pairs.
724        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
725
726        // This loop iterates the KEY_VALUE PAIRS THAT HAVE BEEN FOUND.
727        /// NOTE: The REGEX Matches on Key-Value Pairs.
728        while (m.find())
729
730            // m.group(2) is the "KEY" of the Attribute KEY-VALUE Pair
731            // m.group(3) is the "VALUE" of the Attribute.
732            if (m.group(2).equalsIgnoreCase(innerTagAttribute))
733                return StringParse.ifQuotesStripQuotes(m.group(3));
734
735        // This means the attribute name provided to parameter 'innerTagAttribute' was not found.
736        return null;
737    }
738
739    // ********************************************************************************************
740    // Attribute Modify-Value methods
741    // ********************************************************************************************
742
743    /**
744     * This function will instantiate a new {@code TagNode} which contains this newly added 
745     * <B STYLE="color: red;">attribute-value</B> pair. It uses the constructor listed above, and
746     * furthermore does some error-handling checks.  It will throw an exception if the inner-tag /
747     * value pairs do not pass inspection on quotes-error cases, or contain invalid characters.
748     *
749     * <BR /><BR /><IMG SRC="setAV.png" CLASS=JDIMG ALT="example">
750     * 
751     * @param attribute Any valid HTML attribute-<B STYLE="color: red;">name</B>.  This parameter
752     * may not be null, or a {@code NullPointerException} will throw.
753     *
754     * <BR /><BR /><B>NOTE:</B> If the attribute that is specified is already contained within
755     * this tag (where a {@code CASE-INSENSITIVE} comparison to the inner-tag's returned by
756     * {@code public Properties allAV()} gets a match), then the original attribute is simply
757     * over-written.  A Duplicate HTML-Element attribute <B><I>will not be added.</I></B>
758     *
759     * @param value Any valid attribute-<B STYLE="color: red;">value</B>.  This parameter may not
760     * be null, or a {@code NullPointerException} will throw.
761     *
762     * @param quote This is either a single-quote, double-quote, or null. 
763     * 
764     * <BR /><BR /><UL CLASS="JDUL">
765     * <LI> When parameter {@code 'quote'} is {@code SD.SingleQuotes}, a single-quote is
766     *      prepended and appended to the beginning and ending (respectively) of the
767     *      <B STYLE="color: red;"> {@code value}</B> parameter before inserting or replacing the
768     *      inner-tag of this HTML ({@code TagNode}) Element.
769     * </LI>
770     * <LI> When parameter {@code 'quote'} is {@code SD.DoubleQuotes}, a double-quote is added
771     *      to the beginning and ending of the <B STYLE="color: red;">{@code value}</B>-parameter
772     *      before inserting (or re-inserting, if this attribute as already present).
773     * </LI>
774     * <LI> When {@code 'quote'} is null, there are two alternative results, depending on the
775     *      {@code TagNode:}
776     *      <BR /><BR /><OL CLASS="JDOL">
777     *      <LI>If the {@code TagNode} already has an inner-tag <B STYLE="color: red;">name</B>
778     *          that is equal ({@code CASE_INSENSITIVE}) to the <B STYLE="color: red;">
779     *          {@code 'key'}</B> parameter, then the original {@code quote} found in
780     *          {@code 'this'} Element is used.
781     *      </LI>
782     *      <LI>If a new attribute, not already found in {@code 'this' TagNode} is being 
783     *          inserted, and parameter {@code 'quote'} is null, then no quotes will be used at all
784     *          - which is a scenario sometimes found in HTML documents.  In this case, the
785     *          <B STYLE="color: red;">key-value</B> inner-tag will simply contain the
786     *          {@code String <HTML-ELEMENT ... key=value ...>} without any quotes present, at all.
787     *      </LI>
788     *      </OL>
789     * </LI>
790     * </UL>
791     *
792     * @throws InnerTagKeyException <EMBED CLASS="external-html" DATA-FILE-ID="ITKEYEX2">
793     * 
794     * @throws QuotesException <EMBED CLASS="external-html" DATA-FILE-ID="QEX">
795     * 
796     * @throws HTMLTokException If an invalid HTML 4 or 5 token is not present 
797     * <B>{@code CASE_INSENSITIVE}</B>
798     * 
799     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
800     * 
801     * @return An HTML {@code TagNode} instance with updated attribute information.
802     *
803     * <BR /><BR /><B><SPAN STYLE="color: red;">IMPORTANT:</SPAN></B> Because <I>{@code TagNode's}
804     * are immutable</I> (since they are just wrapped-java-{@code Strings}, which are also
805     * immutable), it is important to remember that this method <I><B>does not change the
806     * contents</B></I> of a {@code TagNode}, but rather <I><B>returns an entirely new
807     * {@code TagNode}</I></B> instance as a result instead.
808     *
809     * @see ClosingTagNodeException#check(TagNode)
810     * @see #generateElementString(String, Properties, Iterable, SD, boolean)
811     * @see #setAV(Properties, SD)
812     * @see #tok
813     * @see #str
814     * @see #isClosing
815     */
816    public TagNode setAV(String attribute, String value, SD quote)
817    {
818        ClosingTagNodeException.check(this);
819
820        if (attribute == null) throw new NullPointerException(
821            "You have passed 'null' to the 'attribute' (attribute-name) String-parameter, " +
822            "but this is not allowed here."
823        );
824
825        if (value == null) throw new NullPointerException(
826            "You have passed 'null' to the 'attribute' (attribute-value) String-parameter, " +
827            "but this is not allowed here."
828        );
829
830        // Retrieve all "Key-Only" (Boolean) Attributes from 'this' (the original) TagNode
831        // Use Java Streams to filter out any that match the newly-added attribute key-value pair.
832        // SAVE: Save the updated / shortened list to a List<String>
833
834        List<String> prunedOriginalKeyOnlyAttributes = allKeyOnlyAttributes(true)
835            .filter((String originalKeyOnlyAttribute) -> 
836                ! originalKeyOnlyAttribute.equalsIgnoreCase(attribute))
837            .collect(Collectors.toList());
838
839        // Retrieve all Inner-Tag Key-Value Pairs.  Preserve the Case of the Attributes.  Preserve
840        // the Quotation-Marks.
841
842        Properties  p                       = allAV(true, true);
843        String      originalValueWithQuotes = null;
844        String      computedQuote           = null;
845
846        // NOTE, there should only be ONE instance of an attribute in an HTML element, but malformed
847        // HTML happens all the time, so to keep this method safe, it checks (and removes) the entire
848        // attribute-list for matches - not just the first found instance.
849
850        for (String key : p.stringPropertyNames())
851            if (key.equalsIgnoreCase(attribute))
852            {
853                Object temp = p.remove(key);
854                if (temp instanceof String) originalValueWithQuotes = (String) temp;
855            }
856
857        // If the user does not wish to "change" the original quote choice, then find out what
858        // the original-quote choice was...
859
860        if (    (quote == null) 
861            &&  (originalValueWithQuotes != null)
862            &&  (originalValueWithQuotes.length() >= 2)
863        )
864        {
865            char s = originalValueWithQuotes.charAt(0);
866            char e = originalValueWithQuotes.charAt(originalValueWithQuotes.length() - 1);
867            if ((s == e) && (s == '\''))        computedQuote = "" + SD.SingleQuotes.quote;
868            else if ((s == e) && (s == '"'))    computedQuote = "" + SD.DoubleQuotes.quote;
869            else                                computedQuote = "";
870        }
871        else if (quote == null)                 computedQuote = "";
872        else                                    computedQuote = "" + quote.quote;
873
874        p.put(attribute, computedQuote + value + computedQuote);
875
876        return new TagNode(generateElementString(
877            // Rather than using '.tok' here, preserve the case of the original HTML Element
878            this.str.substring(1, 1 + tok.length()), p,
879            prunedOriginalKeyOnlyAttributes, null /* SD */, this.str.endsWith("/>")
880        ));
881    }
882
883    /**
884     * This allows for inserting or updating multiple {@code TagNode} inner-tag
885     * <B STYLE="color: red;">key-value</B> pairs with a single method invocation.
886     * 
887     * @param attributes These are the new attribute <B STYLE="color: red;">key-value</B> pairs to
888     * be inserted.
889     * 
890     * @param defaultQuote This is the default quotation mark to use, if the {@code 'attribute'}
891     * themselves do not already have quotations.
892     *
893     * <BR /><BR /><B><SPAN STYLE='color: red;'>IMPORTANT:</B></SPAN> If this value is used, then
894     * none of the provided {@code Property}-<B STYLE="color: red;">values</B> of the input
895     * {@code java.lang.Properties} instance should have quotes already.  Each of these 
896     * new-<B STYLE="color: red;">values</B> will be wrapped in the quote that is provided as the
897     * value to this parameter.
898     *
899     * <BR /><BR /><B><SPAN STYLE='color: red;'>HOWEVER:</B></SPAN> If this parameter is passed a
900     * value of 'null', then no quotes will be added to the new <B STYLE="color: red;">keys</B> -
901     * <I>unless the attribute being inserted is replacing a previous attribute that was already
902     * present in the element.</I>  In this case, the original quotation shall be used.  If this
903     * parameter receives 'null' and any of the new {@code Properties} were not already present in
904     * the original ({@code 'this'}) element, then no quotation marks will be used, which may
905     * throw a {@code QuotesException} if the attribute <B STYLE="color: red;">value</B> contains
906     * any white-space.
907     *
908     * @throws InnerTagKeyException <EMBED CLASS="external-html" DATA-FILE-ID="ITKEYEXPROP">
909     * 
910     * @throws QuotesException if there are "quotes within quotes" problems, due to the
911     * <B STYLE="color: red;">values</B> of the <B STYLE="color: red;">key-value</B> pairs.
912     * 
913     * @throws HTMLTokException if an invalid HTML 4 or 5 token is not present 
914     * <B>({@code CASE_INSENSITIVE})</B>
915     * 
916     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
917     *
918     * @return An HTML {@code TagNode} instance with updated {@code TagNode} information.
919     *
920     * <BR /><BR /><B><SPAN STYLE="color: red;">IMPORTANT:</SPAN></B> Because 
921     * <I>{@code TagNode's} are immutable</I> (since they are just wrapped-java-{@code String's},
922     * which are also immutable), it is important to remember that this method <I><B>does not
923     * change the contents</B></I> of a {@code TagNode}, but rather <I><B>returns an entirely
924     * new {@code TagNode}</I></B> as a result instead.
925     *
926     * @see ClosingTagNodeException#check(TagNode)
927     * @see #setAV(String, String, SD)
928     * @see #allKeyOnlyAttributes(boolean)
929     * @see #tok
930     * @see #str
931     * @see #isClosing
932     */
933    public TagNode setAV(Properties attributes, SD defaultQuote)
934    {
935        ClosingTagNodeException.check(this);
936
937        // Check that this attributes has elements.
938        if (attributes.size() == 0) throw new IllegalArgumentException(
939            "You have passed an empty java.util.Properties instance to the " +
940            "setAV(Properties, SD) method"
941        );
942
943        // Retrieve all Inner-Tag Key-Value Pairs.
944        //      Preserve: the Case of the Attributes.
945        //      Preserve: the Quotation-Marks.
946        Properties originalAttributes = allAV(true, true);
947
948        // Retrieve all "Key-Only" (Boolean) attributes from the new / update attribute-list
949        Set<String> newAttributeKeys = attributes.stringPropertyNames();
950
951        // Retrieve all "Key-Only" (Boolean) Attributes from 'this' (the original) TagNode
952        // Use Java Streams to filter out all the ones that need to be clobbered by-virtue-of
953        // the fact that they are present in the new / parameter-updated attribute key-value list.
954        // SAVE: Save the updated / shortened list to a List<String>
955
956        List<String> prunedOriginalKeyOnlyAttributes = allKeyOnlyAttributes(true)
957            .filter((String originalKeyOnlyAttribute) ->
958            {
959                // Returns false when the original key-only attribute matches one of the
960                // new attributes being inserted.  Notice that a case-insensitive comparison
961                // must be performed - to preserve case.
962                for (String newKey : newAttributeKeys) 
963                    if (newKey.equalsIgnoreCase(originalKeyOnlyAttribute)) 
964                        return false;
965
966                return true;
967            })
968            .collect(Collectors.toList());
969
970        // NOTE: There is no need to check the validity of the new attributes.  The TagNode
971        //       constructor that is invoked on the last line of this method will do a 
972        //       validity-check on the attribute key-names provided to the 'attributes' 
973        //       java.util.Properties instance passed to to this method.
974
975        for (String newKey : newAttributeKeys)
976        {
977            String      originalValueWithQuotes = null;
978            String      computedQuote           = null;
979
980            // NOTE, there should only be ONE instance of an attribute in an HTML element, but
981            // malformed HTML happens all the time, so to keep this method safe, it checks (and
982            // removes) the entire attribute-list for matches - not just the first found instance.
983
984            for (String originalKey : originalAttributes.stringPropertyNames())
985                if (originalKey.equalsIgnoreCase(newKey))
986                {
987                    // Remove the original key-value inner-tag pair.
988                    Object temp = originalAttributes.remove(originalKey);
989                    if (temp instanceof String) originalValueWithQuotes = (String) temp;
990                }
991
992            // If the user does not wish to "change" the original quote choice, then find out what
993            // the original-quote choice was...
994
995            if (    (defaultQuote == null) 
996                &&  (originalValueWithQuotes != null)
997                &&  (originalValueWithQuotes.length() >= 2)
998            )
999            {
1000                char s = originalValueWithQuotes.charAt(0);
1001                char e = originalValueWithQuotes.charAt(originalValueWithQuotes.length() - 1);
1002                if ((s == e) && (s == '\''))        computedQuote = "" + SD.SingleQuotes.quote;
1003                else if ((s == e) && (s == '"'))    computedQuote = "" + SD.DoubleQuotes.quote;
1004                else                                computedQuote = "";
1005            }
1006            else if (defaultQuote == null)          computedQuote = "";
1007            else                                    computedQuote = "" + defaultQuote.quote;
1008
1009            // Insert the newly, updated key-value inner-tag pair.  This 'Properties' will be
1010            // used to construct a new TagNode.
1011            originalAttributes.put(newKey, computedQuote + attributes.get(newKey) + computedQuote);
1012        }
1013
1014        return new TagNode(generateElementString(
1015            // Rather than using '.tok' here, preserve the case of the original HTML Element
1016            this.str.substring(1, 1 + tok.length()),
1017            originalAttributes, prunedOriginalKeyOnlyAttributes, null /* SD */,
1018            this.str.endsWith("/>")
1019        ));
1020    }
1021
1022
1023    /**
1024     * This will append a substring to the attribute <B STYLE="color: red;">value</B> of an HTML
1025     * {@code TagNode}.
1026     *
1027     * This method can be very useful, for instance when dealing with CSS tags that are inserted
1028     * inside the HTML node itself.  For instance, in order to add a {@code 'color: red;
1029     * background: white;'} portion to the CSS {@code 'style'} tag of an HTML
1030     * {@code <TABLE STYLE="...">} element, without clobbering the {@code style}-information that
1031     * is already inside the element, then this method will achieve such a result.
1032     *
1033     * @param attribute The <B STYLE="color: red;">name</B> of the attribute to which the 
1034     * <B STYLE="color: red;">value</B> must be appended.  This parameter may not be null, or a
1035     * {@code NullPointerException} will throw.
1036     *
1037     * @param appendStr The {@code String} to be appended to the
1038     * attribute-<B STYLE="color: red;">value</B>.
1039     * 
1040     * @param startOrEnd If this parameter is <B>TRUE</B> then the append-{@code String} will be
1041     * inserted at the beginning (before) whatever the current attribute-<B STYLE="color: red;">
1042     * value</B> is. If this parameter is <B>FALSE</B> then the append-{@code String} will be
1043     * inserted at the end (after) the current attribute-<B STYLE="color: red;">value</B>
1044     * {@code String}.
1045     *
1046     * <BR /><BR /><B>NOTE:</B> If tag element currently does not posses this attribute, then the
1047     * <B STYLE="color: red;">attribute/value</B> pair will be created and inserted with its
1048     * <B STYLE="color: red;">value</B> set to the value of {@code 'appendStr'.}
1049     *
1050     * @param quote This is the quote that will be used when defining the attribute's 
1051     * <B STYLE="color: red;">key-value</B> pair.  This parameter allows for {@code SD.Single,
1052     * SD.Double} or {@code 'null'.}  This parameter is provided to help ensure that improper
1053     * quotations do not occur when modifying  HTML pages.  If 'null' is passed to this parameter,
1054     * then the choice for quote will follow the rules found in method
1055     * {@link #setAV(String, String, SD)}.
1056     * 
1057     * @return Since all instances of {@code TagNode} are immutable, this method will not actually 
1058     * alter the {@code TagNode} element, but rather create a new object reference that contains
1059     * the updated attribute.
1060     *
1061     * @see #AV(String)
1062     * @see #setAV(String, String, SD)
1063     * @see ClosingTagNodeException#check(TagNode)
1064     * 
1065     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1066     * 
1067     * @throws QuotesException The rules of the {@link #setAV(String, String, SD)} method for
1068     * quotation marks apply here as well.  See that method's requirements and definitions to
1069     * understand how this exception could possibly be thrown.
1070     */
1071    public TagNode appendToAV(String attribute, String appendStr, boolean startOrEnd, SD quote)
1072    {
1073        ClosingTagNodeException.check(this);
1074
1075        if (attribute == null) throw new NullPointerException(
1076            "You have passed 'null' to the 'attribute' (attribute-name) String-parameter, " +
1077            "but this is not allowed here."
1078        );
1079
1080        if (appendStr == null) throw new NullPointerException(
1081            "You have passed 'null' to the 'appendStr' (attribute-value-append-string) " +
1082            "String-parameter, but this is not allowed here."
1083        );
1084
1085        String curVal = AV(attribute);
1086        if (curVal == null) curVal = "";
1087
1088        // This decides whether to insert the "appendStr" before the current value-string,
1089        // or afterwards.  This is based on the passed boolean-parameter 'startOrEnd'
1090        curVal = startOrEnd ? (appendStr + curVal) : (curVal + appendStr);
1091
1092        // Reuse the 'setAV(String, String, SD)' method already defined in this class.
1093        return setAV(attribute, curVal, quote);
1094    }   
1095
1096    // ********************************************************************************************
1097    // Attribute Removal Operations
1098    // ********************************************************************************************
1099
1100    /** Convenience Method.  Invokes {@link #removeAttributes(String[])} */
1101    public TagNode remove(String attributeName) { return removeAttributes(attributeName); }
1102
1103    /**
1104     * This will remove all inner-tag's whose <B STYLE="color: red;">names</B> match (using
1105     * {@code CASE-INSENSITIVE} comparisons) the specified
1106     * attribute-<B STYLE="color: red;">names</B> in this input parameter list
1107     * {@code 'attributes'}.
1108     *
1109     * <BR /><BR /><B>NOTE:</B> This will remove all inner-tags that match the listed attributes 
1110     * provided.  This means removing <B>BOTH</B> {@code boolean}
1111     * <B STYLE="color: red;">'key-only'</B> attributes, <B>AND</B> any
1112     * <B STYLE="color: red;">key-value</B> inner-tags that have <B STYLE="color: red;">names</B>
1113     * which match the requested remove-list of names.
1114     *
1115     * @param attributes This is a list of attribute-<B STYLE="color: red;">names</B> (inner-tags)
1116     * to be removed from {@code 'this'} instance of {@code TagNode}.  Each {@code String} in this
1117     * var-args {@code String...} parameter will have {@code String.toLowerCase()} invoked before
1118     * performing these attribute-<B STYLE="color: red;">name</B> comparisons.
1119     * 
1120     * <BR /><BR /><B>NOTE:</B> If {@code 'this'} instance of {@code TagNode} node does not contain
1121     * any of these attributes, then nothing shall happen; however, a new {@code TagNode} instance
1122     * shall still be constructed and returned.
1123     *
1124     * @return An HTML {@code TagNode} instance with updated {@code TagNode} information.
1125     * 
1126     * <BR /><BR /><B><SPAN STYLE="color: red;">IMPORTANT:</SPAN></B> Because <I>{@code TagNode's}
1127     * are immutable</I> (since they are just wrapped-java-{@code String's}, which are also
1128     * immutable), it is important to remember that this method <I><B>does not change the
1129     * contents</B></I> of a {@code TagNode}, but rather <I><B>returns an entirely new
1130     * {@code TagNode}</I></B> as a result instead.
1131     *
1132     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1133     * 
1134     * @see ClosingTagNodeException#check(TagNode)
1135     * @see #tok
1136     * @see #isClosing
1137     * @see #str
1138     * @see #TagNode(String)
1139     * @see #generateElementString(String, Properties, Iterable, SD, boolean)
1140     */
1141    public TagNode removeAttributes(String... attributes)
1142    {
1143        ClosingTagNodeException.check(this);
1144
1145        // Retrieve all Inner-Tag Key-Value Pairs.  Preserve the Case of the Attributes.  Preserve
1146        // the Quotation-Marks.
1147        Properties originalAttributes = allAV(true, true);
1148
1149        // Remove any attributes from the "Attributes Key-Value Properties Instance" which MATCH the
1150        // attribute names that have been EXPLICITLY REQUESTED FOR REMOVAL
1151        for (String key : originalAttributes.stringPropertyNames())
1152            for (String attribute : attributes)
1153                if (key.equalsIgnoreCase(attribute))
1154                    originalAttributes.remove(key);
1155
1156        // Retrieve all "Boolean Attributes" (key-no-value).  Preserve the Case of these Attributes.
1157        // Retain only the attributes in the 'filteredKeyOnlyAttributes' String-Array which have
1158        // PASSED THE FILTER OPERATION.  The filter operation only returns TRUE if the 
1159        // requested-attribute-list does not contain a copy of the Key-Only-Attribute
1160        // NOTE: 'true' is passed as input to the 'allKeyOnlyAttributes(boolean)' method to request
1161        //       that CASE be PRESERVED.
1162        Iterable<String> prunedKeyOnlyAttributes = allKeyOnlyAttributes(true)
1163            .filter((String attribute) ->
1164            {
1165                // Returns false when the original key-only attribute matches one of the attributes
1166                // that was requested to to be removed.  Notice that a case-insensitive comparison 
1167                // must be performed.
1168                for (String removeAttributes : attributes)
1169                    if (removeAttributes.equalsIgnoreCase(attribute))
1170                        return false;
1171
1172                return true;
1173            })
1174            .collect(Collectors.toList());
1175
1176        return new TagNode(generateElementString(
1177            // Rather than using '.tok' here, preserve the case of the original HTML Element
1178            this.str.substring(1, 1 + tok.length()),
1179            originalAttributes, prunedKeyOnlyAttributes, /* SD */ null, 
1180            this.str.endsWith("/>")
1181        ));
1182    }
1183
1184    /**
1185     * {@code TagNode's} are immutable.  And because of this, calling {@code removeAllAV()} is
1186     * actually the same as retrieving the standard, zero-attribute, pre-instantiated instance of
1187     * an HTML Element.  Pre-instantiated <B><I>factory-instances</I></B> of {@code class TagNode}
1188     * for every HTML-Element are stored by {@code class HTMLTags} inside a {@code Hashtable.}
1189     * They can be retrieved in multiple ways, two of which are found in methods in this class.
1190     *
1191     * <BR /><BR /><B>Point of Interest:</B> Calling these three different methods will all return
1192     * <I>identical</I> {@code Object} references:
1193     * 
1194     * <BR /><BR />
1195     * 
1196     * <UL CLASS="JDUL">
1197     * <LI>{@code TagNode v1 = myTagNode.removeAllAV(); } </LI>
1198     * <LI>{@code TagNode v2 = TagNode.getInstance(myTagToken, openOrClosed); } </LI>
1199     * <LI>{@code TagNode v3 = HTMLTag.hasTag(myTagToken, openOrClosed); } </LI>
1200     * <LI><SPAN STYLE="color: red;">{@code assert((v1 == v2) && (v2 == v3)); }</SPAN></LI>
1201     * </UL>
1202     * 
1203     * <BR /><BR /><IMG SRC='removeAllAV.png' CLASS=JDIMG ALT='example'>
1204     * 
1205     * @return An HTML {@code TagNode} instance with all inner attributes removed.
1206     *
1207     * <BR /><BR /><B>NOTE:</B> If this tag contains an "ending forward slash" that ending slash
1208     * will not be included in the output {@code TagNode.}
1209     *
1210     * <BR /><BR /><B><SPAN STYLE="color: red;">IMPORTANT:</SPAN></B> Because <I>{@code TagNode's} 
1211     * are immutable</I> (since they are just wrapped-java-{@code String's}, which are also
1212     * immutable), it is important to remember that this method <I><B>does not change the
1213     * contents</B></I> of a {@code TagNode}, but rather <I><B>returns an entirely new
1214     * {@code TagNode}</I></B> as a result instead.
1215     * 
1216     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1217     * 
1218     * @see ClosingTagNodeException#check(TagNode)
1219     * @see #getInstance(String, TC)
1220     * @see #str
1221     * @see #tok
1222     * @see TC#OpeningTags
1223     */
1224    public TagNode removeAllAV()
1225    {
1226        ClosingTagNodeException.check(this);
1227
1228        // NOTE: We *CANNOT* use the 'tok' field to instantiate the TagNode here, because the 'tok'
1229        // String-field is *ALWAYS* guaranteed to be in a lower-case format.  The 'str'
1230        // String-field, however uses the original case that was found on the HTML Document by the
1231        // parser (or in the Constructor-Parameters that were passed to construct 'this' instance
1232        // of TagNode.
1233
1234        return getInstance(this.str.substring(1, 1 + tok.length()), TC.OpeningTags);
1235    }
1236
1237    // ********************************************************************************************
1238    // Retrieve all attributes
1239    // ********************************************************************************************
1240
1241    /** Convenience Method.  Invokes {@link #allAV(boolean, boolean)}, attribute-<B STYLE="color: red;">names</B> will be in lower-case. */
1242    public Properties allAV() { return allAV(false, false); }
1243
1244    /**
1245     * This will copy every attribute <B STYLE="color: red;">key-value</B> pair inside
1246     * {@code 'this'} HTML {@code TagNode} element into a {@code java.util.Properties} Hash-Table.
1247     *
1248     * <BR /><BR /><B>RETURN-VALUE NOTE:</B> This method shall not return any "Key-Only Attributes"
1249     * (a.k.a. "Boolean Attributes").  The most commonly used "Boolean Attribute" example is the
1250     * {@code 'HIDDEN'} key-word that is used to prevent the browser from displaying an HTML
1251     * Element. Inner-tags that represent attribute <B STYLE="color: red;">key-value</B> pairs are
1252     * the only attributes that may be included in the returned {@code 'Properties'} instance.
1253     *
1254     * <BR /><BR /><IMG SRC="allAV.png" CLASS=JDIMG ALT="example">
1255     * 
1256     * @param keepQuotes If this parameter is passed <B>TRUE</B>, then any surrounding quotation
1257     * marks will be included for each the <B STYLE="color: red;">values</B> of each attribute
1258     * key-value pair.
1259     *
1260     * @param preserveKeysCase If this parameter is passed <B>TRUE</B>, then the method
1261     * {@code String.toLowerCase()} will not be invoked on any of the
1262     * <B STYLE="color: red;">keys</B> (attribute-names) of each inner-tag key-value pair.
1263     *
1264     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
1265     *
1266     * @return This returns a list of each and every attribute-<B STYLE="color: red;">name</B> -
1267     * <I>and the associate <B STYLE="color: red;">value</B> of the attribute</I> - found in
1268     * {@code 'this' TagNode}.  An instance of {@code class java.util.Properties} is used to store
1269     * the attribute <B STYLE="color: red;">key-value</B> pairs. 
1270     *
1271     * <BR /><BR /><B>NOTE:</B> This method will <B>NOT</B> return any boolean,
1272     * <B STYLE="color: red;">key-only</B> attributes present in {@code 'this' TagNode}.
1273     * 
1274     * <BR /><BR /><B>ALSO:</B> This method shall not return {@code 'null'}.  If there do not
1275     * exist any Attribute-Value Pairs, or if {@code 'this'} node is a closing-element, then
1276     * an empty {@code 'Properties'} instance shall be returned.
1277     * 
1278     * @see StringParse#ifQuotesStripQuotes(String)
1279     * @see AttrRegEx#KEY_VALUE_REGEX
1280     * @see #tok
1281     * @see #str
1282     */
1283    public Properties allAV(boolean keepQuotes, boolean preserveKeysCase)
1284    {
1285        Properties ret = new Properties();
1286
1287        // NOTE:    OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str' field is
1288        //          only longer than the token, itself, by 3 or less characters cannot have attributes.
1289        // CHARS:   '<', TOKEN, SPACE, '>'
1290        // RET:     In that case, just return an empty 'Properties' instance.
1291        if (isClosing || (str.length() <= (tok.length() + 3))) return ret;
1292
1293        // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs.
1294        // m.group(1): UN-USED!  (Includes Key, Equals-Sign, and Value).  Leaves-off leading white-space.
1295        // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign).
1296        // m.group(3): returns the 'value' portion of the key-value pair, after an '='
1297        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1298
1299        // MORE-CODE, but MORE-EFFICIENT (slightly)  It looks neat to boot.
1300        if      (keepQuotes     && preserveKeysCase)    while (m.find()) ret.put(m.group(2), m.group(3));
1301        else if (!keepQuotes    && preserveKeysCase)    while (m.find()) ret.put(m.group(2), StringParse.ifQuotesStripQuotes(m.group(3)));
1302        else if (keepQuotes     && !preserveKeysCase)   while (m.find()) ret.put(m.group(2).toLowerCase(), m.group(3));
1303        else if (!keepQuotes    && !preserveKeysCase)   while (m.find()) ret.put(m.group(2).toLowerCase(), StringParse.ifQuotesStripQuotes(m.group(3)));
1304
1305        return ret;
1306    }
1307
1308    /**
1309     * Convenience Method.  Invokes {@link #allAN(boolean, boolean)}.
1310     * <BR /><BR />Attribute-<B STYLE="color: red;">names</B> will be in lower-case.
1311     */
1312    public Stream<String> allAN()
1313    { return allAN(false, false); }
1314
1315    /**
1316     * This method will only return a list of attribute-<B STYLE="color: red;">names</B>.  The
1317     * attribute-<B STYLE="color: red">values</B> shall <B>NOT</B> be included in the result.  The
1318     * {@code String's} returned can have their "case-preserved" by passing <B>TRUE</B> to the
1319     * input boolean parameter {@code 'preserveCase'}.
1320     *
1321     * @param preserveKeysCase If this is parameter receives <B>TRUE</B> then the case of the
1322     * attribute-<B STYLE="color: red;">names</B> shall be preserved.
1323     *
1324     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
1325     *
1326     * @param includeKeyOnlyAttributes When this parameter receives <B>TRUE</B>, then any
1327     * "Boolean Attributes" or "Key-Only, No-Value-Assignment" Inner-Tags will <B>ALSO</B> be
1328     * included in the {@code Stream<String>} returned by this method.
1329     *
1330     * @return an instance of {@code Stream<String>} containing all
1331     * attribute-<B STYLE="color: red;">names</B> identified in {@code 'this'} instance of
1332     * {@code TagNode}.  A {@code java.util.stream.Stream} is used because it's contents can easily
1333     * be converted to just about any data-type.  
1334     *
1335     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVT">
1336     *
1337     * <BR /><B>NOTE:</B> This method shall never return {@code 'null'} - even if there are no 
1338     * attribute <B STYLE="color: red;">key-value</B> pairs contained by {@code 'this' TagNode}.
1339     * If there are strictly zero attributes, an empty {@code Stream} shall be returned, instead.
1340     * 
1341     * @see #allKeyOnlyAttributes(boolean)
1342     * @see #allAN()
1343     */
1344    public Stream<String> allAN(boolean preserveKeysCase, boolean includeKeyOnlyAttributes)
1345    {
1346        // If there is NO ROOM in the "str" field for attributes, then there is now way attributes
1347        // could exist in this element.  Return "empty" immediately.
1348        // 
1349        // NOTE:    OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str' field
1350        //          is only longer than the token, itself, by 3 or less characters cannot have
1351        //          attributes.
1352        // CHARS:   '<', TOKEN, SPACE, '>'
1353        // RET:     In that case, just return an empty Stream.
1354        if (isClosing || (str.length() <= (tok.length() + 3))) return Stream.empty();
1355
1356        // Use Java Streams.  A String-Stream is easily converted to just about any data-type
1357        Stream.Builder<String> b = Stream.builder();
1358
1359        // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs.
1360        // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign).
1361        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1362
1363        // Retrieve all of the keys of the attribute key-value pairs.
1364        while (m.find()) b.add(m.group(2));
1365
1366        // This Stream contains only keys that were once key-value pairs, if there are "key-only" 
1367        // attributes, they have not been added yet.
1368        Stream<String> ret = b.build();
1369
1370        // Convert these to lower-case, (if requested)
1371        if (! preserveKeysCase) ret = ret.map((String attribute) -> attribute.toLowerCase());
1372
1373        // Now, add in all the "Key-Only" attributes (if there are any).  Note, "preserve-case"
1374        // and "to lower case" are handled, already, in method "allKeyOnlyAttributes(boolean)"
1375        if (includeKeyOnlyAttributes)
1376            return Stream.concat(ret, allKeyOnlyAttributes(preserveKeysCase));
1377
1378        return ret;
1379    }
1380
1381    // ********************************************************************************************
1382    // Key only attributes
1383    // ********************************************************************************************
1384
1385    /**
1386     * This method returns a {@code Stream<String>} of all token-{@code String's} that are found 
1387     * between attribute key-value pairs in {@code 'this' TagNode} instance.  These 
1388     * attribute-<B STYLE="color: red;">names</B> may not have any
1389     * <B STYLE="color: red;">values</B> assignments, or they will be considered <B>INELLIGIBLE</B>
1390     * for being included in the return result set from this method.
1391     * 
1392     * <BR /><BR /><IMG SRC='allKeyOnlyAttributes.png' CLASS=JDIMG ALT='example'>
1393     *
1394     * @param preserveKeysCase If this parameter is passed <B>TRUE</B>, then the method 
1395     * {@code String.toLowerCase()} will not be invoked on any of the
1396     * <B STYLE="color: red;">keys</B> (attribute-<B STYLE="color: red;">names</B>) returned.
1397     *
1398     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
1399     *
1400     * @return a java {@code Stream<String>} that contains any and all character text that resides
1401     * between attribute <B STYLE="color: red;">key-value</B> pairs that have matched.  Generally, 
1402     * in well formed HTML, this should correspond directly to what are normally called "Boolean 
1403     * Attributes."  Boolean attributes are just words inside of an HTML Element that describe the
1404     * contents of the HTML.  The primary issue about "Boolean Attributes" is that they do not need
1405     * <B STYLE="color: red;">values</B> - <I> they are strictly a <B STYLE="color: red;">key</B>,
1406     * alone.</I>
1407     *
1408     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVT">
1409     *
1410     * <BR /><B>NOTE:</B> This method shall never return 'null' - even if there are no attribute
1411     * <B STYLE="color: red;">key-value</B> pairs contained by the {@code TagNode}.  If there are 
1412     * strictly zero attributes, {@code Stream.empty()} shall be returned, instead.
1413     * 
1414     * @see #tok
1415     * @see #str
1416     */
1417    public Stream<String> allKeyOnlyAttributes(boolean preserveKeysCase)
1418    {
1419        // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
1420        //       field is only longer than the token, itself, by 3 or less characters cannot have
1421        //       attributes.  In that case, just return an empty 'Stream' instance.
1422        int len = str.length();
1423        if (isClosing || (len <= (tok.length() + 3))) return Stream.empty();
1424
1425        // Leaves off the opening 'token' and less-than '<' symbol  (leaves off, <DIV - for example)
1426        // Also leave off the "ending-forward-slash" (if there is one).
1427        String  s = str.substring(tok.length() + 1, len - ((str.charAt(len - 2) == '/') ? 2 : 1));
1428
1429        // if all lower-case is requested, do that here.
1430        if (! preserveKeysCase) s = s.toLowerCase();
1431
1432        // java.util.regex.Pattern.split(CharSequence) is sort of an "inverse reg-ex" in that it 
1433        // returns all of the text that was present BETWEEN the matches 
1434        // NOTE: This is the "opposite of the matches, themselves)" - a.k.a. all the stuff that was
1435        //       left-out.
1436
1437        Stream.Builder<String> b = Stream.builder();
1438        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(s))      // 'split' => inverse-matches
1439            for (String keyWord : unMatchedStr.split("\\s+"))               // white-space split (connected chars)
1440                if ((keyWord = keyWord.trim()).length() > 0)                // Call String.trim() and String.length()
1441                    if (AttrRegEx.ATTRIBUTE_KEY_REGEX_PRED.test(keyWord))   // Check for valid Attribute-Name
1442                        b.add(keyWord);                                     // ... put it in the return stream.
1443                                                                            // NOTE: This has the potential to slightly
1444                                                                            //       change the original HTML... It will
1445                                                                            //       "leave out any guck" that was in the Element
1446
1447        // Build the Stream<String>, and return;
1448        return b.build();
1449    }
1450
1451    /**
1452     * Will identify if a "boolean attribute" - a.k.a. a token-<B STYLE="color: red;">name</B> that
1453     * exists BETWEEN inner-tag key-value pairs is present in the {@code TagNode}.  One of the
1454     * most common "Key-Word-Only Attributes" is the inner-tag {@code 'HIDDEN'}.  Hidden HTML
1455     * Elements have their CSS feature {@code style.display} set to {@code 'NONE'};
1456     * 
1457     * @param keyOnlyAttribute This may be the <B STYLE="color: red;">name</B> of any inner-tag.
1458     * 
1459     * <BR /><BR /><B>NOTE:</B> This parameter, is not checked for validity against the
1460     * attribute-<B STYLE='color: red;'>name</B> regular-expression.
1461     * 
1462     * @return Will return <B>TRUE</B> if the named {@code 'keyOnlyAttribute'} is present in the
1463     * HTML Element as a stand-alone attribute - <I>i.e., lacking a
1464     * <B STYLE="color: red;">value</B> assignment</I>  The comparison performed is
1465     * case-insensitive.
1466     * 
1467     * <BR /><BR />If {@code 'this'} instance of {@code TagNode} is a closing-version of the
1468     * element, this method shall return <B>FALSE</B> immediately, and exit.
1469     * 
1470     * @throws IllegalArgumentException If the input-parameter receives a {@code String} that
1471     * contains any white-space itself, an exception will throw.  The search-logic splits the
1472     * {@code String's} based on  white-space, so if a user passes a white-space containing
1473     * {@code String}, a match would simply never occur.
1474     * 
1475     * <BR /><BR /><B STYLE='color:red;'>IMPORTANT:</B> This method <I><B>does not</B></I> check
1476     * the validity of the {@code 'keyOnlyAttribute'} parameter against the
1477     * Attribute-<B STYLE='color:red;'>name</B> regular-expression, because this method uses the
1478     * {@code 'split(String)'} method of the Regular-Expression Matcher.  All this means, is that
1479     * this method may actually be used to check for any-text inside of an HTML Element - <I>so
1480     * long as that text does not contain white-space.</I>  This is not an encouraged use of this
1481     * method, but it will work.
1482     * 
1483     * @see AttrRegEx#KEY_VALUE_REGEX
1484     */
1485    public boolean hasKeyOnlyAttribute(String keyOnlyAttribute)
1486    {
1487        // Closing TagNode's do not have attributes, return false immediately.
1488        if (this.isClosing) return false;
1489
1490        // ONLY CHECKS FOR WHITE-SPACE, *NOT* VALIDITY...
1491        if (StringParse.hasWhiteSpace(keyOnlyAttribute)) throw new IllegalArgumentException(
1492            "The attribute you have passed [" + keyOnlyAttribute + "] has white-space, " +
1493            "This is not allowed here, because the search routine splits on whitespace, and " +
1494            "therefore a match would never be found."
1495        );
1496
1497        // NOTE: TagNode's whose 'str' field is only longer than the token, itself, by 3 or less
1498        //       characters cannot have attributes.  In that case, just return false.
1499        int len = str.length();
1500        if (len <= (tok.length() + 3)) return false;
1501
1502        // Leaves off the opening 'token' and less-than '<' symbol  (leaves off, <DIV - for example)
1503        // Also leave off the "ending-forward-slash" (if there is one).
1504        String s = str.substring(tok.length() + 1, len - ((str.charAt(len - 2) == '/') ? 2 : 1));
1505
1506        // java.util.regex.Pattern.split(CharSequence) is sort of an "inverse reg-ex" in that it 
1507        // returns all of the text that was present BETWEEN the matches 
1508
1509        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(s))  // 'split' => inverse-matches
1510            for (String keyWord : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
1511                if ((keyWord = keyWord.trim()).length() > 0)            // trim, check-length...
1512                    if (keyOnlyAttribute.equalsIgnoreCase(keyWord))
1513                        return true;
1514
1515        // Was not found, return false;
1516        return false;
1517    }
1518
1519    // ********************************************************************************************
1520    // testAV
1521    // ********************************************************************************************
1522
1523    /**
1524     * Test the <B STYLE="color: red;">value</B> of the inner-tag named {@code 'attributeName'}
1525     * (if that attribute exists, and has a non-empty value) using a provided
1526     * {@code TextComparitor}
1527     * 
1528     * @param attributeName Any String will suffice - but only valid attribute
1529     * <B STYLE="color: red;">names</B> will match the internal regular-expression.
1530     * 
1531     * <BR /><BR /><B>NOTE:</B> The validity of this parameter <I><B>is not</I></B> checked with
1532     * the HTML attribute-<B STYLE="color: red;">name</B> Regular-Expression exception checker.
1533     * 
1534     * @param tc This may be any pre-instantiated or user created {@code TextComparitor}
1535     * 
1536     * @param compareStrs This should be a {@code String[] array} of comparison-strings to
1537     * work with parameter {@code 'tc'}.
1538     * 
1539     * @return Method will return <B>TRUE</B> if and only if:
1540     * 
1541     * <BR /><BR /><UL CLASS="JDUL">
1542     * <LI> {@code 'this'} instance of {@code TagNode} has an inner-tag named
1543     *      {@code 'attributeName'}.
1544     *      <BR /><BR />
1545     * </LI>
1546     * <LI> The results of applying the provided {@code TextComparitor} parameter {@code 'tc'}
1547     *      against the attribute-<B STYLE="color: red">value</B> and {@code comparStrs} array,
1548     *      returns <B>TRUE</B>.
1549     * </LI>
1550     * </UL>
1551     * 
1552     * @see AttrRegEx#KEY_VALUE_REGEX
1553     * @see #str 
1554     * @see #isClosing
1555     * @see StringParse#ifQuotesStripQuotes(String)
1556     */
1557    public boolean testAV(String attributeName, TextComparitor tc, String... compareStrs)
1558    {
1559        // Closing TagNode's (</DIV>, </A>) cannot attributes, or attribute-values
1560        if (isClosing) return false;
1561
1562        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1563        // are simply too short to have the attribute named by the input parameter
1564        if (this.str.length() < (this.tok.length() + attributeName.length() + 4)) return false;
1565
1566        // This Reg-Ex will allow us to iterate through each attribute key-value pair
1567        // contained / 'inside' this instance of TagNode.
1568        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1569
1570        // Test each attribute key-value pair, and return the test results if an attribute
1571        // whose name matches 'attributeName' is found.
1572        while (m.find())
1573            if (m.group(2).equalsIgnoreCase(attributeName))
1574                return tc.test
1575                    (StringParse.ifQuotesStripQuotes(m.group(3)), compareStrs);
1576
1577        // No attribute key-value pair was found whose 'key' matched input-parameter
1578        // 'attributeName'
1579        return false;
1580    }
1581
1582    /**
1583     * Test the <B STYLE="color: red;">value</B> of the inner-tag named {@code 'attributeName'}
1584     * (if that attribute exists, and has a non-empty value) using a provided
1585     * {@code java.util.regex.Pattern}
1586     * 
1587     * <BR /><BR /><IMG SRC='testAV2.png' CLASS=JDIMG ALT='example'>
1588     * 
1589     * @param attributeName Any String will suffice - but only valid attribute
1590     * <B STYLE="color: red;">names</B> will match the internal regular-expression.
1591     * 
1592     * <BR /><BR /><B>NOTE:</B> The validity of this parameter <I><B>is not</I></B> checked with
1593     * the HTML attribute-<B STYLE="color: red;">name</B> Regular-Expression exception checker.
1594     * 
1595     * @param attributeValueTest This may be an regular-expression
1596     * 
1597     * @return Method will return <B>TRUE</B> if and only if:
1598     * 
1599     * <BR /><BR /><UL CLASS="JDUL">
1600     * <LI> {@code 'this'} instance of {@code TagNode} has an inner-tag named
1601     *      {@code 'attributeName'}.
1602     *      <BR /><BR />
1603     * </LI>
1604     * <LI> The results of invoking {@code attributeValueTest.matcher(...).find()} on the
1605     *      attribute-<B STYLE="color: red">value</B> returns <B>TRUE</B>.
1606     * </LI>
1607     * </UL>
1608     * 
1609     * @see AttrRegEx#KEY_VALUE_REGEX
1610     * @see #str 
1611     * @see #isClosing
1612     * @see StringParse#ifQuotesStripQuotes(String)
1613     */
1614    public boolean testAV(String attributeName, Pattern attributeValueTest)
1615    {
1616        // Closing TagNode's (</DIV>, </A>) cannot attributes, or attribute-values
1617        if (isClosing) return false;
1618
1619        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1620        // are simply too short to have the attribute named by the input parameter
1621        if (this.str.length() < (this.tok.length() + attributeName.length() + 4)) return false;
1622
1623        // This Reg-Ex will allow us to iterate through each attribute key-value pair
1624        // contained / 'inside' this instance of TagNode.
1625        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1626
1627        // Test each attribute key-value pair, and return the test results if an attribute
1628        // whose name matches 'attributeName' is found.
1629        while (m.find())
1630            if (m.group(2).equalsIgnoreCase(attributeName))
1631                return attributeValueTest.matcher
1632                    (StringParse.ifQuotesStripQuotes(m.group(3))).find();
1633
1634        // No attribute key-value pair was found whose 'key' matched input-parameter
1635        // 'attributeName'
1636        return false;
1637    }
1638
1639    /**
1640     * Test the <B STYLE="color: red;">value</B> of the inner-tag named {@code 'attributeName'}
1641     * (if that attribute exists, and has a non-empty value) using a provided
1642     * {@code Predicate<String>}.
1643     * 
1644     * <BR /><BR /><IMG SRC='testAV1.png' CLASS=JDIMG ALT='example'>
1645     * 
1646     * @param attributeName Any String will suffice - but only valid attribute
1647     * <B STYLE="color: red;">names</B> will match the internal regular-expression.
1648     * 
1649     * <BR /><BR /><B>NOTE:</B> The validity of this parameter <I><B>is not</I></B> checked with
1650     * the HTML attribute-<B STYLE="color: red;">name</B> Regular-Expression exception checker.
1651     * 
1652     * @param attributeValueTest Any {@code java.util.function.Predicate<String>}
1653     * 
1654     * @return Method will return <B>TRUE</B> if and only if:
1655     * 
1656     * <BR /><BR /><UL CLASS="JDUL">
1657     * <LI> {@code 'this'} instance of {@code TagNode} has an inner-tag named
1658     *      {@code 'attributeName'}.
1659     *      <BR /><BR />
1660     * </LI>
1661     * <LI> The results of the provided {@code String-Predicate}, when applied against the
1662     *      <B STYLE="color: red">value</B> of the requested attribute, returns <B>TRUE</B>.
1663     * </LI>
1664     * </UL>
1665     * 
1666     * @see AttrRegEx#KEY_VALUE_REGEX
1667     * @see #str 
1668     * @see #isClosing
1669     * @see StringParse#ifQuotesStripQuotes(String)
1670     */
1671    public boolean testAV(String attributeName, Predicate<String> attributeValueTest)
1672    {
1673        // Closing TagNode's (</DIV>, </A>) cannot attributes, or attribute-values
1674        if (isClosing) return false;
1675
1676        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1677        // are simply too short to have the attribute named by the input parameter
1678        if (this.str.length() < (this.tok.length() + attributeName.length() + 4)) return false;
1679
1680        // This Reg-Ex will allow us to iterate through each attribute key-value pair
1681        // contained / 'inside' this instance of TagNode.
1682        Matcher m = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1683
1684        // Test each attribute key-value pair, and return the test results if an attribute
1685        // whose name matches 'attributeName' is found.
1686        while (m.find())
1687            if (m.group(2).equalsIgnoreCase(attributeName))
1688                return attributeValueTest.test
1689                    (StringParse.ifQuotesStripQuotes(m.group(3)));
1690
1691        // No attribute key-value pair was found whose 'key' matched input-parameter
1692        // 'attributeName'
1693        return false;
1694    }
1695
1696
1697    // ********************************************************************************************
1698    // has-attribute boolean-logic methods
1699    // ********************************************************************************************
1700
1701    /**
1702     * This method provides a quick way of testing if {@code 'this'} HTML {@code TagNode} contains
1703     * certain inner-tags with specified <B STYLE="color: red;">names</B>.
1704     * 
1705     * <BR /><BR />
1706     * This uses <B>{@code logical NAND}</B> meaning that {@code FALSE} shall be returned if any of
1707     * the inner tags provided in the {@code 'attributes'} parameter list are found in
1708     * {@code 'this'} instance of {@code TagNode}.
1709     * 
1710     * @param attributes This is a list of HTML Element Attribute-<B STYLE="color: red;">Names</B> 
1711     * or "Inner Tags" as they are called in this Search and Scrape Package.
1712     * 
1713     * @param checkAttributeStringsForErrors <EMBED CLASS="external-html" DATA-FILE-ID="TNHASBOOL">
1714     * 
1715     * @return <B>TRUE</B> if none of these  attributes-<B STYLE="color: red;">names</B> are
1716     * present, in {@code 'this'} instance and <B>FALSE</B> otherwise.
1717     * 
1718     * <BR /><BR /><B>NOTE:</B> If this method is passed a zero-length {@code String}-array to the
1719     * {@code 'attributes'} parameter, this method shall exit immediately and return <B>TRUE</B>.
1720     * 
1721     * @throws InnerTagKeyException If any of the {@code 'attributes'} are not valid HTML
1722     * attributes, <I><B>and</B></I> the user has passed <B>TRUE</B> to parameter
1723     * {@code checkAttributeStringsForErrors}.
1724     * 
1725     * @throws NullPointerException If any of the {@code 'attributes'} are null.
1726     * 
1727     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1728     * 
1729     * @throws IllegalArgumentException If the {@code 'attributes'} parameter has length zero.
1730     * 
1731     * @see InnerTagKeyException#check(String[])
1732     * @see #AV(String)
1733     * @see AttrRegEx#KEY_VALUE_REGEX
1734     */
1735    public boolean hasNAND(boolean checkAttributeStringsForErrors, String... attributes)
1736    {
1737        ClosingTagNodeException.check(this);
1738
1739        // If no attributes are passed to 'attributes' parameter, throw exception.
1740        if (attributes.length == 0) throw new IllegalArgumentException
1741            ("Input variable-length String[] array parameter, 'attributes', has length zero.");
1742
1743        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1744        // are simply too short to have any attribute-value pairs.
1745        // 4 (characters) are: '<', '>', ' ' and 'X'
1746        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
1747        //
1748        // This TagNode doesn't have any attributes in it.
1749        // There is no need to check anything, so return FALSE immediately ("NAND" succeeds)
1750        if (this.str.length() < (this.tok.length() + 4)) return true;
1751
1752        if (checkAttributeStringsForErrors) InnerTagKeyException.check(attributes);
1753
1754        // Get all inner-tag key-value pairs.  If even one of these is inside the 'attributes'
1755        // input-parameter string-array.  Then we must return false, since this is a NAND - or
1756        // 'NOT ANY' - operation.
1757        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1758
1759        while (keyValueInnerTags.find())
1760        {
1761            String innerTagKey = keyValueInnerTags.group(2);                    // Retrieve the key of the key-value pair
1762            for (String attributeToNAND : attributes)                           // Iterate every element of the String[] 'attributes' parameter
1763                if (innerTagKey.equalsIgnoreCase(attributeToNAND))              // Does the input parameter String-array match with an inner-tag key?
1764                    return false;                                               // NAND: If there is a match, return FALSE immediately
1765        }
1766
1767        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
1768        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the real matches)
1769        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
1770            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
1771                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
1772                    for (String attributeToNAND : attributes)                   // Iterate all the input-parameter String-array attributes
1773                        if (keyOnlyInnerTag.equalsIgnoreCase(attributeToNAND))  // If even one of them
1774                            return false;                                       // NAND: If there is a match, return FALSE immediately
1775
1776        // If NIETHER the Key-Value attributes, NOR the Key-Only attributes had any matches with
1777        // the input String-Array parameter "attributes", then (and only then) should TRUE be returned.
1778        return true;
1779    }
1780
1781    /**
1782     * This method provides a quick way of testing if {@code 'this'} HTML {@code TagNode} contains
1783     * certain inner-tags with specified <B STYLE="color: red;">names</B>.
1784     * 
1785     * <BR /><BR />
1786     * This uses <B>{@code logical XOR}</B> meaning that {@code TRUE} shall be returned if
1787     * <I>*precisely one*</I> of the inner tags provided in the {@code 'attributes'} parameter list
1788     * are found in {@code 'this'} instance of {@code TagNode}.
1789     * 
1790     * @param attributes This is a list of HTML Element Attribute-<B STYLE="color: red;">Names</B> 
1791     * or "Inner Tags" as they are called in this Search and Scrape Package.
1792     * 
1793     * @param checkAttributeStringsForErrors <EMBED CLASS="external-html" DATA-FILE-ID="TNHASBOOL">
1794     * 
1795     * @return <B>TRUE</B> if exactly one of these attributes-<B STYLE="color: red;">names</B> are
1796     * present in {@code 'this'} instance, and <B>FALSE</B> otherwise.
1797     * 
1798     * <BR /><BR /><B>NOTE:</B> If this method is passed a zero-length {@code String}-array to the
1799     * {@code 'attributes'} parameter, this method shall exit immediately and return <B>FALSE</B>.
1800     * 
1801     * @throws InnerTagKeyException If any of the {@code 'attributes'} are not valid HTML
1802     * attributes <I><B>and</B></I> the user has passed <B>TRUE</B> to parameter
1803     * {@code checkAttributeStringsForErrors}.
1804     * 
1805     * @throws NullPointerException If any of the {@code 'attributes'} are null.
1806     * 
1807     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1808     * 
1809     * @throws IllegalArgumentException If the {@code 'attributes'} parameter has length zero.
1810     * 
1811     * @see InnerTagKeyException#check(String[])
1812     * @see #AV(String)
1813     * @see AttrRegEx#KEY_VALUE_REGEX
1814     */
1815    public boolean hasXOR(boolean checkAttributeStringsForErrors, String... attributes)
1816    {
1817        ClosingTagNodeException.check(this);
1818
1819        // If no attributes are passed to 'attributes' parameter, throw exception.
1820        if (attributes.length == 0) throw new IllegalArgumentException
1821            ("Input variable-length String[] array parameter, 'attributes', has length zero.");
1822
1823        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1824        // are simply too short to have any attribute-value pairs.
1825        // 4 (characters) are: '<', '>', ' ' and 'X'
1826        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
1827        //
1828        // This TagNode doesn't have any attributes in it.
1829        // There is no need to check anything, so return FALSE immediately ("XOR" fails)
1830        if (this.str.length() < (this.tok.length() + 4)) return false;
1831
1832        if (checkAttributeStringsForErrors) InnerTagKeyException.check(attributes);
1833
1834        // Temporary variable.  This is required because this is an "XOR" comparison.
1835        boolean ret = false;
1836
1837        // Get all inner-tag key-value pairs.  When one of the input-parameter string-array
1838        // inner-tags is found, we must take a note saying a match has been found.  If there is a
1839        // second-match, because this is XOR, we must return FALSE immediately.
1840        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1841
1842        while (keyValueInnerTags.find())
1843        {
1844            String innerTagKey = keyValueInnerTags.group(2);                    // Retrieve the key of the key-value pair
1845            for (String attributeToXOR : attributes)                            // Iterate every element of the String[] 'attributes' parameter
1846                if (innerTagKey.equalsIgnoreCase(attributeToXOR))               // Does the input parameter String-array match with an inner-tag key?
1847                { if (ret) return false; else ret = true; }                     // XOR: If there is a second match must, return FALSE immediately
1848        }
1849
1850        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
1851        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
1852        // real matches)
1853
1854        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
1855            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
1856                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
1857                    for (String attributeToXOR : attributes)                    // Iterate all the input-parameter String-array attributes
1858                        if (keyOnlyInnerTag.equalsIgnoreCase(attributeToXOR))   // Is there a match?
1859                        { if (ret) return false; else ret = true; }             // XOR: If there is a second match must, return FALSE immediately
1860
1861        // This will be TRUE if there was PRECISELY one match.
1862        return ret;
1863    }
1864
1865    /**
1866     * This method provides a quick way of testing if {@code 'this'} HTML {@code TagNode} contains
1867     * certain inner-tags with specified <B STYLE="color: red;">names</B>.
1868     * 
1869     * <BR /><BR />
1870     * This uses <B>{@code logical AND}</B> meaning that {@code TRUE} shall be returned <I>*if and 
1871     * only if*</I> all of the inner tags provided in the {@code 'attributes'} parameter list are
1872     * found in {@code 'this'} instance of {@code TagNode}.
1873     * 
1874     * <BR /><BR /><IMG SRC="hasAND.png" CLASS=JDIMG ALT="Example">
1875     * 
1876     * @param attributes This is a list of HTML Element Attribute-<B STYLE="color: red;">Names</B> 
1877     * or "Inner Tags" as they are called in this Search and Scrape Package.
1878     * 
1879     * @param checkAttributeStringsForErrors <EMBED CLASS="external-html" DATA-FILE-ID="TNHASBOOL">
1880     * 
1881     * @return <B>TRUE</B> if each and every one of these
1882     * attributes-<B STYLE="color: red;">names</B> are present in {@code 'this'} instance, and
1883     * <B>FALSE</B> otherwise.
1884     * 
1885     * <BR /><BR /><B>NOTE:</B> If this method is passed a zero-length {@code String}-array to the
1886     * {@code 'attributes'} parameter, this method shall exit immediately and return <B>FALSE</B>
1887     * 
1888     * @throws InnerTagKeyException If any of the {@code 'attributes'} are not valid HTML
1889     * attributes, <I><B>and</B></I> the user has passed <B>TRUE</B> to parameter
1890     * {@code checkAttributeStringsForErrors}.
1891     * 
1892     * @throws NullPointerException If any of the {@code 'attributes'} are null.
1893     * 
1894     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1895     * 
1896     * @throws IllegalArgumentException If the {@code 'attributes'} parameter has length zero.
1897     * 
1898     * @see InnerTagKeyException#check(String[])
1899     * @see #AV(String)
1900     * @see AttrRegEx#KEY_VALUE_REGEX
1901     */
1902    public boolean hasAND(boolean checkAttributeStringsForErrors, String... attributes)
1903    {
1904        ClosingTagNodeException.check(this);
1905
1906        // If no attributes are passed to 'attributes' parameter, throw exception.
1907        if (attributes.length == 0) throw new IllegalArgumentException
1908            ("Input variable-length String[] array parameter, 'attributes', has length zero.");
1909
1910        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
1911        // are simply too short to have any attribute-value pairs.
1912        // 4 (characters) are: '<', '>', ' ' and 'X'
1913        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
1914        //
1915        // This TagNode doesn't have any attributes in it.
1916        // There is no need to check anything, so return FALSE immediately ("AND" fails)
1917        if (this.str.length() < (this.tok.length() + 4)) return false;
1918
1919        if (checkAttributeStringsForErrors) InnerTagKeyException.check(attributes);
1920
1921        // This is a temporary data-structure.  It will keep a list of the inner-tags from the
1922        // input-parameter string-array 'attributes' stored in lowe-case format.  These elements
1923        // will be slowly removed as they are found in the HTML Element.  Once the size() of this
1924        // TreeSet is zero, it would imply that ALL INNER-TAGS WERE FOUND, and then TRUE would be
1925        // returned.
1926        TreeSet<String> tlcAttributes = new TreeSet<>();
1927
1928        for (String attribute : attributes) tlcAttributes.add(attribute.toLowerCase());
1929
1930        // Get all inner-tag key-value pairs.  Every time the "attribute-name" matches one of the
1931        // elements in the temporary TreeSet data-structure, it means another one of the requested
1932        // attributes has been found.  When they are all found, we can return TRUE.
1933        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
1934
1935        String innerTagKey;
1936        while (keyValueInnerTags.find())
1937            if (tlcAttributes.contains(innerTagKey = keyValueInnerTags.group(2).toLowerCase()))
1938            {
1939                tlcAttributes.remove(innerTagKey);                              // Found another match with the input String[] parameter
1940                if (tlcAttributes.size() == 0) return true;                     // If all attributes have been found, return TRUE immediately
1941            }
1942
1943        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attrbutes"
1944        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
1945        // real matches)
1946
1947        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
1948            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
1949                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
1950                    if (tlcAttributes.contains(keyOnlyInnerTag = keyOnlyInnerTag.toLowerCase()))
1951                    {
1952                        tlcAttributes.remove(keyOnlyInnerTag);                  // Found another match with the input String[] parameter
1953                        if (tlcAttributes.size() == 0) return true;             // If all attributes have been found, return TRUE immediately
1954                    }
1955
1956        // Some of the input-parameter string-array 'attributes' were NOT FOUND (or else TRUE would
1957        // have already been returned).
1958        // Since this is an 'AND' return false.
1959        return false;
1960    }
1961
1962    /**
1963     * This method provides a quick way of testing if {@code 'this'} HTML {@code TagNode} contains
1964     * certain inner-tags with specified <B STYLE="color: red;">names</B>.
1965     * 
1966     * <BR /><BR />
1967     * This uses {@code logical OR} meaning that {@code TRUE} shall be returned if any of the
1968     * inner tags provided in the {@code 'attributes'} parameter list are found in {@code 'this'}
1969     * instance of {@code TagNode}.
1970     * 
1971     * <BR /><BR /><IMG SRC="hasOR.png" CLASS=JDIMG ALT="Example">
1972     * 
1973     * @param attributes This is a list of HTML Element Attribute-<B STYLE="color: red;">Names</B> 
1974     * or "Inner Tags" as they are called in this Search and Scrape Package.
1975     * 
1976     * @param checkAttributeStringsForErrors <EMBED CLASS="external-html" DATA-FILE-ID="TNHASBOOL">
1977     * 
1978     * @return <B>TRUE</B> if at least one of these attribute-<B STYLE="color: red;">names</B> are
1979     * present in {@code 'this'} instance, and <B>FALSE</B> otherwise.
1980     * 
1981     * <BR /><BR /><B>NOTE:</B> If this method is passed a zero-length {@code String}-array to the
1982     * {@code 'attributes'} parameter, this method shall exit immediately and return <B>FALSE</B>.
1983     * 
1984     * @throws InnerTagKeyException If any of the {@code 'attributes'} are not valid HTML
1985     * attributes, <I><B>and</B></I> the user has passed <B>TRUE</B> to parameter
1986     * {@code checkAttributeStringsForErrors}.
1987     * 
1988     * @throws NullPointerException If any of the {@code 'attributes'} are null.
1989     * 
1990     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
1991     * 
1992     * @throws IllegalArgumentException If the {@code 'attributes'} parameter has length zero.
1993     * 
1994     * @see InnerTagKeyException#check(String[])
1995     * @see #AV(String)
1996     * @see AttrRegEx#KEY_VALUE_REGEX
1997     */
1998    public boolean hasOR(boolean checkAttributeStringsForErrors, String... attributes)
1999    {
2000        ClosingTagNodeException.check(this);
2001
2002        // If no attributes are passed to 'attributes' parameter, throw exception.
2003        if (attributes.length == 0) throw new IllegalArgumentException
2004            ("Input variable-length String[] array parameter, 'attributes', has length zero.");
2005
2006        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2007        // are simply too short to have any attribute-value pairs.
2008        // 4 (characters) are: '<', '>', ' ' and 'X'
2009        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
2010        //
2011        // This TagNode doesn't have any attributes in it.
2012        // There is no need to check anything, so return FALSE immediately ("OR" fails)
2013        if (this.str.length() < (this.tok.length() + 4)) return false;
2014
2015        if (checkAttributeStringsForErrors) InnerTagKeyException.check(attributes);
2016
2017        // Get all inner-tag key-value pairs.  If even one of these is inside the 'attributes'
2018        // input-parameter string-array,  Then we must return true, since this is OR
2019        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2020
2021        while (keyValueInnerTags.find())
2022        {
2023            String innerTagKey = keyValueInnerTags.group(2);                    // Retrieve the key of the key-value pair
2024            for (String attributeToOR : attributes)                             // Iterate every element of the String[] 'attributes' parameter
2025                if (innerTagKey.equalsIgnoreCase(attributeToOR))                // Does the input parameter String-array match with an inner-tag key?
2026                    return true;                                                // OR: If there is any match, return TRUE immediately
2027        }
2028
2029        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
2030        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
2031        // real matches)
2032
2033        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
2034            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
2035                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
2036                    for (String attributeToOR : attributes)                     // Iterate all the input-parameter String-array attributes
2037                        if (keyOnlyInnerTag.equalsIgnoreCase(attributeToOR))    // If even one of them
2038                            return true;                                        // NAND: If there is any match, return TRUE immediately
2039
2040        // If NIETHER the Key-Value attributes, NOR the Key-Only attributes had any matches with
2041        // the input String-Array parameter "attributes", the OR-comparison has failed, and we
2042        // must return false.
2043        return false;
2044    }
2045
2046    // ********************************************************************************************
2047    // has methods - extended, variable attribute-names
2048    // ********************************************************************************************
2049
2050    /**
2051     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2052     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>)  that are
2053     * identical (using case-insensitive comparisons) to input-parameter
2054     * {@code String attributeName}.
2055     * 
2056     * <EMBED CLASS='external-html' DATA-FILE-ID=TNHASNOTE>
2057     * 
2058     * <BR /><BR /><IMG SRC='has.png' CLASS=JDIMG ALT='example'>
2059     * 
2060     * @param attributeName A {@code String} parameter that may be the
2061     * <B STYLE="color: red;">name</B> of any attribute / inner-tag to be found inside
2062     * {@code 'this'} HTML Element.
2063     * 
2064     * <BR /><BR /><B>NOTE:</B> No validity checks are performed about this parameter.  If the 
2065     * {@code 'attributeName'} does not represent a valid attribute, it likely will
2066     * just fail to find a match - and the method will exit gracefully, returning <B>FALSE</B>.
2067     * 
2068     * @return Will return <B>TRUE</B> if there are any inner-tag's whose
2069     * <B STYLE="color: red;">name</B> equals parameter {@code 'attributeName'} using a
2070     * case-insensitive {@code String}-equality comparison (and <B>FALSE</B> otherwise).
2071     * 
2072     * @see AttrRegEx#KEY_VALUE_REGEX
2073     */
2074    public boolean has(String attributeName)
2075    {
2076        // Closing HTML Elements may not have attribute-names or values.
2077        // Exit gracefully, and immediately.
2078        if (this.isClosing) return false;
2079
2080        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2081        // are simply too short to have such an attribute-value pair.
2082        // 4 (characters) are: '<', '>', ' ' and 'X'
2083        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
2084        if (this.str.length() < (this.tok.length() + 4)) return false;
2085
2086        // Get all inner-tag key-value pairs.  If any are 'equal' to parameter attributeName,
2087        // return TRUE immediately.
2088        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2089
2090        // the matcher.group(2) has the key (not the value)
2091        while (keyValueInnerTags.find())
2092            if (attributeName.equalsIgnoreCase(keyValueInnerTags.group(2)))
2093                return true;
2094
2095        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
2096        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
2097        // real matches)
2098
2099        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
2100            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
2101                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
2102                    if (attributeName.equalsIgnoreCase(keyOnlyInnerTag)) return true;
2103
2104        // A match was not found in either the "key-value pairs", or the boolean "key-only list."
2105        return false;
2106    }
2107
2108    /**
2109     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2110     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>) that is accepted
2111     * by the {@code Predicate<String>} parameter {@code 'attributeNameTest'}.
2112     * 
2113     * <EMBED CLASS='external-html' DATA-FILE-ID=TNHASNOTE>
2114     * 
2115     * @param attributeNameTest A {@code String Predicate} parameter that is used to accept or
2116     * reject a match with the list of attribute <B STYLE="color: red;">keys</B> or
2117     * boolean-attribute <B STYLE="color: red;">keys</B> in {@code 'this'} HTML Element.
2118     * 
2119     * <BR /><BR /><B>NOTE:</B> The {@link StrFilter} in {@code package Torello.Java} is
2120     * capable of generating a rather wide range of test-{@code Predicate's} using
2121     * regular-expressions. This is one option to think about when using this method.
2122     * 
2123     * @return Will return <B>TRUE</B> if there are any inner-tag's whose
2124     * <B STYLE="color: red;">name</B> is accepted by the input-parameter {@code
2125     * Predicate 'attributeNameTest'} (and <B>FALSE</B> otherwise).
2126     * 
2127     * @see AttrRegEx#KEY_VALUE_REGEX
2128     * @see StrFilter
2129     */
2130    public boolean has(Predicate<String> attributeNameTest)
2131    {
2132        // Closing HTML Elements may not have attribute-names or values.
2133        // Exit gracefully, and immediately.
2134        if (this.isClosing) return false;
2135
2136        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2137        // are simply too short to have such an attribute-value pair.
2138        // 4 (characters) are: '<', '>', ' ' and 'X'
2139        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
2140        if (this.str.length() < (this.tok.length() + 4)) return false;
2141
2142        // Get all inner-tag key-value pairs.  If any of them match with the 'attributeNameTest'
2143        // Predicate, return TRUE immediately.
2144        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2145
2146        // the matcher.group(2) has the key (not the value)
2147        while (keyValueInnerTags.find())
2148            if (attributeNameTest.test(keyValueInnerTags.group(2)))
2149                return true;
2150
2151        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
2152        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
2153        // real matches)
2154
2155        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
2156            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
2157                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
2158                    if (attributeNameTest.test(keyOnlyInnerTag))
2159                        return true;
2160
2161        // A match was not found in either the "key-value pairs", or the boolean "key-only list."
2162        return false;
2163    }
2164
2165    /**
2166     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2167     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>) that are accepted
2168     * by the Java Regular-Expression parameter {@code 'attributeNameTest'}.
2169     * 
2170     * <EMBED CLASS='external-html' DATA-FILE-ID=TNHASNOTE>
2171     * 
2172     * @param attributeNameTest A {@code java.util.regex.Pattern} regular-expression  that is used
2173     * to accept or reject a match with the list of attribute <B STYLE="color: red;">keys</B> or 
2174     * boolean-attribute <B STYLE="color: red;">keys</B> in {@code 'this'} HTML Element 
2175     * 
2176     * @return Will return <B>TRUE</B> if there are any inner-tag's whose
2177     * <B STYLE="color: red;">name</B> is accepted by the input-parameter regular-expression
2178     * {@code 'attributeNameTest'} (and <B>FALSE</B> otherwise).
2179     * 
2180     * @see AttrRegEx#KEY_VALUE_REGEX
2181     */
2182    public boolean has(Pattern attributeNameTest)
2183    {
2184        // Closing HTML Elements may not have attribute-names or values.
2185        // Exit gracefully, and immediately.
2186        if (this.isClosing) return false;
2187
2188        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2189        // are simply too short to have such an attribute-value pair.
2190        // 4 (characters) are: '<', '>', ' ' and 'X'
2191        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
2192        if (this.str.length() < (this.tok.length() + 4)) return false;
2193
2194        // Get all inner-tag key-value pairs.  If any of them match with the 'attributeNameTest'
2195        // Regular-Expression, return TRUE immediately.
2196        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2197
2198        // the matcher.group(2) has the key (not the value)
2199        while (keyValueInnerTags.find())
2200            if (attributeNameTest.matcher(keyValueInnerTags.group(2)).find())
2201                return true;
2202
2203        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
2204        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
2205        // real matches)
2206
2207        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
2208            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
2209                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
2210                    if (attributeNameTest.matcher(keyOnlyInnerTag).find())
2211                        return true;
2212
2213        // A match was not found in either the "key-value pairs", or the boolean "key-only list."
2214        return false;
2215    }
2216
2217    /**
2218     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2219     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>) that are accepted
2220     * by the {@code TextComparitor} &amp; Compare-{@code String's} parameters.
2221     * 
2222     * <EMBED CLASS='external-html' DATA-FILE-ID=TNHASNOTE>
2223     * 
2224     * @param tc An instance of {@code TextComparitor} that is used (in conjunction with the
2225     * {@code 'compareStrs'}) to accept or reject a match with the list of attribute
2226     * <B STYLE="color: red;">keys</B> or boolean-attribute <B STYLE="color: red;">keys</B> in
2227     * {@code 'this'} HTML Element 
2228     * 
2229     * @param compareStrs This is the list of comparison-{@code String's} with which the named
2230     * {@code TextComparitor} performs the tests against an input 'test-{@code String}.'  For more
2231     * information, please review the list of {@code TextComparitor} static-instances defined in
2232     * that class.
2233     * 
2234     * @return Will return <B>TRUE</B> if there are any inner-tag's whose
2235     * <B STYLE="color: red;">name</B> is accepted by the input-parameter {@code TextComparitor}
2236     * (and <B>FALSE</B> otherwise).
2237     * 
2238     * @see AttrRegEx#KEY_VALUE_REGEX
2239     * @see TextComparitor
2240     */
2241    public boolean has(TextComparitor tc, String... compareStrs)
2242    {
2243        // Closing HTML Elements may not have attribute-names or values.
2244        // Exit gracefully, and immediately.
2245        if (this.isClosing) return false;
2246
2247        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2248        // are simply too short to have such an attribute-value pair.
2249        // 4 (characters) are: '<', '>', ' ' and 'X'
2250        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X>
2251        if (this.str.length() < (this.tok.length() + 4)) return false;
2252
2253        // Get all inner-tag key-value pairs.  If any of them match with the TextComparitor & compareStrs,
2254        // return TRUE immediately.
2255        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2256
2257        // the matcher.group(2) has the key (not the value)
2258        while (keyValueInnerTags.find())
2259            if (tc.test(keyValueInnerTags.group(2), compareStrs))
2260                return true;
2261
2262        // Also check the "Boolean Attributes" also known as the "Key-Word Only Attributes"
2263        // Use the "Inverse Reg-Ex Matcher" (which matches all the strings that are "between" the
2264        // real matches)
2265
2266        for (String unMatchedStr : AttrRegEx.KEY_VALUE_REGEX.split(this.str))   // INVERSE-matches of the key-value Reg-Ex
2267            for (String keyOnlyInnerTag : unMatchedStr.split("\\s+"))           // white-space split (connected chars)
2268                if ((keyOnlyInnerTag = keyOnlyInnerTag.trim()).length() > 0)    // Just-In-Case, usually not necessary
2269                    if (tc.test(keyOnlyInnerTag, compareStrs))
2270                        return true;
2271
2272        // A match was not found in either the "key-value pairs", or the boolean "key-only list."
2273        return false;
2274    }
2275
2276    // ********************************************************************************************
2277    // hasValue(...) methods
2278    // ********************************************************************************************
2279
2280    /**
2281     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2282     * an attribute-<B STYLE="color: red;">value</B> that is identical (using case-insensitive
2283     * comparisons) to input-parameter {@code String attributeName}.
2284     * 
2285     * <EMBED CLASS="external-html" DATA-FILE-ID=TNHASVAL1>
2286     * 
2287     * @param attributeValue A {@code String} parameter that may be the
2288     * <B STYLE="color: red;">value</B> of any attribute / inner-tag to be found inside
2289     * {@code 'this'} HTML Element.
2290     * 
2291     * <BR /><BR /><B>NOTE:</B> This parameter is not checked for validity.  If this {@code String}
2292     * is not a valid HTML Attribute-<B STYLE="color:red;">Name</B>, likely it will not match the
2293     * regular-expression matcher.  In such cases, the method returns null.
2294     *
2295     * @param retainQuotes The parameter is required to inform the program whether or not the
2296     * quotation marks should be included along with the returned <B STYLE="color: red;">value</B>
2297     * inside the {@code Map.Entry}.  This can sometimes be useful, for example, when complicated
2298     * script-containing {@code TagNode's} are involved.
2299     *
2300     * @param preserveKeysCase When this parameter is <B>TRUE</B>, the program will not invoke
2301     * {@code String.toLowerCase()} on the {@code Map.Entry's} <B STYLE="color: red;">key</B>.  If
2302     * <B>FALSE</B>, then the returned {@code Map.Entry} will have a
2303     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>) that is strictly
2304     * in lower-case format. 
2305     * 
2306     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
2307     *
2308     * @return Returns a {@code Map.Entry<String, String>} <B STYLE="color: red;">Key-Value</B>
2309     * Pair - <B><I>if and only if</I></B> there are any inner-tag's whose
2310     * <B STYLE="color: red;">value</B> is equal to input-parameter {@code 'attributeValue'} using a
2311     * case-insensitive {@code String}-equality comparison.
2312     * 
2313     * <BR /><BR />If no such match is found, this method will return {@code 'null'}
2314     *
2315     * @see AttrRegEx#KEY_VALUE_REGEX
2316     */
2317    public Map.Entry<String, String> hasValue
2318        (String attributeValue, boolean retainQuotes, boolean preserveKeysCase)
2319    {
2320        // Closing HTML Elements may not have attribute-names or values.
2321        // Exit gracefully, and immediately.
2322        if (this.isClosing) return null;
2323
2324        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2325        // are simply too short to have such an attribute-value pair.
2326        // 5 (characters) are: '<', '>', ' ', 'X' and '=' 
2327        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X=>
2328        if (this.str.length() < (this.tok.length() + 5)) return null;
2329
2330        // Get all inner-tag key-value pairs.
2331        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2332
2333        while (keyValueInnerTags.find())
2334        {
2335            // Matcher.group(3) has the key's value, of the inner-tag key-value pair
2336            // (matcher.group(2) has the key's name)
2337            String foundAttributeValue = keyValueInnerTags.group(3);
2338
2339            // The comparison must be performed on the version of the value that DOES NOT HAVE the
2340            // surrounding quotation-marks
2341            String foundAttributeValueNoQuotes = StringParse.ifQuotesStripQuotes(foundAttributeValue);
2342
2343            // Perform the comparison, making sure to ignore case when comparing.
2344            if (attributeValue.equalsIgnoreCase(foundAttributeValueNoQuotes))
2345
2346                // matcher.group(2) has the key's name, not the value.  This is returned via the
2347                // Map.Entry key
2348                return retainQuotes
2349                    ? new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2350                            ? keyValueInnerTags.group(2)
2351                            : keyValueInnerTags.group(2).toLowerCase(),
2352                        foundAttributeValue)
2353                    : new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2354                            ? keyValueInnerTags.group(2)
2355                            : keyValueInnerTags.group(2).toLowerCase(),
2356                        foundAttributeValueNoQuotes);
2357        }
2358
2359        // No match was identified, return null.
2360        return null;
2361    }
2362
2363    /**
2364     * Will search this {@code TagNode} to determine if any inner-tag key-value pairs have a 
2365     * an attribute-<B STYLE="color: red;">value</B> that is accepted by the
2366     * {@code String-Predicate} parameter {@code 'attributeValueTest'}.
2367     * 
2368     * <EMBED CLASS="external-html" DATA-FILE-ID=TNHASVAL1>
2369     *
2370     * @param attributeValueTest A {@code String-Predicate} parameter that is used to accept or
2371     * reject a match with the list of all attribute <B STYLE="color: red;">values</B> in
2372     * {@code 'this'} HTML Element
2373     *
2374     * <BR /><BR /><B>NOTE:</B> The {@link StrFilter} in {@code package Torello.Java} is capable
2375     * of generating a rather wide range of test-{@code Predicate's} using regular-expressions.  
2376     * This is one option to think about when using this method.
2377     *
2378     * @param retainQuotes The parameter is required to inform the program whether or not the
2379     * quotation marks should be included along with the returned <B STYLE="color: red;">value</B>
2380     * inside the {@code Map.Entry}.  This can sometimes be useful, for example, when complicated
2381     * script-containing {@code TagNode's} are involved.
2382     *
2383     * @param preserveKeysCase When this parameter is <B>TRUE</B>, the program will not invoke
2384     * {@code String.toLowerCase()} on the {@code Map.Entry's} <B STYLE="color: red;">key</B>.  If
2385     * <B>FALSE</B>, then the returned {@code Map.Entry} will have a <B STYLE="color: red;">key</B>
2386     * (attribute-<B STYLE="color: red;">name</B>)  that is strictly in lower-case format.
2387     * 
2388     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
2389     *
2390     * @return Returns a {@code Map.Entry<String, String>} <B STYLE="color: red;">Key-Value</B>
2391     * Pair - <B><I>if and only if</I></B> there are any inner-tag's whose
2392     * <B STYLE="color: red;">value</B> is accepted by the input-parameter {@code Predicate}-test
2393     * {@code 'attributeNameTest'}.
2394     * 
2395     * <BR /><BR />If no such match is found, this method will return {@code 'null'}
2396     *
2397     * @see AttrRegEx#KEY_VALUE_REGEX
2398     * @see StrFilter
2399     */
2400    public Map.Entry<String, String> hasValue
2401        (Predicate<String> attributeValueTest, boolean retainQuotes, boolean preserveKeysCase)
2402    {
2403        // Closing HTML Elements may not have attribute-names or values.
2404        // Exit gracefully, and immediately.
2405        if (this.isClosing) return null;
2406
2407        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2408        // are simply too short to have such an attribute-value pair.
2409        // 5 (characters) are: '<', '>', ' ', 'X' and '=' 
2410        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X=>
2411        if (this.str.length() < (this.tok.length() + 5)) return null;
2412
2413        // Get all inner-tag key-value pairs.  If any are 'equal' to parameter attributeName,
2414        // return TRUE immediately.
2415        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2416
2417        while (keyValueInnerTags.find())
2418        {
2419            // Matcher.group(3) has the key's value, of the inner-tag key-value pair
2420            // (matcher.group(2) has the key's name)
2421            String foundAttributeValue = keyValueInnerTags.group(3);
2422
2423            // The comparison must be performed on the version of the value that DOES NOT HAVE the
2424            // surrounding quotation-marks
2425            String foundAttributeValueNoQuotes = StringParse.ifQuotesStripQuotes(foundAttributeValue);
2426
2427            // Matcher.group(3) has the key-value, make sure to remove quotation marks (if present) before comparing.
2428            if (attributeValueTest.test(foundAttributeValueNoQuotes))
2429
2430                // matcher.group(2) has the key's name, not the value.  This is returned via the
2431                // Map.Entry key
2432                return retainQuotes
2433                    ? new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2434                            ? keyValueInnerTags.group(2)
2435                            : keyValueInnerTags.group(2).toLowerCase(),
2436                        foundAttributeValue)
2437                    : new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2438                            ? keyValueInnerTags.group(2)
2439                            : keyValueInnerTags.group(2).toLowerCase(),
2440                        foundAttributeValueNoQuotes);
2441        }
2442
2443        // No match was identified, return null.
2444        return null;
2445    }
2446
2447    /**
2448     * Will search this {@code TagNode} to determine if any inner-tag
2449     * <B STYLE="color: red;">key-value</B> pairs have an
2450     * attribute-<B STYLE="color: red;">value</B> that is accepted by the Java Regular-Expression
2451     * parameter {@code 'attributeValueTest'}.
2452     * 
2453     * <EMBED CLASS="external-html" DATA-FILE-ID=TNHASVAL1>
2454     *
2455     * @param attributeValueTest A {@code java.util.regex.Pattern} parameter that is used to accept
2456     * or reject a match with the list of all attribute <B STYLE="color: red;">values</B> in 
2457     * {@code 'this'} HTML Element
2458     *
2459     * @param retainQuotes The parameter is required to inform the program whether or not the
2460     * quotation marks should be included along with the returned <B STYLE="color: red;">value</B>
2461     * inside the {@code Map.Entry}.  This can sometimes be useful, for example, when complicated
2462     * script-containing {@code TagNode's} are involved.
2463     *
2464     * @param preserveKeysCase When this parameter is <B>TRUE</B>, the program will not invoke
2465     * {@code String.toLowerCase()} on the {@code Map.Entry's} <B STYLE="color: red;">key</B>.
2466     * If <B>FALSE</B>, then the returned {@code Map.Entry} will have a
2467     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>) that is strictly
2468     * in lower-case format. 
2469     * 
2470     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
2471     *
2472     * @returnReturns a {@code Map.Entry<String, String>} <B STYLE="color: red;">Key-Value</B>
2473     * Pair - <B><I>if and only if</I></B> there are any inner-tag's whose
2474     * <B STYLE="color: red;">value</B> is accepted by the input-parameter regular-expression
2475     * {@code 'attributeValueTest'}.
2476     * 
2477     * <BR /><BR />If no such match is found, this method will return {@code 'null'}
2478     *
2479     * @see AttrRegEx#KEY_VALUE_REGEX
2480     */
2481    public Map.Entry<String, String> hasValue
2482        (Pattern attributeValueTest, boolean retainQuotes, boolean preserveKeysCase)
2483    {
2484        // Closing HTML Elements may not have attribute-names or values.
2485        // Exit gracefully, and immediately.
2486        if (this.isClosing) return null;
2487
2488        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2489        // are simply too short to have such an attribute-value pair.
2490        // 5 (characters) are: '<', '>', ' ', 'X' and '=' 
2491        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X=>
2492        if (this.str.length() < (this.tok.length() + 5)) return null;
2493
2494        // Get all inner-tag key-value pairs.  If any are 'equal' to parameter attributeName, return
2495        // TRUE immediately.
2496        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2497
2498        while (keyValueInnerTags.find())
2499        {
2500            // Matcher.group(3) has the key's value, of the inner-tag key-value pair
2501            // (matcher.group(2) has the key's name)
2502            String foundAttributeValue = keyValueInnerTags.group(3);
2503
2504            // The comparison must be performed on the version of the value that DOES NOT HAVE the
2505            // surrounding quotation-marks
2506            String foundAttributeValueNoQuotes = StringParse.ifQuotesStripQuotes(foundAttributeValue);
2507
2508            // Matcher.group(3) has the key-value, make sure to remove quotation marks (if present)
2509            // before comparing.
2510            if (attributeValueTest.matcher(foundAttributeValueNoQuotes).find())
2511
2512                // matcher.group(2) has the key's name, not the value.  This is returned via the 
2513                // Map.Entry key
2514                return retainQuotes
2515                    ? new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2516                            ? keyValueInnerTags.group(2)
2517                            : keyValueInnerTags.group(2).toLowerCase(),
2518                        foundAttributeValue)
2519                    : new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2520                            ? keyValueInnerTags.group(2)
2521                            : keyValueInnerTags.group(2).toLowerCase(),
2522                        foundAttributeValueNoQuotes);
2523        }
2524
2525        // No match was identified, return null.
2526        return null;
2527    }
2528
2529    /**
2530     * Will search this {@code TagNode} to determine if any inner-tag
2531     * <B STYLE="color: red;">key-value</B> pairs have a an
2532     * attribute-<B STYLE="color: red;">value</B> that is accepted by the {@code TextComparitor}
2533     * &amp; Compare-{@code String's} parameters.
2534     * 
2535     * <EMBED CLASS="external-html" DATA-FILE-ID=TNHASVAL1>
2536     *
2537     * @param tc An instance of {@code TextComparitor} that is used (in conjunction with the
2538     * {@code 'compareStrs'}) to accept or reject a match with the list of
2539     * attribute-<B STYLE="color: red">values</B> in {@code 'this'} HTML Element 
2540     *
2541     * @param compareStrs These are the standard compare-{@code String's} to be used as reference
2542     * with the {@code TextComparitor} provided.
2543     *
2544     * @param retainQuotes The parameter is required to inform the program whether or not the
2545     * quotation marks should be included along with the returned <B STYLE="color: red;">value</B>
2546     * inside the {@code Map.Entry}.  This can sometimes be useful, for example, when complicated
2547     * script-containing {@code TagNode's} are involved.
2548     *
2549     * @param preserveKeysCase When this parameter is <B>TRUE</B>, the program will not invoke
2550     * {@code String.toLowerCase()} on the {@code Map.Entry's} <B STYLE="color: red;">key</B>.
2551     * If <B>FALSE</B>, then the returned {@code Map.Entry} will have a
2552     * <B STYLE="color: red;">key</B> (attribute-<B STYLE="color: red;">name</B>)  that is strictly
2553     * in lower-case format. 
2554     * 
2555     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
2556     *
2557     * @return Returns a {@code Map.Entry<String, String>} <B STYLE="color: red;">Key-Value</B>
2558     * Pair - <B><I>if and only if</I></B> there are any inner-tag's whose
2559     * <B STYLE="color: red;">value</B> is accepted by the input-parameter {@code TextComparitor}.
2560     * 
2561     * <BR /><BR />If no such match is found, this method will return {@code 'null'}
2562     * 
2563     * @see AttrRegEx#KEY_VALUE_REGEX
2564     * @see TextComparitor
2565     */
2566    public Map.Entry<String, String> hasValue
2567        (boolean retainQuotes, boolean preserveKeysCase, TextComparitor tc, String... compareStrs)
2568    {
2569        // Closing HTML Elements may not have attribute-names or values.
2570        // Exit gracefully, and immediately.
2571        if (this.isClosing) return null;
2572
2573        // OPTIMIZATION: TagNode's whose String-length is less than this computed length 
2574        // are simply too short to have such an attribute-value pair.
2575        // 5 (characters) are: '<', '>', ' ', 'X' and '=' 
2576        // SHORTEST POSSIBLE SUCH-ELEMENT: <DIV X=>
2577        if (this.str.length() < (this.tok.length() + 5)) return null;
2578
2579        // Get all inner-tag key-value pairs.  If any are 'equal' to parameter attributeName, 
2580        // return TRUE immediately.
2581        Matcher keyValueInnerTags = AttrRegEx.KEY_VALUE_REGEX.matcher(this.str);
2582
2583        while (keyValueInnerTags.find())
2584        {
2585            // Matcher.group(3) has the key's value, of the inner-tag key-value pair
2586            // (matcher.group(2) has the key's name)
2587            String foundAttributeValue = keyValueInnerTags.group(3);
2588
2589            // The comparison must be performed on the version of the value that DOES NOT HAVE the
2590            // surrounding quotation-marks
2591            String foundAttributeValueNoQuotes = StringParse.ifQuotesStripQuotes(foundAttributeValue);
2592
2593            // Matcher.group(3) has the key-value, make sure to remove quotation marks (if present)
2594            // before comparing.
2595            if (tc.test(foundAttributeValueNoQuotes, compareStrs))
2596
2597                // matcher.group(2) has the key's name, not the value.  This is returned via the
2598                // Map.Entry key
2599                return retainQuotes
2600                    ? new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2601                            ? keyValueInnerTags.group(2)
2602                            : keyValueInnerTags.group(2).toLowerCase(),
2603                        foundAttributeValue)
2604                    : new AbstractMap.SimpleImmutableEntry<String, String>(preserveKeysCase
2605                            ? keyValueInnerTags.group(2)
2606                            : keyValueInnerTags.group(2).toLowerCase(),
2607                        foundAttributeValueNoQuotes);
2608        }
2609
2610        // No match was identified, return null.
2611        return null;
2612    }
2613
2614    // ********************************************************************************************
2615    // getInstance()
2616    // ********************************************************************************************
2617
2618    /**
2619     * This is an "identical" method to the one found in {@code class HTMLTags}, specifically: 
2620     * {@code public static TagNode hasTag(String tag, TC openOrClosed) { ... } }, except that this
2621     * method will do some parameter error-checking, and throw exceptions if possible, rather than
2622     * simply returning null.
2623     *
2624     * <BR /><BR /><B>NOTE:</B> All three classes which inherit {@code class HTMLNode} - including 
2625     * classes {@code TagNode, TextNode} and {@code CommentNode} are immutable.  The instances
2626     * <B><I>cannot be changed</I></B>.  In order to update, tweak, or modify an HTML page, you
2627     * must instantiate or retrieve another instance of {@code HTMLNode}, and replace the position
2628     * in the containing-{@code Vector} with a new node.  This method, {@code 'getInstance(...)'}
2629     * interacts with the {@code class HTMLTags} to retrieve one of the pre-instantiated
2630     * {@code TC.OpeningTags} or {@code TC.ClosingTags TagNode's}.  It is possible to 're-use' the
2631     * same instance of a {@code TagNode} in different pages and different {@code Vector's} <I>in
2632     * the same way that Java {@code String's} can be re-used</I>, exactly because they are
2633     * immutable.
2634     *
2635     * <BR /><BR />If a {@code TagNode} instance is used in different pages, the in-ability to
2636     * change a {@code TagNode's} contents is what allows multiple, different pages to use them in
2637     * multiple {@code Vector's} without worrying about their contents becoming affected by
2638     * concurrency issues. {@code class HTMLTags} maintains 4 complete lists of
2639     * already-instantiated {@code TagNode's} (upper-case, lower-case, opening-tag, closing-tag) in
2640     * a {@code java.util.TreeMap<String, TagNode>} since usually a very high percentage of HTML
2641     * Element's are elements with no attribute information at all.
2642     *
2643     * <BR /><BR /><B>ALSO:</B> It should be obvious to the reader, by now, that keeping
2644     * 'pre-instantiated' {@code TagNode} elements that <B>do contain attributes</B> would be
2645     * orders of magnitude more costly (and difficult}.  This practice is not performed by the
2646     * Java-HTML library.
2647     *
2648     * <BR /><BR /><B>SIMILARLY:</B> All pre-instantiated HTML {@code TagNode's} have <B>ZERO</B>
2649     * attributes or "inner-tags" inside. To generate a {@code TagNode} with attributes, use the
2650     * {@code class TagNode} constructor.   Make sure not to leave out the rest of the inner-tags
2651     * from the element-body {@code String}.
2652     *
2653     * @param tok Any valid HTML tag.
2654     * 
2655     * @param openOrClosed If {@code TC.OpeningTags} is passed, then an "open" version of the HTML
2656     * tag will be returned,
2657     * <BR />If {@code TC.ClosingTags}  is passed, then a closing version will be returned.
2658     * <BR />If {@code TC.Both} is accidentally passed - an {@code IllegalArgumentException} is
2659     * thrown.
2660     * 
2661     * @throws IllegalArgumentException If parameter {@code TC openOrClose} is {@code null} or
2662     * {@code TC.Both}
2663     * 
2664     * @throws HTMLTokException If the parameter {@code String tok} is not a valid HTML-tag
2665     * 
2666     * @throws SingletonException If the token requested is a {@code singleton} (self-closing) tag,
2667     * but the Tag-Criteria {@code 'TC'} parameter is requesting a closing-version of the tag.
2668     * 
2669     * @see HTMLTags#hasTag(String, TC)
2670     * @see HTMLTags#isSingleton(String)
2671     */
2672    public static TagNode getInstance(String tok, TC openOrClosed)
2673    {
2674        if (openOrClosed == null)
2675            throw new NullPointerException("The value of openOrClosed cannot be null.");
2676
2677        if (openOrClosed == TC.Both)
2678            throw new IllegalArgumentException("The value of openOrClosed cannot be TC.Both.");
2679
2680        if (HTMLTags.isSingleton(tok) && (openOrClosed == TC.ClosingTags))
2681            throw new SingletonException(
2682                    "The value of openOrClosed is TC.ClosingTags, but unfortunately you have asked " +
2683                    "for a [" + tok + "] HTML element, which is a singleton element, and therefore " +
2684                    "cannot have a closing-tag instance."
2685                );
2686
2687        TagNode ret = HTMLTags.hasTag(tok, openOrClosed);
2688        if (ret == null)
2689            throw new HTMLTokException("The HTML-Tag provided isn't valid!\ntok: " + tok + "\nTC: " + openOrClosed);
2690        return ret;
2691    }
2692
2693    // ********************************************************************************************
2694    // Methods for "CSS Classes"
2695    // ********************************************************************************************
2696
2697    /** Convenience Method.  Invokes {@link #cssClasses()}.  Catches-Exception. */
2698    public Stream<String> cssClassesNOCSE()
2699    { try { return cssClasses(); } catch (CSSStrException e) { return Stream.empty(); } }
2700
2701    /**
2702     * Method {@code cssClasses} is to be used as a tool for splitting the results of invoking
2703     * method {@code TagNode.AV("class")} into {@code String's} - <I>separating it by
2704     * white-space.</I> The code here is a rather 'naïve' attempt to retrieve CSS {@code classes},
2705     * because it follows the <B><I>95% percent rule:</B></I> 95% of the time there should be no
2706     * problems!  More advanced HTML Generator Tools have taken advantage of HTML's 'flexibility'
2707     * and have created CSS {@code classes} that my contain anything from parenthesis {@code '('}
2708     * and {@code ')'}, to semi-colons {@code ';'}, to many other formats that proprietary
2709     * web-developers so choose.  And rather than attempting to <B>'predict'</B> all future changes
2710     * to CSS {@code Class}-Names, this method is intended to help developers retrieve basic (READ:
2711     * 'non-preprocessor') CSS {@code class}-information.  This is the nature of changing
2712     * web-internet tools.
2713     *
2714     * <BR /><BR />  Primarily, recognizing that dynamically, script and pre-processor generated
2715     * {@code class} information, <I><B>cannot be known, without having script-processing
2716     * performed</I></B>.  This package is designed for parsing, searching, and updating HTML
2717     * Pages, but it does not execute script (at least not now).  Therefore, rather than returning
2718     * possibly fallacious results - an exception throws when non-standard HTML CSS Classes are
2719     * used, instead. This does not mean that the CSS {@code String} 'attribute-value' cannot be
2720     * retrieved, but rather just that the method {@code TagNode.getAV("class")} should
2721     * <I><B>always</I></B> work and be used instead.
2722     * 
2723     * <BR /><BR />If a return value is provided it is guaranteed to be correct (unless of course
2724     * script on the page would further change or modify the {@code class}).  This HTML
2725     * Parse-and-Search Package is highly flexible when it comes to attribute-values, in that an
2726     * attribute is never checked for 'validity.'
2727     * 
2728     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDCSSCL">
2729     *
2730     * @return The {@code String}-Value of the Attribute-<B STYLE="color: red;">Name</B>
2731     * {@code 'class'} where that {@code String} has been 'split' - using {String.split(...)} and a
2732     * Match-White-Space Regular-Expression.  This is a method that will work just fine, unless
2733     * proprietary non-standard HTML 5 CSS {@code Class}-Names have been used.
2734     *
2735     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVT">
2736     *
2737     * <BR /><B>NOTE:</B> This method shall never return 'null' - even if there are no attribute
2738     * <B STYLE="color: red;">key-value</B> pairs contained by the {@code TagNode}.  If there are 
2739     * strictly zero {@code classes}, {@code Stream.empty()} shall be returned, instead.
2740     *
2741     * @throws CSSStrException <B><SPAN STYLE="color: red;">IMPORTANT:</B></SPAN> This exception
2742     * will throw if <I><B>*any*</I></B> of the identified sub-{@code String's} of the CSS 
2743     * {@code 'class'} attributes contain non-standard characters, or do not meet the requirements
2744     * of a standard CSS {@code class} name.  The Regular-Expression describing a properly-formed
2745     * CSS Name can be viewed on the internet, or in {@code class CSSStrException} field 
2746     * {@code 'VALID_CSS_CLASS_OR_NAME_TOKEN'}.  This is an unchecked-{@code RuntimeException}, so
2747     * beware before using this method on pages generated by different, proprietary Web-Design
2748     * Tools.
2749     *
2750     * <BR /><BR /><B>NOTE:</B> To avoid an exception throw and retrieve the CSS {@code 'class'} 
2751     * attribute, regardless of whether it is standard CSS, use one of the following alternatives:
2752     *
2753     * <BR /><BR /><OL CLASS="JDOL">
2754     * <LI> {@code TagNode.AV("class")} This will return the raw-contents of the
2755     *      {@code 'class'} inner-tag inside {@code 'this'} HTML Element
2756     * </LI>
2757     * <LI> {@code TagNode.cssClassesNOCSE(true)} This invocation will retrieve the contents of
2758     *      a CSS {@code 'class'} inner-tag, but if there are non-standard CSS {@code class} names,
2759     *      or invalid CSS {@code class} names, this method will exit gracefully, and just return
2760     *     'null'.
2761     * </LI>
2762     * </OL>
2763     * 
2764     * @see #cssClasses()
2765     * @see #AV(String)
2766     * @see StringParse#WHITE_SPACE_REGEX
2767     * @see CSSStrException#check(Stream)
2768     */
2769    public Stream<String> cssClasses()
2770    {
2771        // The CSS Class is just an attribute/inner-tag within an HTML Element.
2772        String classes = AV("class"); 
2773
2774        // IF the "class" attribute was not present, OR (after trimming) was empty, return "empty stream"
2775        if ((classes == null) || ((classes = classes.trim()).length() == 0)) return Stream.empty();
2776
2777        // STEP 1: Split the string on white-space
2778        // STEP 2: Check each element of the output Stream using the "CSSStrException Checker"
2779        return CSSStrException.check(StringParse.WHITE_SPACE_REGEX.splitAsStream(classes));
2780    }
2781
2782    /**
2783     * This method sets the CSS {@code Class} attribute-<B STYLE="color: red;">value</B>, applying
2784     * either one, or many, CSS {@code Class}-Names.
2785     * 
2786     * <BR /><BR /><IMG SRC='setCSSClasses.png' CLASS=JDIMG ALT='example'>
2787     *
2788     * @param quote It should become a habit to chose which quote to use when setting HTML
2789     * {@code TagNode} attributes.  Though CSS {@code Class}-Names are not allowed to contain
2790     * either single or double-quotes - so it should not matter which quote is used - force of
2791     * habit and consistency requires this parameter be used.  This parameter may be passed 'null'
2792     * - and if it is, the original quotation provided in the element will be used if there were
2793     * quotations, or no-quotes will be used if the {@code 'class'} inner-tag was not defined in
2794     * the element.
2795     *
2796     * @param appendOrClobber When this parameter is <B>TRUE</B>, new class-names will be appended
2797     * to any, already-existing, {@code class}-names in the {@code TagNode 'class'} 
2798     * attribute-<B STYLE="color: red;">value</B>.  When this parameter is <B>FALSE</B>, the new 
2799     * {@code class}-names will replace the current {@code TagNode's 'class'} attribute's
2800     * <B STYLE="color: red;">value</B>/definition.  If <B>FALSE</B> is used, and this
2801     * {@code TagNode} does not have a {@code 'class'} attribute-<B STYLE="color: red;">value</B>
2802     * assigned to it yet, a new one will be created.
2803     *
2804     * @param cssClasses This var-args ({@code String[]} array, or 'list-of-{@code String's}) will
2805     * accept any combination of valid CSS {@code Class}-Names.  It is important to know that the
2806     * elements of the input-parameter {@code 'cssClasses'} will each be checked for validity
2807     * based on the standard CSS {@code Class}-Naming Rules.  There is a regular-expression in the
2808     * exception {@code class CSSStrException} that depicts exactly what constitutes a valid CSS
2809     * {@code Class}-Name.
2810     *
2811     * <BR /><BR /><B>NOTE:</B> If the programmer is using a web-development tool that relies on
2812     * bypassing the browser's CSS Styling Rules, and intends to dynamically update
2813     * {@code class}-names (using some form of CSS pre-processor), then perhaps he/she may wish to
2814     * use non-standard CSS {@code class}-names in his {@code TagNode} elements.  If so, inserting
2815     * non-standard names into the {@code 'class'} attribute-<B STYLE="color: red;">value</B> of a
2816     * {@code TagNode} should be done by calling the general-purpose method
2817     * {@code TagNode.setAV("class", "any-CSS-Class-String")}. 
2818     * 
2819     * <BR /><BR />The user may also use the general-purpose
2820     * {@code TagNode.appendAV("class", "any-CSS-Class-String", boolean, SD)}, if appending a
2821     * non-standard CSS {@code class}-name {@code String}, rather than clobbering or setting it is
2822     * the desired result.  In this way, the programmer <B><I>can avoid the "Invalid CSS Class"
2823     * Exception</B></I>, which would certainly be thrown for most pre-processor CSS directives.
2824     *
2825     * @return This will return an updated {@code TagNode}.  {@code TagNode's} are immutable, and
2826     * cannot be changed.  This is identical behavior to Java's {@code class String} Libraries and
2827     * Classes. The updated {@code TagNode} that is returned will have it's original
2828     * {@code 'class'} attribute eliminated (if it had one), and a new {@code 'class'}
2829     * attribute-<B STYLE="color: red;">value</B> will be assigned to contain the CSS
2830     * {@code classes} named by the var-args {@code String}-parameter, each separated by a space
2831     * character.
2832     *
2833     * @throws CSSStrException This exception shall throw if any of the {@code 'cssClasses'} in the
2834     * var-args {@code String...} parameter do not meet the HTML 5 CSS {@code Class} naming rules.
2835     * 
2836     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
2837     * 
2838     * @throws QuotesException Quotation marks are not intended to be used in a {@code class}
2839     * inner-tag definition.  However, if there happened to be one in a non-standard
2840     * {@code class}-attribute definition and the same quote is selected to wrap the updated class
2841     * definition, this exception will throw.  If this parameter is passed 'null', and the updated
2842     * HTML Element {@code class}-attribute definition would require quotation marks, then this
2843     * exception will also throw - <I>because an unquoted
2844     * attribute-<B STYLE="color: red;">value</B> would have white-space in its definition</I>,
2845     * which is not allowed either.
2846     *
2847     * @see CSSStrException#check(String[])
2848     * @see CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
2849     * @see #appendToAV(String, String, boolean, SD)
2850     * @see #setAV(String, String, SD)
2851     */
2852    public TagNode setCSSClasses(SD quote, boolean appendOrClobber, String... cssClasses)
2853    {
2854        // Throw CSSStrException if any of the input strings are invalid CSS Class-Names.
2855        CSSStrException.check(cssClasses);
2856
2857        // Build the CSS 'class' Attribute String.  This will be inserted into the TagNode Element.
2858        StringBuilder   sb      = new StringBuilder();
2859        boolean         first   = true;
2860
2861        for (String c : cssClasses) 
2862        { sb.append((first ? "" : " ") + c.trim()); first=false; }
2863
2864        return appendOrClobber
2865            ? appendToAV("class", " " + sb.toString(), false, quote)
2866            : setAV("class", sb.toString(), quote);
2867    }
2868
2869    /**
2870     * Adds another CSS {@code Class} Name into {@code 'this' TagNode}
2871     * 
2872     * <BR /><BR /><IMG SRC="appendCSSClass.png" CLASS=JDIMG alt="example">
2873     * 
2874     * @param cssClass This is the CSS-{@code Class} name that is being inserted into
2875     * {@code 'this'} instance of {@code TagNode}
2876     * 
2877     * @param quote This is the quote to use for this attribute.  This parameter may be null, and
2878     * if it is the current-quote already in place will be used.  If this parameter is null, and
2879     * the {@code "class"} attribute is not defined in {@code 'this'} instance of {@code TagNode},
2880     * <I>then no quotation mark will be used.</I>
2881     * 
2882     * @return A newly instantiated {@code TagNode} with updated CSS {@code Class} Name(s).
2883     *
2884     * @throws CSSStrException This exception shall throw if any of the {@code 'cssClasses'} in the
2885     * var-args {@code String...} parameter do not meet the HTML 5 CSS {@code Class} naming rules.
2886     * 
2887     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
2888     *
2889     * @throws QuotesException Quotation marks within the provided {@code CSS Class} are not
2890     * compapitable with the quotation marks used by the attribute, itself.  Generally, quotations
2891     * are not used with CSS Classes; however, if a proprietary-implementation requires them, it
2892     * becomes necessary to heed the requirements set forth by a browser's parser.
2893     */
2894    public TagNode appendCSSClass(String cssClass, SD quote)
2895    {
2896        // Do a validity check on the class.  If this is "problematic" due to use of specialized / 
2897        // pre-processed CSS Class directives, use the general purpose "setAV(...)" method
2898
2899        CSSStrException.check(cssClass);
2900
2901        String curCSSClassSetting = AV("class");
2902
2903        // If there wasn't a CSS Class already defined, use "setAV(...)", 
2904        // otherwise use "appendToAV(...)"
2905
2906        if ((curCSSClassSetting == null) || (curCSSClassSetting.length() == 0))
2907            return setAV("class", cssClass, quote);
2908        else
2909            return appendToAV("class", cssClass + ' ', true, quote);
2910    }
2911
2912    // ********************************************************************************************
2913    // Methods for "CSS Style"
2914    // ********************************************************************************************
2915
2916    /**
2917     * This is a regular-expression matching for picking out all CSS {@code Style} Elements.  There
2918     * are myriad 'ways and means' when it comes to CSS {@code style} elements.  The official line
2919     * is that CSS declarations and definitions, when inside an HTML {@code TagNode} Element, 
2920     * {@code 'style'} attribute-names must obey a few simple rules.  The declaration-name - which
2921     * includes things like {@code 'width', 'height', 'font-weight', 'border',} etc... should be
2922     * first. That declaration must be followed by a colon {@code ':'} character.  Afterwards, the
2923     * {@code Style}-Definition must either end, or a semi-color {@code ';'} must be present, and 
2924     * another {@code Style}-Definition may follow.
2925     *
2926     * <BR /><BR /><B><SPAN STYLE="color: red;">SANITY-CHECK:</B></SPAN> At any point, an
2927     * invocation of the {@code TagNode.AV(...)} method, using the parameter {@code 'style'} -
2928     * <I>as in {@code TagNode.AV("style")}</I> - should always return the complete inline
2929     * CSS-Style Attribute <B STYLE="color: red;">Value</B>.  Because of the sheer-number of
2930     * variation of style-properties, this method will refrain from any type of validity-checking.
2931     * <I>So long as the rules declared by the Regular-Expression
2932     * {@link AttrRegEx#CSS_INLINE_STYLE_REGEX} are obeyed,</I> this method will return a usable
2933     * {@code java.util.Properties} instance.  
2934     * 
2935     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDCSSST">
2936     *
2937     * @return This shall return an instance of Java's {@code 'Properties'} class, and it will
2938     * contain all the style definitions and their values.
2939     *
2940     * <BR /><BR /><B>NOTE:</B> All style-declaration names <I>shall be saved using lower-case.</I>
2941     * The style attribute-values, however, <I>will not</I> be converted to lower-case.
2942     *
2943     * <BR /><BR /><B>NULL NOTE:</B> This method will not return null.  Rather, if {@code 'this'}
2944     * instance of {@code TagNode} Element does not have a style attribute, an empty (non-null)
2945     * instance of {@code java.util.Properties} which contains zero CSS Style-Declaration Key-Value
2946     * Pairs will be returned instead.
2947     *
2948     * @see AttrRegEx#CSS_INLINE_STYLE_REGEX
2949     * @see #AV(String)
2950     */
2951    public Properties cssStyle()
2952    {
2953        Properties  p           = new Properties();
2954        String      styleStr    = AV("style");
2955            // Returns the complete attribute-value of "style" in the TagNode
2956
2957        if (styleStr == null)           return p;
2958            // There was no STYLE='...' attribute found, return empty Properties.
2959
2960        styleStr = styleStr.trim();
2961            // Standard String-trim routine
2962
2963        if (styleStr.length() == 0)     return p;
2964
2965        Matcher m = AttrRegEx.CSS_INLINE_STYLE_REGEX.matcher(styleStr);
2966
2967            // This reg-ex "iterates" over matches of strings that follow the (very basic) form:
2968            // declaration-name: declaration-string;
2969            //
2970            // Where the ":" (color), and ";" (semi-colon) are the only watched/monitored tokens.
2971            // See the reg-ex definition in "See Also" tag.
2972
2973        while (m.find()) p.put(m.group(1).toLowerCase(), m.group(2));
2974
2975            // m.group(1): declaration-name     (stuff before the ":")
2976            // m.group(2): declaration-string   (stuff before the next ";", or end-of-string)
2977            // 
2978            // For Example, if the style attribute-value was specified as:
2979            // STYLE="font-weight: bold;   margin: 1em 1em 1em 2em;   color: #0000FF"
2980            //
2981            // The returned Properties object would contain the string key-value pair elements:
2982            // "font-weight"    -> "bold"
2983            // "margin"         -> "1em 1em 1em 2em"
2984            // "color"          -> "#0000FF"
2985
2986        return p;
2987    }
2988
2989    /**
2990     * This will set {@code 'this'} instance of {@code TagNode}'s CSS-{@code Style} Attribute field
2991     * such that it contains the CSS Definition Property-Value Pairs that are contained in the
2992     * {@code java.util.Properties} input-parameter.
2993     *
2994     * @param p This should be a map between CSS Definition Property-Names to Property-Values.
2995     * <B>Some examples would include (below):</B>
2996     *
2997     * <BR /><TABLE CLASS="BRIEFSAMPLETABLE">
2998     * <TR><TH>Property-Name</TH><TH>Property-Value</TH></TR>
2999     * <TR><TD>font-weight</TD><TD>bold</TD></TR>
3000     * <TR><TD>background</TD><TD>lightyellow</TD></TR>
3001     * <TR><TD>width</TD><TD>200px</TD></TR>
3002     * <TR><TD>height</TD><TD>20em</TD></TR>
3003     * <TR><TD>border</TD><TD>5px 10px 5px 15px</TD></TR>
3004     * <TR><TD>box-shadow</TD><TD>inset 0 0 1em brown</TD></TR>
3005     * </TABLE>
3006     *
3007     * @param quote It is expected that the user will decide which quote to use -
3008     * {@code SD.SingleQuotes} or {@code SD.DoubleQuotes} - when building the new {@code TagNode}
3009     * Element.
3010     *
3011     * @param appendOrClobber When this parameter is <B>TRUE</B> new {@code style}-definitions will
3012     * be appended to any, already-existing, {@code style}-definitions in the
3013     * {@code TagNode 'style'} attribute-value.  When this parameter is <B>FALSE</B>, the new
3014     * {@code style}-definitions will replace the HTML Element {@code 'style'} attributes
3015     * current-definition (or build a new one, if {@code 'this' TagNode} does not have a
3016     * {@code 'style'} attribute).
3017     *
3018     * @return This will return a new, updated HTML {@code TagNode} element with the new values
3019     * inserted (or replaced).  The {@code class TagNode} is immutable - <I>as it is mostly a
3020     * wrapper on a Java-{@code String}</I>.
3021     *
3022     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
3023     * 
3024     * @throws CSSStrException If there is an invalid CSS Style Property Name.
3025     * 
3026     * @throws QuotesException If the style-element's quotation marks are incompatible with any
3027     * and all quotation marks employed by the style-element definitions.
3028     * 
3029     * @see CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
3030     * @see #appendToAV(String, String, boolean, SD)
3031     * @see #setAV(String, String, SD)
3032     */
3033    public TagNode setCSSStyle(Properties p, SD quote, boolean appendOrClobber)
3034    {
3035        // this is used in the "exception constructor" below (which means, it might not be used).
3036        int counter = 0;
3037
3038        // Follows the "FAIL-FAST" philosophy, and does not allow invalid CSS declaration-name
3039        // tokens.  Use TagNode.setAV("style", ...), or TagNode.appendToAV("style", ...), to
3040        // bypass exception-check.
3041        for (String key : p.stringPropertyNames())
3042
3043            if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(key))
3044            {
3045                String[] keyArr = new String[p.size()];
3046
3047                throw new CSSStrException(
3048
3049                    "CSS Style Definition Property: [" + key + "] does not conform to the valid, " +
3050                    "HTML 5, regular-expression for CSS Style Definitions Properties:\n[" + 
3051                    CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN.pattern() + "].",
3052
3053                    p.stringPropertyNames().toArray(keyArr), ++counter
3054                    // One minor "PRESUMPTION" that is the Iterator will return the elements of 
3055                    // Properties in the EXACT SAME ORDER on both creations / instantiations of the
3056                    // iterator.  Specifically: two invocations of method .iterator(), will return
3057                    // the same-list of property-keys, in the same order, BOTH TIMES.  Once for the
3058                    // for-loop, and once for the exception message.  This only matters if there is
3059                    // an exception.
3060                );
3061            }
3062            else ++counter; 
3063
3064        // Follows the "FAIL-FAST" philosophy, and does not allow "quotes-within-quote" problems
3065        // to occur.  An attribute-value surrounded by single-quotes, cannot contain a
3066        // single-quote inside, and double-within-double.
3067        counter = 0;
3068
3069        for (String key : p.stringPropertyNames())
3070
3071            if (StrCmpr.containsOR(p.get(key).toString(), ("" + quote.quote), ";"))
3072            {
3073                String[] keyArr = new String[p.size()];
3074
3075                throw new CSSStrException(
3076                    "CSS Style Definition Property: [" + key + "], which maps to style-definition " +
3077                    "property-value:\n[" + p.get(key) + "], contains either a semi-colon ';' " +
3078                    "character, or the same quotation-mark specified: [" + quote.quote + "], and is " +
3079                    "therefore not a valid assignment for a CSS Definition Property.",
3080
3081                    p   .stringPropertyNames()
3082                        .stream()
3083                        .map((String propertyName) -> p.get(propertyName))
3084                        .toArray((int i) -> new String[i]),
3085
3086                    ++counter
3087                );
3088            }
3089            else ++counter;
3090
3091        // ERROR-CHECKING: FINISHED, NOW JUST BUILD THE ATTRIBUTE-VALUE STRING (using StringBuilder),
3092        // AND INSERT IT.
3093        StringBuilder sb = new StringBuilder(); 
3094        for (String key : p.stringPropertyNames()) sb.append(key + ": " + p.get(key) + ";");
3095
3096        return appendOrClobber
3097            ? appendToAV("style", " " + sb.toString(), false, quote)
3098            : setAV("style", sb.toString(), quote);
3099    }
3100
3101    // ********************************************************************************************
3102    // Methods for "CSS ID"
3103    // ********************************************************************************************
3104
3105    /**
3106     * Convenience Method. Invokes {@link #AV(String)}.
3107     * <BR /><BR />Uses {@code String "id"}, the CSS-ID attribute-<B STYLE="color: red;">name</B>.
3108     * <BR /><BR /><IMG SRC='getID.png' CLASS=JDIMG ALT='example'>
3109     */
3110    public String getID() { return AV("id").trim(); }
3111
3112    /**
3113     * This merely sets the current CSS {@code 'ID'} Attribute <B STYLE="color: red;">Value</B>.
3114     *
3115     * @param id This is the new CSS {@code 'ID'} attribute-<B STYLE="color: red;">value</B> that
3116     * the user would like applied to {@code 'this'} instance of {@code TagNode}.
3117     * 
3118     * @param quote All operations which involve modifying an HTML attribute 
3119     * <B STYLE="color: red;">key-value</B> pair are expected to choose between the
3120     * {@code SD.SingleQuotes} or {@code SD.DoubleQuotes} option.  This can help avoid 
3121     * quotes-within-quotes problems, as HTML does not provide a way to escape quotes in
3122     * attribute-<B STYLE="color: red;">value</B> definitions.
3123     *
3124     * @return Returns a new instance of {@code TagNode} that has an updated {@code 'ID'} 
3125     * attribute-<B STYLE="color: red;">value</B>.
3126     *
3127     * @see #setAV(String, String, SD)
3128     * 
3129     * @throws IllegalArgumentException This exception shall throw if an invalid
3130     * {@code String}-token has been passed to parameter {@code 'id'}.
3131     *
3132     * <BR /><BR /><B>BYPASS NOTE:</B> If the user would like to bypass this exception-check, for
3133     * instance because he / she is using a CSS Pre-Processor, then applying the general-purpose
3134     * method {@code TagNode.setAV("id", "some-new-id")} ought to suffice.  This other method will
3135     * not apply validity checking, beyond scanning for the usual "quotes-within-quotes" problems,
3136     * which is always disallowed.
3137     *
3138     * @throws ClosingTagNodeException <EMBED CLASS="external-html" DATA-FILE-ID="CTNEX">
3139     * 
3140     * @see CSSStrException#VALID_CSS_CLASS_OR_NAME_TOKEN
3141     * @see #setAV(String, String, SD)
3142     */
3143    public TagNode setID(String id, SD quote)
3144    {
3145        if (! CSSStrException.VALID_CSS_CLASS_OR_NAME_TOKEN_PRED.test(id))
3146            throw new IllegalArgumentException(
3147                "The id parameter provide: [" + id + "], does not conform to the standard CSS " +
3148                "Names.\nEither try using the generic TagNode.setAV(\"id\", yourNewId, quote); " +
3149                "method to bypass this check, or change the value passed to the 'id' parameter here."
3150            );
3151
3152        return setAV("id", id.trim(), quote);
3153    }
3154
3155    // ********************************************************************************************
3156    // Attributes that begin with "data-..."
3157    // ********************************************************************************************
3158
3159
3160    /** Convenience Method.  Invokes {@link #AV(String)} with {@code "data-"} prepended to {@code 'dataName'} */
3161    public String dataAV(String dataName) { return AV("data-" + dataName); }
3162
3163    /**
3164     * This method will remove any HTML <B STYLE="color: red;">'data'</B> Attributes - if there are
3165     * any present.   "Data Inner-Tags" are simply the attributes inside of HTML Elements whose 
3166     * <B STYLE="color: red;">names</B> begin with <B STYLE="color: red;">{@code "data-"}</B>.
3167     * 
3168     * <BR /><BR />Since {@code class TagNode} is immutable, a new {@code TagNode} must be
3169     * instantiated, if any data-inner-tags are removed.  If no data-attributes are removed,
3170     * {@code 'this'} instance {@code TagNode} shall be returned instead.
3171     *
3172     * @return This will return a newly constructed {@code 'TagNode'} instance, if there were any
3173     * "<B STYLE="color: red;">Data</B> Attributes" that were removed by request.  If the original
3174     * {@code TagNode} has remained unchanged, a reference to {@code 'this'} shall be returned.
3175     * 
3176     * @throws ClosingTagNodeException This exception throws if {@code 'this'} instance of 
3177     * {@code TagNode} is a closing-version of the HTML Element.  Closing HTML Elements may not
3178     * have data attributes, because they simply are not intended to contain <I>any</I> attributes.
3179     */
3180    public TagNode removeDataAttributes() 
3181    {
3182        // Because this method expects to modify the TagNode, this exception-check is necessary.
3183        ClosingTagNodeException.check(this);
3184
3185        // Make sure to keep the quotes that were already used, we are removing attributes, and the
3186        // original attributes that aren't removed need to preserve their quotation marks.
3187        Properties          p       = this.allAV(true, true);
3188        Enumeration<Object> keys    = p.keys();
3189        int                 oldSize = p.size();
3190
3191        // Visit each Property Element, and remove any properties that have key-names which
3192        // begin with the word "data-"
3193        while (keys.hasMoreElements())
3194        {
3195            String key = keys.nextElement().toString();
3196            if (key.startsWith("data-")) p.remove(key);
3197        }
3198
3199        // If any of properties were removed, we have to rebuild the TagNode, and replace it.
3200        // REMEMBER: 'null' is passed to the 'SD' parameter, because we preserved the original
3201        //           quotes above.
3202        return (p.size() == oldSize) ? this : new TagNode(this.tok, p, null, this.str.endsWith("/>"));
3203    }
3204
3205    /**
3206     * Convenience Method.  Invokes {@link getDataAV(boolean)}.  
3207     * <BR /><BR />Attribute-<B STYLE="color: red;">names</B> will be in lower-case.
3208     */
3209    public Properties getDataAV() { return getDataAV(false); }
3210
3211    /**
3212     * This will retrieve and return any/all <B STYLE="color: red;">'data'</B> HTML Attributes.
3213     * "Data Inner-Tags" are simply the attributes inside of HTML Elements whose 
3214     * <B STYLE="color: red;">names</B> begin with <B STYLE="color: red;">{@code "data-"}</B>.
3215     *
3216     * @param preserveKeysCase When this parameter is passed <B>TRUE</B>, the case of the attribute
3217     * <B STYLE="color: red;">names</B> in the returned {@code Properties} table will have been
3218     * preserved.  When <B>FALSE</B> is passed, all {@code Properties}
3219     * <B STYLE="color: red">keys</B> shall have been converted to lower-case first.
3220     *
3221     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
3222     *
3223     * @return This will return a {@code java.util.Properties} of all 
3224     * <B STYLE="color: red;">data</B>-attributes which are found in {@code 'this'} HTML Element.
3225     * If no such attributes were found, 'null' shall not be returned by this method, but rather an
3226     * empty {@code Properties} instance will be provided, instead.
3227     *
3228     * @see TagNode#isClosing
3229     * @see TagNode#str
3230     * @see TagNode#tok
3231     * @see AttrRegEx#DATA_ATTRIBUTE_REGEX
3232     */
3233    public Properties getDataAV(boolean preserveKeysCase) 
3234    {
3235        Properties ret = new Properties();
3236
3237        // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
3238        //       field is only longer than the token, itself, by 3 or less characters cannot have
3239        //       attributes.v In that case, just return an empty 'Properties' instance.
3240        if (isClosing || (str.length() <= (tok.length() + 3))) return ret;
3241
3242        // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs
3243        //      ONLY PAIRS WHOSE KEY BEGINS WITH "data-" WILL MATCH
3244        // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign).
3245        // m.group(3): returns the 'value' portion of the key-value pair, after an '='
3246        Matcher m = AttrRegEx.DATA_ATTRIBUTE_REGEX.matcher(this.str);
3247
3248        // NOTE: HTML mandates attributes must be 'case-insensitive' to the attribute 'key-part'
3249        //      (but not necessarily the 'value-part')
3250        // HOWEVER: Java does not require anything for the 'Properties' class.
3251        // ALSO: Case is PRESERVED for the 'value-part' of the key-value pair.
3252        if (preserveKeysCase)
3253            while (m.find())
3254                ret.put(m.group(2), StringParse.ifQuotesStripQuotes(m.group(3)));
3255        else
3256            while (m.find())
3257                ret.put(m.group(2).toLowerCase(), StringParse.ifQuotesStripQuotes(m.group(3)));
3258 
3259        return ret;
3260    }
3261
3262    /**
3263     * Convenience Method.  Invokes {@link getDataAN(boolean)}.
3264     * <BR /><BR />Attribute-<B STYLE="color: red;">names</B> will be in lower-case.
3265     */
3266    public Stream<String> getDataAN() { return getDataAN(false); }
3267
3268    /**
3269     * This method will only return a list of all data-attribute <B STYLE="color: red;">names</B>.
3270     * The data-attribute <B STYLE="color: red;">values</B> shall not be included in the result.
3271     * An HTML Element "data-attribute" is any attribute inside of an HTML {@code TagNode} whose 
3272     * <B STYLE="color: red;">key-value</B> pair uses a <B STYLE="color: red;">key</B> that begins
3273     * with <B STYLE="color: red;">{@code "data-"}</B>...  <I>It is that simple!</I>
3274     *
3275     * @param preserveKeysCase When this parameter is passed <B>TRUE</B>, the case of the attribute
3276     * <B STYLE="color: red;">names</B> that are returned by this method will have been
3277     * preserved.  When <B>FALSE</B> is passed, they shall be converted to lower-case first.
3278     *
3279     * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDPC">
3280     *
3281     * @return Returns an instance of {@code Stream<String>}.  The attribute 
3282     * <B STYLE="color: red;">names</B> that are returned, are all converted to lower-case.
3283     * 
3284     * <BR /><BR />A return type of {@code Stream<String>} is used.  Please see the list below for
3285     * how to convert a {@code Stream} to another data-type.
3286     *
3287     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVT">
3288     *
3289     * <BR /><B>NOTE:</B> This method shall never return 'null' - even if there are no
3290     * <B STYLE="color: red;">data-</B>attribute <B STYLE="color: red;">key-value</B> pairs
3291     * contained by the {@code TagNode}.  If there are strictly zero such attributes, 
3292     * ({@code Stream.empty()}) shall be returned, instead.
3293     * 
3294     * @see #str
3295     * @see #tok
3296     * @see AttrRegEx#DATA_ATTRIBUTE_REGEX
3297     */
3298    public Stream<String> getDataAN(boolean preserveKeysCase) 
3299    {
3300        // Java Stream's can be quickly and easily converted to any data-structure the user needs.
3301        Stream.Builder<String> b = Stream.builder();
3302
3303        // NOTE: OPTIMIZED, "closing-versions" of the TagNode, and TagNode's whose 'str'
3304        //       field is only longer than the token, itself, by 3 or less characters cannot have
3305        //       attributes.  In that case, just return an empty 'Properties' instance.
3306        if (isClosing || (str.length() <= (tok.length() + 3))) return Stream.empty();
3307
3308        // This RegEx Matcher 'matches' against Attribute/InnerTag Key-Value Pairs
3309        //      ONLY PAIRS WHOSE KEY BEGINS WITH "data-" WILL MATCH
3310        // m.group(2): returns the 'key' portion of the key-value pair, before an '=' (equals-sign).
3311        // m.group(3): returns the 'value' portion of the key-value pair, after an '='
3312        Matcher m = AttrRegEx.DATA_ATTRIBUTE_REGEX.matcher(this.str);
3313
3314        // NOTE: HTML mandates attributes must be 'case-insensitive' to the attribute 'key-part'
3315        //      (but not necessarily the 'value-part')
3316        // HOWEVER: Java does not require anything for the 'Properties' class.
3317        // ALSO: Case is PRESERVED for the 'value-part' of the key-value pair.
3318        if (preserveKeysCase)   while (m.find()) b.accept(m.group(2));
3319        else                    while (m.find()) b.accept(m.group(2).toLowerCase());
3320 
3321        return b.build();
3322    }
3323
3324    // ********************************************************************************************
3325    // Java Methods
3326    // ********************************************************************************************
3327
3328    /**
3329     * This does a "longer version" of the parent {@code toString()} method.  This is because it
3330     * also parses and prints inner-tag <B STYLE="color: red;">key-value</B> pairs.  The ordinary
3331     * {@code public String toString()} method that is inherited from parent {@code class HTMLNode}
3332     * will just return the value of {@code class HTMLNode} field: {@code public final String str}.
3333     * 
3334     * @return A String with the inner-tag <B STYLE="color: red;">key-value</B> pairs specified.
3335     * 
3336     * <DIV CLASS="EXAMPLE">{@code 
3337     * // The following code, would output the text below
3338     * TagNode tn = new TagNode("<BUTTON CLASS='MyButtons' ONCLICK='MyListener();'>");
3339     * System.out.println(tn.toStringAV());
3340     *
3341     * // Outputs the following Text:
3342     * 
3343     * // TagNode.str: [<BUTTON class='MyButtons' onclick='MyListener();'>], TagNode.tok: [button], TagNode.isClosing: [false]
3344     * // CONTAINS a total of (2) attributes / inner-tag key-value pairs:
3345     * // (KEY, VALUE):   [onclick], [MyListener();]
3346     * // (KEY, VALUE):   [class], [MyButtons]
3347     * }</DIV>
3348     * 
3349     * @see #allAV()
3350     * @see #tok
3351     * @see #isClosing
3352     * @see HTMLNode#toString()
3353     */
3354    public String toStringAV()
3355    {
3356        StringBuilder sb = new StringBuilder();
3357
3358        // Basic information.  This info is common to ALL instances of TagNode
3359        sb.append(
3360            "TagNode.str: [" + this.str + "], TagNode.tok: [" + this.tok + "], " +
3361            "TagNode.isClosing: [" + this.isClosing + "]\n"
3362        );
3363
3364        // Not all instances of TagNode will have attributes.
3365        Properties attributes = this.allAV(false, true);
3366        sb.append(
3367            "CONTAINS a total of (" + attributes.size() + ") attributes / inner-tag " +
3368            "key-value pairs" + (attributes.size() == 0 ? "." : ":") + "\n"
3369        );
3370
3371        // If there are inner-tags / attributes, then add them to the output-string, each on a
3372        // separate line.
3373        for (String key : attributes.stringPropertyNames())
3374            sb.append("(KEY, VALUE):\t[" + key + "], [" + attributes.get(key) + "]\n");
3375
3376        // Build the string from the StringBuilder, and return it.
3377        return sb.toString();    
3378    }
3379
3380    /**
3381     * Java's {@code interface Cloneable} requirements.  This instantiates a new {@code TagNode}
3382     * with identical <SPAN STYLE="color: red;">{@code String str}</SPAN> fields, and also
3383     * identical <SPAN STYLE="color: red;">{@code boolean isClosing}</SPAN> and
3384     * <SPAN STYLE="color: red;">{@code String tok}</SPAN> fields.
3385     * 
3386     * @return A new {@code TagNode} whose internal fields are identical to this one.
3387     */
3388    public TagNode clone() { return new TagNode(str); }
3389
3390    /**
3391     * This sorts by:
3392     * 
3393     * <BR /><BR /><OL CLASS="JDOL">
3394     * <LI> by {@code String 'tok'} fields character-order (ASCII-alphabetical).
3395     *      <BR />The following {@code final String 'tok'} fields are ASCII ordered:
3396     *      {@code 'a', 'button', 'canvas', 'div', 'em', 'figure' ...}
3397     * </LI>
3398     * <LI>then (if {@code 'tok'} fields are equal) by the {@code public final boolean 'isClosing'}
3399     *      field. <BR />{@code TagNode's} that have a {@code 'isClosing'} set to <B>FALSE</B> come
3400     *      before {@code TagNode's} whose {@code 'isClosing'} field is set to <B>TRUE</B>
3401     * </LI>
3402     * <LI> finally, if the {@code 'tok'} and {@code 'isClosing'} fields are equal, they are
3403     *      sorted by <I>the integer-length of</I> {@code final String 'str'} field.
3404     * </LI>
3405     * </OL>
3406     * 
3407     * @param n Any other {@code TagNode} to be compared to {@code 'this' TagNode}
3408     * 
3409     * @return An integer that fulfils Java's
3410     * {@code interface Comparable<T> public boolean compareTo(T t)} method requirements.
3411     * 
3412     * @see #tok
3413     * @see #isClosing
3414     * @see #str
3415     */
3416    public int compareTo(TagNode n)
3417    {
3418        // Utilize the standard "String.compare(String)" method with the '.tok' string field.
3419        // All 'tok' fields are stored as lower-case strings.
3420        int compare1 = this.tok.compareTo(n.tok);
3421
3422        // Comparison #1 will be non-zero if the two TagNode's being compared had different
3423        // .tok fields
3424        if (compare1 != 0) return compare1;
3425
3426        // If the '.tok' fields were the same, use the 'isClosing' field for comparison instead.
3427        // This comparison will only be used if they are different.
3428        if (this.isClosing != n.isClosing) return (this.isClosing == false) ? -1 : 1;
3429    
3430        // Finally try using the entire element '.str' String field, instead.  
3431        return this.str.length() - n.str.length();
3432    }
3433
3434    // ********************************************************************************************
3435    // Internally used Regular Expressions, (STATIC FIELDS INSIDE STATIC CLASS)
3436    // ********************************************************************************************
3437
3438    /**
3439     * <CODE>TagNode Attribute Regular Expressions - Documentation</CODE><BR /><BR />
3440     * <EMBED CLASS="external-html" DATA-FILE-ID="TGARE">
3441     */
3442    public static final class AttrRegEx
3443    {
3444        private AttrRegEx() { }
3445
3446        /**
3447         * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDKVR">
3448         * @see TagNode#allAV(boolean, boolean)
3449         */
3450        public static final Pattern KEY_VALUE_REGEX = Pattern.compile(
3451            "(?:\\s+?" +                    // mandatory leading white-space
3452                "(([\\w-]+?)=(" +           // inner-tag name (a.k.a. 'key' or 'attribute-name')
3453                    "'[^']*?'"     + "|" +  // inner-tag value using single-quotes ... 'OR'
3454                    "\"[^\"]*?\""   + "|" + // inner-tag value using double-quotes ... 'OR'
3455                    "[^\"'>\\s]*"   +       // inner-tag value without quotes
3456            ")))",
3457            Pattern.CASE_INSENSITIVE | Pattern.DOTALL
3458        );
3459
3460        /**
3461         * A {@code Predicate<String>} Regular-Expression.
3462         * @see #KEY_VALUE_REGEX
3463         */
3464        public static final Predicate<String> KEY_VALUE_REGEX_PRED =
3465            KEY_VALUE_REGEX.asPredicate();
3466
3467        /**
3468         * <SPAN STYLE="color: red;"><B>LEGACY-REGEX:</B></SPAN> Was used by the method
3469         * {@code TagNode.AV(String)}, among others.  Note that this regular-expression will be
3470         * deprecated, since it is redundant.
3471         *
3472         * <BR /><BR /><B>CAPTURE GROUPS:</B> <I>Nearly</I> the entire Reg-Ex is surrounding by
3473         * parenthesis.  {@code m.group(1)} shall return a {@code String} that differs with
3474         * {@code m.group()}, less a leading {@code '='} (equals-sign).
3475         *
3476         * @see TagNode#AV(String)
3477         */
3478        public static final Pattern QUOTES_AND_VALUE_REGEX = Pattern.compile(
3479            // Matches, for example:  ='MyClass'   or    ="MyClass"   or   =MyClass
3480            "=(" + 
3481                "\"[^\"]*?\""   + "|" + // inner-tag value using single-quotes ... 'OR'
3482                "'[^']*?'"      + "|" + // inner-tag value using double-quotes ... 'OR'
3483                "[\\w-]+"       +       // inner-tag value without quotes
3484            ")",
3485            Pattern.DOTALL
3486        );
3487
3488        /**
3489         * A {@code Predicate<String>} Regular-Expression.
3490         * @see #QUOTES_AND_VALUE_REGEX
3491         */
3492        public static final Predicate<String> QUOTES_AND_VALUE_REGEX_PRED =
3493            QUOTES_AND_VALUE_REGEX.asPredicate();
3494
3495        /**
3496         * This matches all valid attribute-<B STYLE="color: red;">keys</B> <I>(not values)</I> of
3497         * HTML Element <B STYLE="color: red;">key-value pairs</B>.
3498         * 
3499         * <BR /><BR /><UL CLASS="JDUL">
3500         * <LI> <B>PART-1:</B> {@code [A-Za-z_]} The first character must be a letter or the
3501         *      underscore.
3502         * </LI>
3503         * <LI> <B>PART-2:</B> {@code [A-Za-z0-9_-]} All other characters must be alpha-numeric,
3504         *      the dash {@code '-'}, or the underscore {@code '_'}.
3505         * </LI>
3506         * </UL>
3507         * 
3508         * @see InnerTagKeyException#check(String[])
3509         * @see #allKeyOnlyAttributes(boolean)
3510         */
3511        public static final Pattern ATTRIBUTE_KEY_REGEX = 
3512            Pattern.compile("^[A-Za-z_][A-Za-z0-9_-]*$");
3513
3514        /**
3515         * A {@code Predicate<String>} Regular-Expression.
3516         * @see #ATTRIBUTE_KEY_REGEX
3517         */
3518        public static final Predicate<String> ATTRIBUTE_KEY_REGEX_PRED =
3519            ATTRIBUTE_KEY_REGEX.asPredicate();
3520
3521        /**
3522         * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDDAR">
3523         * @see TagNode#getDataAN()
3524         * @see TagNode#getDataAV()
3525         */
3526        public static final Pattern DATA_ATTRIBUTE_REGEX = Pattern.compile(
3527            // regex will match, for example:   data-src="https://cdn.imgur.com/MyImage.jpg"
3528            "(?:\\s+?" +                            // mandatory leading white-space
3529                "(data-([\\w-]+?)=" +               // data inner-tag name 
3530                    "(" +   "'[^']*?'"      + "|" + // inner-tag value using single-quotes ... 'OR'
3531                            "\"[^\"]*?\""   + "|" + // inner-tag value using double-quotes ... 'OR
3532                            "[^\"'>\\s]*"   +       // inner-tag value without quotes
3533                ")))",
3534            Pattern.CASE_INSENSITIVE | Pattern.DOTALL  
3535        );
3536
3537        /**
3538         * A {@code Predicate<String>} Regular-Expression.
3539         * @see #DATA_ATTRIBUTE_REGEX
3540         */
3541        public static final Predicate<String> DATA_ATTRIBUTE_REGEX_PRED =
3542            DATA_ATTRIBUTE_REGEX.asPredicate();
3543
3544        /**
3545         * <EMBED CLASS="external-html" DATA-FILE-ID="TGNDCSS">
3546         * @see TagNode#cssStyle()
3547         */
3548        public static final Pattern CSS_INLINE_STYLE_REGEX = Pattern.compile(
3549                // regex will match, for example:  font-weight: bold;
3550                "([_\\-a-zA-Z]+" + "[_\\-a-zA-Z0-9]*)" +    // CSS Style Property Name - Must begin with letter or underscore
3551                "\\s*?" + ":" + "\\s*?" +                   // The ":" symbol between property-name and property-value
3552                "([^;]+?\\s*)" +                            // CSS Style Property Value
3553                "(;|$|[\\w]+$)"                             // text after the "Name : Value" definition    
3554        );
3555
3556        /**
3557         * A {@code Predicate<String>} Regular-Expression.
3558         * @see #CSS_INLINE_STYLE_REGEX
3559         */
3560        public static final Predicate<String> CSS_INLINE_STYLE_REGEX_PRED =
3561            CSS_INLINE_STYLE_REGEX.asPredicate();
3562    }
3563
3564}